Skip to main content
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) and from the AG-UI transport Veryfront Studio uses.

Prerequisites

  • A Veryfront project with tools, prompts, or resources you want to expose (see Tools).
  • A way to mint bearer tokens for MCP clients (a static MCP_TOKEN env var is fine in development).

Setup

// 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. Export a local token and start the dev server:
export MCP_TOKEN=<TOKEN>
veryfront dev
Smoke test the route by sending an MCP initialize request and storing the session ID:
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:
// 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:
// 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:
// resources/docs.ts
import { resource } from "veryfront/resource";

export default resource({
  description: "Project documentation",
  pattern: "docs://project",
  load: async () => {
    const docs = await loadDocs();
    return { contents: docs };
  },
});

Manual registration

For tools, prompts, or resources not in the auto-discovered directories:
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:
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.