route.ts
Complete generic Next.js API route example implementing MCP protocol with bearer token authentication, example tools, resources, and integration patterns for any application
route.tsv1.0.015.7 KB
route.ts(typescript)
1/**
2 * Generic Remote MCP Server Implementation
3 *
4 * This is a complete example of a Next.js API route that implements the Model Context Protocol
5 * over HTTP using the mcp-handler package and Anthropic's MCP SDK.
6 *
7 * Uses Vercel's MCP Adapter (mcp-handler): https://github.com/vercel/mcp-adapter
8 *
9 * IMPORTANT - Application-Specific Components:
10 * This example shows integration patterns that should be adapted to your application:
11 * - MockIdentityService: Replace with your authentication system
12 * - MockSecurityService: Replace with your rate limiting/security middleware
13 * - QuickstartCommand: Demonstrates the Utaba Micro-Block Command pattern
14 * (see utaba/main/patterns/micro-block/README.md for the full architecture)
15 * - ApiContext: Security context pattern - adapt to your middleware
16 *
17 * Key Features:
18 * - Bearer token authentication
19 * - One example tool (quickstart)
20 * - One example resource (quickstart guide)
21 * - Proper error handling with MCP error codes
22 * - Rate limiting and security context integration
23 * - Generic patterns that can be adapted to any application
24 *
25 * For guidance on implementing this example, see the UCM here:
26 * ucm:utaba/main/guidance/development/nextjs-remote-mcp-server.md
27 *
28 */
29
30import { McpServer, ResourceMetadata } from "@modelcontextprotocol/sdk/server/mcp.js";
31import {
32 McpError,
33 TextContent,
34 CallToolResult,
35 ServerRequest,
36 ServerNotification,
37 ReadResourceResult
38} from "@modelcontextprotocol/sdk/types.js";
39import { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";
40import { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
41import { createMcpHandler, withMcpAuth } from "mcp-handler";
42import { NextRequest } from "next/server";
43
44// ================================
45// MCP Error Codes (Standard)
46// ================================
47enum McpErrorCode {
48 InvalidRequest = -32600,
49 MethodNotFound = -32601,
50 InvalidParams = -32602,
51 InternalError = -32603,
52 UnuserIdized = -32001,
53}
54
55// ================================
56// Type Definitions
57// ================================
58
59/**
60 * Authentication ticket containing user information and permissions
61 * This would typically come from your application's auth system
62 */
63interface AuthTicket {
64 isAuthenticated: boolean;
65 userId?: string;
66 permissions: string[];
67 // Add other user-specific data your application needs
68}
69
70/**
71 * Extended context information passed through MCP auth
72 */
73interface AuthExtra {
74 authTicket: AuthTicket;
75 request: NextRequest;
76 timestamp: string;
77}
78
79/**
80 * API Context for business operations
81 * This represents the security and request context for your business logic
82 */
83interface ApiContext {
84 securityContext: {
85 authTicket: AuthTicket;
86 };
87 requestContext: {
88 method: string;
89 timestamp: string;
90 activityType: string;
91 };
92}
93
94// ================================
95// Business Logic Interfaces
96// ================================
97
98/**
99 * Example command input for generating quickstart content
100 */
101interface QuickstartInput {
102 userId:string;
103 serverType: 'remote' | 'local';
104 includeExamples?: boolean;
105}
106
107/**
108 * Example command output
109 */
110interface QuickstartOutput {
111 markdown: string;
112 timestamp: string;
113}
114
115// ================================
116// Mock Business Services
117// ================================
118
119/**
120 * Mock identity service for token validation
121 * In a real implementation, this would integrate with your auth system
122 */
123class MockIdentityService {
124 async validateToken(bearerToken: string): Promise<AuthTicket | null> {
125 // Parse bearer token
126 //Note: This example uses PAT, but in your system you should adapt this to suit.
127 const [userId, apiKey] = bearerToken.split(':');
128
129 if (!userId || !apiKey) {
130 return null;
131 }
132
133 // Mock validation - in reality, check against your auth system
134 if (apiKey === 'demo-api-key' || apiKey === 'abcdef') {
135 return {
136 isAuthenticated: true,
137 userId: userId,
138 permissions: ['read', 'write']
139 };
140 }
141
142 return null;
143 }
144}
145
146/**
147 * Mock service for creating API security context
148 * This would integrate with your rate limiting and security systems
149 */
150class MockSecurityService {
151 async createApiContext(
152 request: NextRequest,
153 activityType: string,
154 method: string,
155 authTicket: AuthTicket
156 ): Promise<ApiContext> {
157 // In a real implementation, this would:
158 // - Check rate limits
159 // - Log security events
160 // - Validate permissions
161 // - Create audit trails
162
163 return {
164 securityContext: {
165 authTicket
166 },
167 requestContext: {
168 method,
169 timestamp: new Date().toISOString(),
170 activityType
171 }
172 };
173 }
174}
175
176/**
177 * Mock business command for generating quickstart content
178 */
179class QuickstartCommand {
180 constructor(private input: QuickstartInput) {}
181
182 async execute(): Promise<QuickstartOutput> {
183 const examples = this.input.includeExamples ? `
184
185## Quick Examples
186
187\`\`\`typescript
188// Example API call
189const result = await fetch('/api/data');
190\`\`\`
191` : '';
192
193 const markdown = `# Welcome to ${this.input.serverType === 'remote' ? 'Remote' : 'Local'} MCP Server
194
195Hello **${this.input.userId}**!
196
197This is your quickstart guide for using the MCP server.
198
199## Getting Started
200
2011. Use the health check tool to verify connectivity
2022. Explore available tools with the list command
2033. Start building with your business logic
204
205${examples}
206
207## Available Operations
208
209- **Health Check**: Verify server status
210- **Resource Access**: Get documentation and guides
211- **Custom Tools**: Your application-specific functionality
212
213---
214*Generated at ${new Date().toISOString()}*`;
215
216 return {
217 markdown,
218 timestamp: new Date().toISOString()
219 };
220 }
221}
222
223// ================================
224// Service Instances
225// ================================
226const identityService = new MockIdentityService();
227const securityService = new MockSecurityService();
228
229// ================================
230// MCP Tool Implementations
231// ================================
232
233/**
234 * Quickstart tool - provides getting started information
235 */
236const quickstartTool = async (authInfo?: AuthInfo): Promise<CallToolResult> => {
237 const extra = authInfo?.extra as AuthExtra;
238
239 // Create API context with security validation
240 const context = await securityService.createApiContext(
241 extra.request,
242 'quickstart',
243 'GET',
244 extra.authTicket
245 );
246
247 // Execute business logic
248 const command = new QuickstartCommand({
249 userId: context.securityContext.authTicket.userId,
250 serverType: 'remote',
251 includeExamples: true
252 });
253
254 const result = await command.execute();
255 return formatMcpResponse(result.markdown);
256};
257
258/**
259 * Health check tool - verifies system status
260 */
261const healthCheckTool = async (authInfo?: AuthInfo): Promise<CallToolResult> => {
262 const extra = authInfo?.extra as AuthExtra;
263
264 const context = await securityService.createApiContext(
265 extra.request,
266 'health-check',
267 'GET',
268 extra.authTicket
269 );
270
271 const healthData = {
272 status: "healthy",
273 timestamp: new Date().toISOString(),
274 version: "1.0.0",
275 userId: context.securityContext.authTicket.userId,
276 authenticated: true,
277 permissions: context.securityContext.authTicket.permissions
278 };
279
280 return formatMcpResponse(healthData);
281};
282
283// ================================
284// MCP Resource Implementations
285// ================================
286
287/**
288 * Read resource handler for MCP resources
289 */
290async function readResource(uri: string, authInfo?: AuthInfo): Promise<ReadResourceResult> {
291 const extra = authInfo?.extra as AuthExtra;
292
293 const context = await securityService.createApiContext(
294 extra.request,
295 'read-resource',
296 'GET',
297 extra.authTicket
298 );
299
300 switch (uri) {
301 case "app://quickstart":
302 // Generate personalized quickstart content
303 const command = new QuickstartCommand({
304 userId: context.securityContext.authTicket.userId,
305 serverType: 'remote',
306 includeExamples: true
307 });
308
309 const result = await command.execute();
310
311 return {
312 contents: [{
313 uri: "app://quickstart",
314 mimeType: "text/markdown",
315 text: result.markdown
316 }]
317 };
318
319 default:
320 throw new McpError(McpErrorCode.InvalidParams, `Resource not found: ${uri}`);
321 }
322}
323
324// ================================
325// Utility Functions
326// ================================
327
328/**
329 * Format response data for MCP protocol
330 */
331function formatMcpResponse(data: any): CallToolResult {
332 if (typeof data === 'string') {
333 return {
334 content: [{ type: 'text', text: data } as TextContent]
335 };
336 } else {
337 return {
338 content: [{ type: 'text', text: JSON.stringify(data, null, 2) } as TextContent]
339 };
340 }
341}
342
343/**
344 * Wrap errors in MCP format
345 */
346const wrapMcpError = (error: McpError): CallToolResult => {
347 return {
348 content: [{
349 type: "text",
350 text: `Error: ${error.message}${error.data ? '\nData: ' + JSON.stringify(error.data) : ''}`
351 }],
352 isError: true,
353 };
354};
355
356/**
357 * Execute tool with comprehensive error handling
358 */
359const executeTool = async (toolHandler: () => Promise<CallToolResult>): Promise<CallToolResult> => {
360 try {
361 return await toolHandler();
362 } catch (error) {
363 let mcpError: McpError;
364
365 if (error instanceof McpError) {
366 mcpError = error;
367 } else if (error instanceof Error) {
368 mcpError = new McpError(McpErrorCode.InternalError, error.message);
369 } else {
370 mcpError = new McpError(McpErrorCode.InternalError, String(error));
371 }
372
373 return wrapMcpError(mcpError);
374 }
375};
376
377/**
378 * Create auth-enabled tool wrapper
379 */
380const createAuthTool = (toolFn: (authInfo?: AuthInfo) => Promise<CallToolResult>) =>
381 async (args: any, extra: any) => {
382 return await executeTool(() => toolFn(extra.authInfo));
383 };
384
385// ================================
386// MCP Server Registration
387// ================================
388
389/**
390 * Register all tools with the MCP server
391 */
392const registerTools = (server: McpServer) => {
393 // Quickstart tool - provides getting started guidance
394 server.tool(
395 "quickstart",
396 "Get getting started guidance and system overview",
397 {
398 // No parameters needed for basic quickstart
399 },
400 createAuthTool(quickstartTool)
401 );
402
403 // Health check tool - verify system status
404 server.tool(
405 "health_check",
406 "Check server connectivity and authentication status",
407 {
408 // No parameters needed for health check
409 },
410 createAuthTool(healthCheckTool)
411 );
412
413 // Add more tools here following the same pattern:
414 // server.tool(
415 // "tool_name",
416 // "Tool description",
417 // {
418 // param: z.string().describe("Parameter description")
419 // },
420 // async ({ param }, extra) => {
421 // return await executeTool(() => yourToolFunction(param, extra.authInfo));
422 // }
423 // );
424};
425
426/**
427 * Register all resources with the MCP server
428 * Resources appear in Claude Desktop's '+' prompt button
429 */
430const registerResources = (server: McpServer) => {
431 server.resource(
432 "Getting Started Guide",
433 "app://quickstart",
434 {
435 description: "Interactive getting started guide with personalized examples and instructions",
436 } as ResourceMetadata,
437 (uri: URL, extra: RequestHandlerExtra<ServerRequest, ServerNotification>) =>
438 readResource(uri.toString(), extra.authInfo)
439 );
440
441 // Add more resources here:
442 // server.resource(
443 // "Resource Name",
444 // "app://resource-uri",
445 // { description: "Resource description" } as ResourceMetadata,
446 // (uri: URL, extra) => readResource(uri.toString(), extra.authInfo)
447 // );
448};
449
450// ================================
451// Authentication Implementation
452// ================================
453
454/**
455 * Verify bearer token and create auth context
456 * This integrates with your application's authentication system
457 */
458const verifyToken = async (
459 req: Request,
460 bearerToken?: string
461): Promise<AuthInfo | undefined> => {
462 if (!bearerToken) {
463 return undefined;
464 }
465
466 // Validate token with your identity service
467 const authTicket = await identityService.validateToken(bearerToken);
468
469 if (!authTicket || !authTicket.isAuthenticated) {
470 return undefined;
471 }
472
473 // Create extra context for downstream operations
474 const extra: AuthExtra = {
475 authTicket,
476 request: req as NextRequest,
477 timestamp: new Date().toISOString(),
478 };
479
480 return {
481 token: bearerToken,
482 scopes: [".default"], // Your application's scope system
483 clientId: "mcp-client",
484 extra: extra as any
485 };
486};
487
488// ================================
489// MCP Handler Configuration
490// ================================
491
492/**
493 * Create the MCP handler with server configuration
494 */
495const handler = createMcpHandler(
496 (server: McpServer) => {
497 registerTools(server);
498 registerResources(server);
499 },
500 {
501 // Instructions shown to AI assistants
502 instructions: 'This MCP server provides access to application functionality. Start with the quickstart tool to understand available operations.',
503
504 // Server capabilities
505 capabilities: {
506 auth: {
507 type: "bearer",
508 required: true,
509 },
510 tools: {
511 listChanged: false
512 },
513 resources: {
514 subscribe: false,
515 listChanged: false
516 },
517 logging: {},
518 experimental: {}
519 },
520
521 // Server identification
522 serverInfo: {
523 name: 'Generic Remote MCP Server',
524 version: '1.0.0',
525 },
526 },
527 {
528 // HTTP endpoint configuration
529 streamableHttpEndpoint: '/api/mcp',
530 basePath: '/api'
531 }
532);
533
534/**
535 * Create authenticated handler with token verification
536 */
537const authHandler = withMcpAuth(handler, verifyToken, {
538 required: true,
539 requiredScopes: [".default"],
540 resourceMetadataPath: "/.well-known/oauth-protected-resource",
541});
542
543// ================================
544// Next.js API Route Exports
545// ================================
546
547export { authHandler as GET, authHandler as POST };
548
549/**
550 * Usage Instructions:
551 *
552 * For comprehensive implementation guidance, see:
553 * utaba/main/guidance/development/nextjs-remote-mcp-server.md
554 *
555 * 1. Deploy this route at /src/app/api/mcp/route.ts in your Next.js app
556 *
557 * 2. Configure authentication by implementing your own IdentityService
558 *
559 * 3. Add your business logic by creating commands/services
560 *
561 * 4. Register additional tools and resources as needed
562 *
563 * 5. Test with: curl -H "auhtorization: Bearer userId:demo-api-key" http://localhost:3000/api/mcp
564 *
565 * 6. Connect MCP clients using: http://localhost:3000/api/mcp
566 *
567 * Key Integration Points:
568 * - Replace MockIdentityService with your auth system
569 * - Replace MockSecurityService with your rate limiting/security
570 * - Add your business commands and services
571 * - Customize the namespace and permission model
572 * - Add monitoring and logging as needed
573 */
Metadata
- Path
- utaba/main/implementations/remote-mcp-server/route.ts
- Namespace
- utaba/main/implementations/remote-mcp-server
- Author
- utaba
- Category
- implementations
- Technology
- typescript
- Version
- 1.0.0
- MIME Type
- application/typescript
- Published
- 29-Aug-2025
- Last Updated
- 29-Aug-2025