> ## Documentation Index
> Fetch the complete documentation index at: https://docs.codenullapp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP Server

> Connect any AI agent (Claude Code, Cursor, Codex) to your Codenull application through the built-in MCP server to create features, hooks, tables, menus and more.

Every Codenull application exposes a remote [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server at:

```
POST https://MY_APPLICATION_DOMAIN/api/mcp
```

Through it, any MCP-compatible AI agent can explore your application (features, hooks, entities, menus, roles) and — depending on the permissions you grant — create and update configuration, running through the exact same resolvers, validations and audit trail as the visual builder.

## 1. Create an MCP API key

MCP access is authenticated with personal API keys (`cnk_...`), bound to **your user** and **this application**, with explicit scopes and an expiration date. Create one with a GraphQL mutation against `https://MY_APPLICATION_DOMAIN/graphql` (authenticated as your user):

```graphql theme={null}
mutation {
  createMcpApiKey(
    name: "Claude Code - my laptop"
    scopes: ["config:read", "features:write"]
    expiresInDays: 90
  ) {
    apiKey # shown ONLY once — store it securely
    key {
      _id
      keyPrefix
      scopes
      expiresAt
    }
  }
}
```

<Warning>
  The `apiKey` value is returned **only once**. Codenull stores a hash and cannot recover it. Anyone
  holding the key can act as your user within its scopes — treat it like a password.
</Warning>

List your keys with the `mcpApiKeys` query and revoke one at any time with `revokeMcpApiKey(_id: "...")`.

### Scopes

| Scope            | Allows                                                                                               | Notes                                                                                                                                                                                        |
| ---------------- | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `config:read`    | Explore features, hooks, entities, menus, roles, env var **names**, GraphQL schema, authoring guides | Env var **values are never exposed**                                                                                                                                                         |
| `features:write` | Create/update features (standard and custom code)                                                    |                                                                                                                                                                                              |
| `tables:write`   | Create tables, update metadata, add fields                                                           |                                                                                                                                                                                              |
| `menus:write`    | Create/replace navigation menus                                                                      |                                                                                                                                                                                              |
| `hooks:write`    | Create/update hooks                                                                                  | **Admin-only grant.** Hook code is server-side JavaScript executed by the backend — this scope is equivalent to code execution on your application. Grant it only to keys you fully control. |
| `roles:write`    | Change which roles see each feature                                                                  | Admin-only grant                                                                                                                                                                             |
| `env:write`      | Create/update environment variables                                                                  | Admin-only grant                                                                                                                                                                             |
| `config:delete`  | Delete features/hooks (each call requires an explicit `confirm: true`)                               | Admin-only grant                                                                                                                                                                             |

Creating keys requires the **AI Designer agent permission** on your role. Effective permissions are re-evaluated on every request as the intersection of the key's scopes and your current role permissions — if your user is disabled or demoted, the key stops working within a minute. Admin-only scopes (`hooks:write`, `roles:write`, `env:write`, `config:delete`) additionally require that the key's user **still is an admin at request time**: losing the admin role deactivates those scopes immediately, even on existing keys.

## 2. Connect your agent

The key travels in the `Authorization` header. No OAuth flow is required.

Name the server **per application** — `codenull-<app-name>` — so connecting several Codenull
applications at the same time doesn't collide on the name (the keys panel generates the snippets
with this convention already). The examples below use `codenull-myapp`.

<Tabs>
  <Tab title="Claude Code">
    ```bash theme={null}
    claude mcp add --transport http codenull-myapp https://MY_APPLICATION_DOMAIN/api/mcp \
      --header "Authorization: Bearer cnk_YOUR_KEY"
    ```
  </Tab>

  <Tab title="Claude Desktop">
    Claude Desktop's custom connectors don't support custom `Authorization` headers yet, so the
    connection goes through the [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) stdio
    bridge (requires Node.js installed). Add to `claude_desktop_config.json` — on macOS at
    `~/Library/Application Support/Claude/claude_desktop_config.json`, on Windows at
    `%APPDATA%\Claude\claude_desktop_config.json`:

    ```json theme={null}
    {
      "mcpServers": {
        "codenull-myapp": {
          "command": "npx",
          "args": [
            "mcp-remote",
            "https://MY_APPLICATION_DOMAIN/api/mcp",
            "--header",
            "Authorization:${AUTH_HEADER}"
          ],
          "env": {
            "AUTH_HEADER": "Bearer cnk_YOUR_KEY"
          }
        }
      }
    }
    ```

    Then fully quit and reopen Claude Desktop — the tools appear under the tools icon. Passing the
    key via `env` (instead of inline in `args`) avoids a known `mcp-remote` bug on Windows with
    spaces in arguments.
  </Tab>

  <Tab title="Cursor">
    Add to `~/.cursor/mcp.json` (or the project's `.cursor/mcp.json`):

    ```json theme={null}
    {
      "mcpServers": {
        "codenull": {
          "url": "https://MY_APPLICATION_DOMAIN/api/mcp",
          "headers": {
            "Authorization": "Bearer cnk_YOUR_KEY"
          }
        }
      }
    }
    ```
  </Tab>

  <Tab title="Codex (CLI, app & IDE)">
    The Codex CLI, the Codex desktop app and the IDE extension all share the same MCP
    configuration in `~/.codex/config.toml`. Add:

    ```toml theme={null}
    [mcp_servers.codenull-myapp]
    url = "https://MY_APPLICATION_DOMAIN/api/mcp"
    http_headers = { "Authorization" = "Bearer cnk_YOUR_KEY" }
    ```

    To keep the key out of the file, you can reference an environment variable instead:

    ```toml theme={null}
    [mcp_servers.codenull-myapp]
    url = "https://MY_APPLICATION_DOMAIN/api/mcp"
    bearer_token_env_var = "CODENULL_MCP_KEY" # must contain the raw cnk_... key
    ```

    Restart the desktop app (or the CLI session) after editing for the server to load.

    <Info>
      `codex mcp add` only supports STDIO servers — for remote HTTP servers like this one, edit
      `config.toml` directly. Use the **global** `~/.codex/config.toml`: the desktop app is known
      to ignore project-scoped `.codex/config.toml` files.
    </Info>
  </Tab>

  <Tab title="MCP Inspector">
    For debugging:

    ```bash theme={null}
    npx @modelcontextprotocol/inspector
    ```

    Transport: `Streamable HTTP`, URL: `https://MY_APPLICATION_DOMAIN/api/mcp`, and add the
    `Authorization` header.
  </Tab>
</Tabs>

<Note>
  The server is stateless: only `POST /api/mcp` is served. Tools your key's scopes don't allow are
  not even listed. Claude.ai custom connectors currently require OAuth and are not yet supported —
  use Claude Code, or Claude Desktop through the `mcp-remote` bridge (tab above).
</Note>

<Tip>
  No extra setup, rules files or skills are needed on the agent side: the server ships its own
  playbook via the MCP `instructions` field during the connection handshake, so any compatible
  client (Claude Code, Cursor, Codex) automatically teaches its model the Codenull working method —
  explore first, read the authoring guide and schema, validate, then create.
</Tip>

## 3. Available tools

**Discovery (`config:read`)** — `list_features`, `get_feature_details`, `list_hooks`, `get_hook_details`, `list_entities`, `get_entity_details`, `list_menus`, `list_roles`, `list_environment_variables`, `get_graphql_schema`, `get_feature_authoring_guide`, `validate_feature_code`, `validate_hook_code`.

**Writing** — `create_feature` / `update_feature` / `edit_feature_code` (`features:write`), `create_hook` / `update_hook` / `edit_hook_code` (`hooks:write`), `create_table` / `update_table` / `add_table_field` (`tables:write`), `upsert_menu` (`menus:write`), `grant_feature_access` (`roles:write`), `set_environment_variable` (`env:write`), `delete_feature` / `delete_hook` (`config:delete`, require `confirm: true`).

<Tip>
  **See what the agent changed, line by line.** When modifying existing code, agents are instructed
  to use `edit_feature_code` / `edit_hook_code`, which take `old_str`/`new_str` patches — so the
  tool call itself shows exactly what changes. The server applies the patches to the current code,
  validates the result, persists it, and returns a **unified diff** of what was actually written
  (also returned by `update_feature`/`update_hook` when replacing code in full). If a patch doesn't
  match the current code, the edit is rejected instead of overwriting the file with a stale version.
</Tip>

A typical flow the agent follows to create a custom feature:

1. `get_feature_authoring_guide` — learns the allowed modules (antd, Apollo hooks, dayjs…), the runtime rules, and **your application's configured theme** (colors + shell CSS variables), used as the base palette when you don't ask for specific colors — explicit color requests always take precedence.
2. `get_graphql_schema` + `get_entity_details` — learns your data model.
3. `validate_feature_code` — dry-run validation of the JSX (never executed server-side). Also returns informational **warnings** when the code uses fixed hex colors or Tailwind color classes (fine if you asked for them; otherwise the theme is the default).
4. `create_feature` — persists through the same resolver the builder uses (id validation, default permissions, versioning).

## 4. Usage examples

Once connected, you talk to your agent in natural language — it discovers and calls the MCP tools on its own. These are real prompts you can paste into Claude Code or Cursor, with the tool sequence the agent typically runs.

### Explore an application you don't know

> "Connect to my Codenull app and give me an overview: what entities, features and hooks does it have?"

The agent calls `list_entities`, `list_features` and `list_hooks`, then summarizes your data model, screens and server-side logic. Requires only `config:read`.

### Create a table and a screen for it

> "Create a `Cliente` table with fields Nombre (text, required), Correo (text, unique) and FechaRegistro (date). Then build a screen to list and create clients, and add it to the side menu."

Typical sequence:

1. `get_entity_details` on an existing table — to copy your field type conventions.
2. `create_table` — creates `Cliente` with the fields (`tables:write`).
3. `get_feature_authoring_guide` + `get_graphql_schema` — learns the runtime rules and the generated `Cliente` CRUD operations.
4. `validate_feature_code` then `create_feature` — a custom feature with an antd table + creation form wired to `useQuery`/`useMutation` (`features:write`).
5. `upsert_menu` — adds the `/feature/clientes` entry to the side menu (`menus:write`).

### Add a validation hook

> "Add a beforeSave validation on the Facturas table: reject any invoice whose Total is negative. Follow the same style as the existing hooks."

Sequence: `list_hooks` + `get_hook_details` (to mimic your patterns) → `validate_hook_code` (syntax + rules dry-run) → `create_hook` with `type: "beforeSave"` and the table's `tableId`. Requires `hooks:write` (admin-granted).

### Modify an existing feature

> "In the feature `ventas_dashboard`, add a date-range filter and a card with the month's total."

Sequence: `get_feature_details` (reads the current `customCode`) → `validate_feature_code` → `update_feature`. Updates run through the same versioning flow as the builder — pass `status: "draft"`/`"published"` if you use draft versions.

### Grant access to a role

> "Make the new Clientes screen visible to the Ventas role, plus Admin."

Sequence: `list_roles` + `list_features` (to resolve ids) → `grant_feature_access` with the feature `_id` and the role ids (`roles:write`).

### Calling the server without an agent (raw JSON-RPC)

The endpoint speaks standard MCP over Streamable HTTP, so you can also script it directly:

```bash theme={null}
# List the tools your key can use
curl -s https://MY_APPLICATION_DOMAIN/api/mcp \
  -H "Authorization: Bearer cnk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

# Call a tool
curl -s https://MY_APPLICATION_DOMAIN/api/mcp \
  -H "Authorization: Bearer cnk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {
      "name": "list_features",
      "arguments": { "query": "ventas" }
    }
  }'
```

<Tip>
  Destructive tools (`delete_feature`, `delete_hook`) refuse to run unless the agent passes
  `confirm: true` — agents are instructed to ask you before confirming. If a tool you expect is
  missing from `tools/list`, your key lacks that scope (or you lost the role permission backing it).
</Tip>

## Security model

* **Keys**: 256-bit entropy, SHA-256 hashed at rest, mandatory expiry (max 365 days), revocable, `lastUsedAt` tracking. The `cnk_` prefix makes leaked keys easy to detect with secret scanners.
* **Tenant isolation**: each deployment serves one application; keys are bound to it and rejected elsewhere.
* **Attribution**: every mutation records your user as the author, same as in the builder.
* **Audit**: every tool call is logged (`mcp_audit_logs`) with redacted arguments — env var values and API keys never reach the log; long code payloads are stored as a preview plus SHA-256 hash. Entries are retained for one year.
* **Rate limits**: 60 requests/minute per key; failed authentication is limited per IP. Limits are tracked per server instance, so with N replicas the effective ceiling is N× the stated value.
* **Payload limits**: 2 MB per request, 200 KB per hook, 1 MB per feature code.
* **Prompt-injection guard**: read-tool responses are wrapped with a note marking application content as data, not instructions.

<Warning>
  `hooks:write` deserves special care: hook `content` is JavaScript executed by your application's
  backend. A leaked key with this scope means remote code execution. Only admins can grant it, and
  every hook write is audited with a hash of the exact code persisted. Application owners receive
  an email notification on every hook **creation and modification**, whichever entry point was
  used (visual builder or MCP).
</Warning>
