@roostjs/mcp Guides
Task-oriented instructions for building MCP servers with tools, resources, and prompts.
How to create an MCP server
Extend McpServer, declare the classes to expose, and set server metadata. The server handles JSON-RPC over HTTP automatically.
import { McpServer } from '@roostjs/mcp';
import { SearchTool } from './tools/SearchTool';
import { DocsResource } from './resources/DocsResource';
import { SummarizePrompt } from './prompts/SummarizePrompt';
export class AppServer extends McpServer {
tools = [SearchTool];
resources = [DocsResource];
prompts = [SummarizePrompt];
serverName(): string {
return 'My App MCP Server';
}
serverVersion(): string {
return '1.0.0';
}
serverInstructions(): string {
return 'Use these tools to search and manage content in My App.';
}
}Mount the server on a route to accept MCP connections from clients like Claude or Cursor. See @roostjs/mcp reference for mounting details.
How to define MCP tools
Extend McpTool and implement description(), schema(), and handle(). Return an McpResponse.
import { McpTool, McpResponse } from '@roostjs/mcp';
import { schema } from '@roostjs/schema';
import type { McpRequest } from '@roostjs/mcp';
import { Post } from '../../models/Post';
export class CreatePostTool extends McpTool {
description(): string {
return 'Create a new blog post draft';
}
schema(s: typeof schema) {
return {
title: s.string().description('Post title').minLength(1),
body: s.string().description('Post body in markdown'),
tags: s.array().items(s.string()).optional().description('Optional tags'),
};
}
async handle(request: McpRequest): Promise<McpResponse> {
const title = request.get<string>('title');
const body = request.get<string>('body');
const tags = request.get<string[] | undefined>('tags');
const post = await Post.create({ title, body, tags: tags?.join(','), status: 'draft' });
return McpResponse.text(`Created draft post "${title}" with ID ${post.attributes.id}`);
}
}To conditionally hide a tool based on configuration or feature flags, implement shouldRegister():
shouldRegister(): boolean {
return process.env.FEATURE_POST_CREATION === 'true';
}How to expose resources via MCP
Extend McpResource to provide documents or data the AI can read. URIs are unique identifiers for each resource.
import { McpResource, McpResponse } from '@roostjs/mcp';
import type { McpRequest } from '@roostjs/mcp';
export class SchemaResource extends McpResource {
uri(): string {
return 'db://schema';
}
description(): string {
return 'The current database schema — table and column definitions';
}
mimeType(): string {
return 'text/plain';
}
async handle(request: McpRequest): Promise<McpResponse> {
// Return a description of the database schema
const schemaText = `
Tables:
- users (id INTEGER PK, name TEXT, email TEXT UNIQUE, created_at TEXT)
- posts (id INTEGER PK, title TEXT, body TEXT, author_id INTEGER FK users.id, created_at TEXT)
- comments (id INTEGER PK, body TEXT, post_id INTEGER FK posts.id, author_id INTEGER FK users.id)
`.trim();
return McpResponse.text(schemaText);
}
}Use protocol-style URIs (docs://, db://, config://) to namespace related resources. The AI client uses these URIs when requesting resource content.
How to define MCP prompts
Extend McpPrompt to create reusable prompt templates the AI client can invoke by name.
import { McpPrompt, McpResponse } from '@roostjs/mcp';
import { schema } from '@roostjs/schema';
import type { McpRequest } from '@roostjs/mcp';
export class ReviewCodePrompt extends McpPrompt {
description(): string {
return 'Review a code snippet for bugs, security issues, and style';
}
schema(s: typeof schema) {
return {
code: s.string().description('The code snippet to review'),
language: s.string().description('Programming language').optional(),
};
}
async handle(request: McpRequest): Promise<McpResponse | McpResponse[]> {
const code = request.get<string>('code');
const language = request.get<string | undefined>('language') ?? 'unknown';
const prompt = `Please review the following ${language} code for:
1. Bugs and logic errors
2. Security vulnerabilities
3. Performance issues
4. Code style and readability
\`\`\`${language}
${code}
\`\`\`
Provide specific, actionable feedback.`;
return McpResponse.text(prompt);
}
}Prompts can return a single McpResponse or an array of them. Arrays are useful for multi-turn prompt scaffolding.
How to expose AI Search via MCP
Use AiSearchResource to make a Cloudflare AI Search index available as an MCP resource. MCP clients can then query it by URI.
1. Add the AI Search binding to wrangler.jsonc
{
"ai_search": [
{ "binding": "AI_SEARCH", "index_name": "my-docs" }
]
}2. Create a subclass to capture the binding
AiSearchResource requires constructor arguments, so wrap it in a subclass that reads from env:
import { AiSearchResource } from '@roostjs/mcp';
export class DocsSearch extends AiSearchResource {
constructor() {
// env must be accessible here — inject via closure or module-level reference
super(env.AI_SEARCH, 'docs');
}
}3. Register on your MCP server
import { McpServer } from '@roostjs/mcp';
import { DocsSearch } from './resources/DocsSearch';
export class AppServer extends McpServer {
resources = [DocsSearch];
serverName() { return 'My App'; }
serverVersion() { return '1.0.0'; }
}4. Query the resource from an MCP client
The resource URI is aisearch://docs. Clients pass query, and optionally metadataFilters and pathFilters:
{
"uri": "aisearch://docs",
"query": "how do I reset my password",
"metadataFilters": { "section": "support" },
"pathFilters": ["/help/"]
}The response contains an answer string and a sources array with url, title, and excerpt for each matched document.