---
title: Agents & MCP
description: Connect a coding agent to the Framewerk MCP server and have it generate a rendered screen.
status: shipped
url: https://getairframe.com/framewerk/integrations/agents-and-mcp
---

> Part of the Framewerk docs. Index of every page: /framewerk/llms.txt

# Agents & MCP

**Status: shipped.**

Framewerk is authorable by an agent. Connect a coding agent to the MCP server,
give it one prompt, and it writes a spec you can render — no designer in the
loop.

This page assumes the developer from the [Introduction](./introduction.md).
It does not restate what a spec is.

## Connect

The server speaks MCP over HTTP. Add it to your client's MCP config with the
URL your instance exposes (cloud or local). On connect, the server returns
instructions that include the skills install one-liner — the same command as
on the [Skills](./skills.md) page — so the agent can teach itself the
conventions before it writes anything.

Minimal client config shape (exact keys vary by client):

```json
{
  "mcpServers": {
    "framewerk": {
      "url": "https://airframe.to/mcp"
    }
  }
}
```

Once connected, the agent has the Framewerk tools: read and write screens,
components, styles, flows, and validate the result.

## One prompt that produces a screen

Paste this into a session with the server connected:

> Create a Framewerk spec with two screens. `home` has a column layout: a
> heading "Coffee Cart", a text node listing three drinks, and a primary
> button "Order" whose action is `navigate(order)`. `order` has a heading
> "Order" and a button "Back" with `action: back()`. Give `home` a state
> variable `oatMilk` (boolean, default `"false"`) and a badge content
> "Oat milk" with `showWhen: "$oatMilk == true"`, plus a button that runs
> `toggleState(oatMilk)`. Validate the document with `validate()` from
> `@framewerk/spec` and fix any errors before you finish.

The result is a document like this:

**What the prompt produces**

```json
{
  "formatVersion": "1.0",
  "screens": [
    {
      "id": "home",
      "stateVars": { "oatMilk": { "type": "boolean", "default": "false" } },
      "layout": {
        "_id": "home-root",
        "type": "column",
        "gap": 12,
        "padding": 24,
        "children": [
          { "_id": "h", "type": "text", "variant": "heading", "content": "Coffee Cart" },
          { "_id": "list", "type": "text", "content": "Espresso · Cortado · Filter" },
          { "_id": "oat", "type": "button", "content": "Toggle oat milk", "action": "toggleState(oatMilk)" },
          { "_id": "badge", "type": "badge", "content": "Oat milk", "showWhen": "$oatMilk == true" },
          { "_id": "order", "type": "button", "variant": "primary", "content": "Order", "action": "navigate(order)" }
        ]
      }
    },
    {
      "id": "order",
      "layout": {
        "_id": "order-root",
        "type": "column",
        "gap": 12,
        "padding": 24,
        "children": [
          { "_id": "oh", "type": "text", "variant": "heading", "content": "Order" },
          { "_id": "back", "type": "button", "content": "Back", "action": "back()" }
        ]
      }
    }
  ],
  "flows": [{ "from": "home", "to": "order", "trigger": "Order" }]
}
```

Render it with the [Quick Start](./quick-start.md) host, or open it in the
[Playground](https://airframe.to/framewerk/playground).

## Why validation matters here

Agents make two mistakes that look fine on screen: a `navigate()` to a
renamed screen, and an action string that parses as nothing. Both are
**warnings** from `validate()`, not errors. Run the validator in your loop
and fail on warnings — the button will not tell you it is dead.

See [Validation](/framewerk/core/validation.md).
