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

# MCP server

> Expose tools, prompts, and resources over Model Context Protocol.

Mount an MCP server route in your app to expose your project's tools, prompts, and resources to MCP clients like Claude Desktop. The runtime auto-discovers everything under `tools/`, `prompts/`, and `resources/`, so the route handler is essentially a thin auth shim.

This is the application-facing MCP server. It is separate from `veryfront mcp` (the CLI's dev MCP server, see [Coding agents](/docs/code/guides/coding-agents)) and from the AG-UI transport Veryfront Studio uses.

## Prerequisites

* A Veryfront project with tools, prompts, or resources you want to expose
  (see [Tools](/docs/code/guides/tools)).
* A way to mint bearer tokens for MCP clients (a static `MCP_TOKEN` env var
  is fine in development).

## Setup

```ts theme={null}
// app/api/mcp/route.ts
import { createMCPServer } from "veryfront/mcp";

const server = createMCPServer({
  enabled: true,
  auth: {
    type: "bearer",
    validate: async (token) => token === Deno.env.get("MCP_TOKEN"),
  },
});
const handler = server.createHTTPHandler();

export const POST = handler;
export const DELETE = handler;
export const OPTIONS = handler;
```

Mount the handler on your application-owned MCP route. All auto-discovered tools, prompts, and resources are then exposed through the app-facing MCP transport.

MCP clients are not browsers, so they hold no CSRF cookie to echo. Exclude the route from the CSRF check, or every `POST` receives `403`:

```ts theme={null}
export default {
  security: {
    csrf: {
      excludePaths: ["/api/mcp"],
    },
  },
};
```

The route stays protected by the `auth` field below, which is what authenticates an MCP client. See [Security headers and CSP](/docs/code/guides/security-headers) for the CSRF contract.

Export a local token and start the dev server:

```bash theme={null}
export MCP_TOKEN=<TOKEN>
veryfront dev
```

Smoke test the route by sending an MCP `initialize` request and storing the session ID:

```bash theme={null}
SESSION_ID=$(curl -i http://localhost:3000/api/mcp \
  -H "Authorization: Bearer $MCP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"0.0.0"}}}' \
  | awk -F': ' 'tolower($1) == "mcp-session-id" {print $2}' \
  | tr -d '\r')
```

The response includes a `MCP-Session-Id` header and a JSON-RPC result with server capabilities.

### Auth is required

`auth` is a required field. The server fails closed at construction time if
it is missing. Options:

* `{ type: "bearer", validate }` (recommended for production): validates a
  bearer token against your own logic.
* `{ type: "none", allowUnauthenticated: true }`: **local development only**.
  Must be set explicitly; accepts every request without any check. Do not ship
  this to production.

The HTTP transport is session-based:

* clients `POST` `initialize`
* the server returns `MCP-Session-Id`
* subsequent requests send that header back
* `DELETE` with the session header ends the session

## Tools

Tools defined in `tools/` are automatically available via MCP:

```ts theme={null}
// tools/search-docs.ts
import { defineSchema } from "veryfront/schemas";
import { tool } from "veryfront/tool";

export default tool({
  description: "Search the documentation",
  inputSchema: defineSchema((v) =>
    v.object({
      query: v.string().describe("Search query"),
      limit: v.number().default(10).describe("Max results"),
    })
  )(),
  execute: async ({ query, limit }) => {
    const results = await searchIndex(query, limit);
    return { results };
  },
});
```

An MCP client can discover this tool's schema and call it.

## Prompts

Prompts defined in `prompts/` are exposed as MCP prompt templates:

```ts theme={null}
// prompts/code-review.ts
import { prompt } from "veryfront/prompt";

export default prompt({
  description: "Review code for quality issues",
  content: `Review the following code for:
- Security vulnerabilities
- Performance issues
- Code style problems

Code to review:
{{code}}`,
});
```

## Resources

Resources are data sources that MCP clients can read:

```ts theme={null}
// resources/docs.ts
import { resource } from "veryfront/resource";
import { defineSchema } from "veryfront/schemas";

export default resource({
  description: "Project documentation",
  pattern: "docs://project",
  paramsSchema: defineSchema((v) => v.object({}))(),
  load: async () => {
    const docs = await Deno.readTextFile("README.md");
    return { contents: docs };
  },
});
```

Resource patterns use `:name` URI-template parameters. Common forms include:

* hierarchical paths such as `/docs/:section`;
* rootless hierarchical paths such as `docs:collection/:id`;
* embedded and query parameters such as `/files/file-:base.:ext?lang=:lang`;
* opaque literal identifiers such as `urn:isbn`, where colons do not declare parameters.

Parameter names in one pattern must be unique and separated by literal text. A `:` that
directly follows an alphanumeric character is always data, never a parameter: write
`/files/file-:id`, not `/files/file:id`. This is the same uniform rule that keeps opaque
identifiers like `urn:isbn` literal.

For embedded
parameters, the first following literal belongs to the template: `/file-:base.:ext` reads
`file-report.final.pdf` as `base=report` and `ext=final.pdf`. Path captures stop at raw `/`,
`?`, or `#`; query captures stop at raw `&` or `#`; and fragment captures cannot cross another `#`.
Clients can percent-encode those characters inside a value.

Captures are decoded exactly once
before schema validation and loading, so `%2F` becomes `/`, `%252F` becomes `%2F`, and `+`
stays `+`. A malformed percent escape does not match the resource and its loader is not called.

Set `mcp: { enabled: false }` to keep a resource registered for application use while hiding it
from MCP `resources/list`, `resources/templates/list`, and `resources/read`.

## Manual registration

For tools, prompts, or resources not in the auto-discovered directories:

```ts theme={null}
import { defineSchema } from "veryfront/schemas";
import { registerTool } from "veryfront/mcp";
import { tool } from "veryfront/tool";

registerTool(
  "custom-tool",
  tool({
    description: "A custom tool",
    inputSchema: defineSchema((v) =>
      v.object({
        input: v.string().describe("Text to transform"),
      })
    )(),
    execute: async ({ input }) => ({ result: input.toUpperCase() }),
  }),
);
```

## Transport note

This guide is about the application-facing MCP server from `veryfront/mcp`.

It is not the same surface as the CLI development server started with `veryfront mcp`, which exposes Veryfront development/runtime tools rather than your app's MCP route.

## Verify it worked

Use any MCP-aware client (Claude Desktop, an MCP CLI, or `curl`) to call the
`tools/list` method:

```bash theme={null}
SESSION_ID=$(curl -i http://localhost:3000/api/mcp \
  -H "Authorization: Bearer $MCP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"0.0.0"}}}' \
  | awk -F': ' 'tolower($1)=="mcp-session-id"{gsub(/\r/,"",$2); print $2}')

curl -X POST http://localhost:3000/api/mcp \
  -H "Authorization: Bearer $MCP_TOKEN" \
  -H "MCP-Session-Id: $SESSION_ID" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
```

A working server returns a JSON-RPC response that lists every registered tool.
Calling without the bearer token returns `401 Unauthorized`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.