1+ ---
2+ alwaysApply: true
3+ description: "MCP Server Development Patterns for Tools, Resources, Prompts, and Custom Routes"
4+ ---
5+
6+ # MCP Server Development Guide
7+
8+ This project follows specific patterns for building MCP (Model Context Protocol) servers using Cloudflare Workers and Durable Objects.
9+
10+ ## Core Architecture
11+
12+ - Main server class extends `McpHonoServerDO<Env>` from `@nullshot/mcp`
13+ - Server configuration is split into three modules: [tools.ts](mdc:src/tools.ts), [resources.ts](mdc:src/resources.ts), [prompts.ts](mdc:src/prompts.ts)
14+ - Entry point is [server.ts](mdc:src/server.ts) which coordinates everything
15+
16+ ## MCP Server Class Pattern
17+
18+ ```typescript
19+ export class YourMcpServer extends McpHonoServerDO<Env> {
20+ constructor(ctx: DurableObjectState, env: Env) {
21+ super(ctx, env);
22+ }
23+
24+ getImplementation(): Implementation {
25+ return {
26+ name: 'YourMcpServer',
27+ version: '1.0.0',
28+ };
29+ }
30+
31+ configureServer(server: McpServer): void {
32+ setupServerTools(server);
33+ setupServerResources(server);
34+ setupServerPrompts(server);
35+ }
36+
37+ // OPTIONAL: Override for custom HTTP endpoints
38+ protected setupRoutes(app: Hono<{ Bindings: Env }>): void {
39+ super.setupRoutes(app); // Call parent to maintain MCP functionality
40+
41+ // Add custom routes here
42+ app.get('/api/custom', (c) => c.json({ message: 'custom endpoint' }));
43+ }
44+ }
45+ ```
46+
47+ ## Tools Pattern (src/tools.ts)
48+
49+ Tools are functions that clients can call. Always use this exact pattern:
50+
51+ ```typescript
52+ export function setupServerTools(server: McpServer) {
53+ server.tool(
54+ 'tool_name', // Unique tool identifier
55+ 'Brief tool description', // Human-readable description
56+ { // Zod schema for parameters
57+ param1: z.string().describe('Parameter description'),
58+ param2: z.number().optional().describe('Optional parameter'),
59+ },
60+ async ({ param1, param2 }) => { // Implementation function
61+ // Tool logic here
62+ return {
63+ content: [
64+ {
65+ type: "text",
66+ text: `Result: ${param1}`
67+ }
68+ ],
69+ // Optional: return additional data
70+ metadata: { /* any extra data */ }
71+ };
72+ }
73+ );
74+ }
75+ ```
76+
77+ **Key Requirements:**
78+ - Use Zod schemas with `.describe()` for all parameters
79+ - Return content array with type "text"
80+ - Handle errors gracefully with try/catch
81+ - Use descriptive tool names (snake_case)
82+
83+ ## Resources Pattern (src/resources.ts)
84+
85+ Resources provide persistent data access. Always use this pattern:
86+
87+ ```typescript
88+ export function setupServerResources(server: McpServer) {
89+ server.resource(
90+ 'resource_name',
91+ 'protocol://path/pattern/{id}', // URI pattern with placeholders
92+ async (uri: URL) => {
93+ try {
94+ // Parse URI to extract parameters
95+ const parts = uri.pathname.split('/');
96+ const id = parts[parts.length - 1];
97+
98+ // Fetch/compute resource data
99+ const data = await fetchResourceData(id);
100+
101+ return {
102+ contents: [
103+ {
104+ text: `Resource content: ${JSON.stringify(data)}`,
105+ uri: uri.href
106+ }
107+ ]
108+ };
109+ } catch (error) {
110+ throw new Error(`Failed to fetch resource: ${error.message}`);
111+ }
112+ }
113+ );
114+ }
115+ ```
116+
117+ **Key Requirements:**
118+ - Use descriptive URI patterns with placeholders
119+ - Parse URI to extract parameters
120+ - Return contents array with text and uri
121+ - Always wrap in try/catch for error handling
122+
123+ ## Prompts Pattern (src/prompts.ts)
124+
125+ Prompts provide reusable message templates. Always use this pattern:
126+
127+ ```typescript
128+ export function setupServerPrompts(server: McpServer) {
129+ server.prompt(
130+ 'prompt_name',
131+ 'Brief prompt description',
132+ (args?: { param?: string }) => ({ // Optional parameters
133+ messages: [{
134+ role: 'assistant', // or 'user', 'system'
135+ content: {
136+ type: 'text',
137+ text: `Your prompt content here. ${args?.param || ''}`
138+ }
139+ }]
140+ })
141+ );
142+ }
143+ ```
144+
145+ **Key Requirements:**
146+ - Use descriptive prompt names (snake_case)
147+ - Support optional parameters via args
148+ - Always return messages array
149+ - Use appropriate role (assistant/user/system)
150+
151+ ## Custom Routes with setupRoutes
152+
153+ To add custom HTTP endpoints beyond MCP functionality:
154+
155+ ```typescript
156+ protected setupRoutes(app: Hono<{ Bindings: Env }>): void {
157+ // CRITICAL: Always call parent first to maintain MCP functionality
158+ super.setupRoutes(app);
159+
160+ // Add custom endpoints
161+ app.get('/api/health', (c) => {
162+ return c.json({ status: 'healthy', timestamp: new Date().toISOString() });
163+ });
164+
165+ app.post('/api/webhook', async (c) => {
166+ const body = await c.req.json();
167+ // Process webhook
168+ return c.json({ received: true });
169+ });
170+
171+ // Access environment bindings via c.env
172+ app.get('/api/data', async (c) => {
173+ const result = await c.env.DATABASE.prepare('SELECT * FROM items').all();
174+ return c.json(result);
175+ });
176+ }
177+ ```
178+
179+ ## Development Checklist
180+
181+ When implementing MCP functionality:
182+
183+ 1. **Tools**: Define in `setupServerTools()` with Zod schemas
184+ 2. **Resources**: Define in `setupServerResources()` with URI patterns
185+ 3. **Prompts**: Define in `setupServerPrompts()` with message templates
186+ 4. **Custom Routes**: Override `setupRoutes()` if HTTP endpoints needed
187+ 5. **Error Handling**: Always use try/catch in async operations
188+ 6. **Types**: Use TypeScript interfaces for complex data structures
189+ 7. **Testing**: Test tools, resources, and prompts independently
190+
191+ ## Common Imports
192+
193+ ```typescript
194+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
195+ import { McpHonoServerDO } from '@nullshot/mcp';
196+ import { Implementation } from '@modelcontextprotocol/sdk/types.js';
197+ import { z } from 'zod';
198+ import { Hono } from 'hono';
199+ ```
0 commit comments