Chapter 10 of 15

AI, MCP, and agents that know the framework

AI shows up three times in Freddy's week: inside his app, suggesting tags; outside it, as Claude adding a note for him; and in his editor, as the coding agent writing the app. Elyra Framework has something for each.

AI in the app

The AI SDK is a Laravel-inspired API for agents, tools, structured output, images, audio and embeddings over Anthropic and OpenAI, behind the ai feature. It runs in the Rust backend, so a key never reaches the page. With AiProvider, a command resolves the client:

#[derive(serde::Deserialize, JsonSchema)]
struct TagSuggestion { tags: Vec<String> }

#[command]
async fn suggest_tags(ctx: Ctx, body: String) -> Result<TagSuggestion, String> {
    ctx.get::<Ai>().chat()
        .instructions("Suggest at most three short tags for this note.")
        .prompt_as(body)
        .await
        .map_err(|e| e.to_string())
}

prompt_as forces the model to answer with JSON matching the struct and deserializes it; flat structs work best. For a summary that paints as it is written, stream yields deltas, and the guide's pattern is to emit each one on the event bus and subscribe in Svelte, chapter 4's channel again. Retries are on by default, failover can try the other provider, and plain prompts can be cached.

Where the key lives. The SDK reads ANTHROPIC_API_KEY and OPENAI_API_KEY from the environment, which is fine on Freddy's own machine. Chapter 9's warning stands for an app he gives to others: a key in a desktop binary is a key its users have. The SDK also reads ANTHROPIC_BASE_URL and OPENAI_BASE_URL, the overrides for a proxy or gateway, which is where a shared key belongs. And the guide is honest about testing: the live provider calls only run when ELYRA_AI_LIVE=1 and a key are set, and are skipped in CI.

The app as an MCP server

Turn it around: an AI agent in Claude, Cursor or VS Code can call the app's commands as tools, through the same middleware and validation as the page. Nothing is exposed until Freddy says so:

App::new()
    .commands(resources::commands())
    .mcp(Mcp::new()
        .allow_abilities(["notes.view", "notes.create"])
        .allow_ability("notes.delete")
        .confirm("notes.delete"))

A command is a tool when its ability is granted here, a grant separate from the page's. Its /// doc comment is the tool's description, so Freddy writes those for a model. A live command is marked read-only, and one that can run without arguments is also a resource the agent can read and subscribe to, told when the result changes, the way a window is.

confirm makes the server ask the user before such a call runs, in the MCP client, with the tool, the first line of its description and the arguments. The server enforces it: a client that cannot ask gets an error and the tool does not run, and so does a decline or no answer within 10 minutes. Every tool takes at most 60 calls a minute by default, a stop for an agent caught in a loop.

rata mcp inspect      # what the agent will see
rata mcp install      # the config that connects Claude to this app

The client launches freddy-notes --mcp. If the app is open, the agent works inside it over a private local endpoint, and the windows update as it adds a note; if not, the app serves MCP headless. On the page, onAgent shows a toast when a call starts, and in middleware a request's origin is Origin::Agent, so Freddy's audit line from chapter 4 can say an agent did it.

Agents that know the framework

The third agent is the one writing Freddy's code. Since 0.16.0 it does not have to guess at Tauri's or Laravel's API:

rata ai:install       # guidelines, skills, and the dev MCP server
rata ai:update        # refresh them after upgrading rata

ai:install writes the framework's guidelines as a block in AGENTS.md, and in CLAUDE.md unless it imports AGENTS.md; five skills in .agents/skills and .claude/skills, loaded only when they apply, for commands, models and migrations, resources, live queries and the Laravel backend; and an elyra-framework server in .mcp.json next to the servers already there. Only its own block and skills are rewritten, so Freddy's notes in AGENTS.md survive.

That server is rata mcp dev, and it tells the coding agent what the app is: every command with its abilities and middleware, the models, the database schema, the migrations applied or pending, the newest error in the app's log, and the guides for this version, searched. Every tool only reads, a SQLite database is opened read-only, and the code is read with syn, so it needs no build.

The difference between the two servers is worth keeping straight. The app's own MCP server, from the section above, is the app's commands for an agent using the app. rata mcp dev is the project for an agent writing it. The guides are AI SDK, AI agents (MCP) and Coding agents.

What you learned

  • The AI SDK gives chat, structured output, streaming, retries and failover in Rust; keys come from the environment, and a shared key belongs behind a proxy.
  • .mcp(Mcp::new()...) makes granted commands tools for AI agents, with server-enforced confirmation and rate limits.
  • rata ai:install gives the coding agent the framework's guidelines, skills and rata mcp dev, which only reads.
Next: in Chapter 11 Freddy connects the app to his Laravel backend.