MCP Server
MCP Server
Overview
MCP (Model Context Protocol) servers provide standardized interfaces for AI tools and resources within Raindrop applications. Each MCP server exposes tools, prompts, and resources that AI models can access through the standardized protocol.
MCP servers excel at creating reusable AI capabilities, exposing system functionality to language models, and building composable tool ecosystems. They provide type-safe interfaces with automatic validation and documentation generation.
Key benefits include standardized AI tool interfaces, automatic type validation, resource template support, and seamless integration with AI models.
Prerequisites
- Active Raindrop project with manifest configuration
- Understanding of TypeScript interfaces and callback patterns
- Familiarity with AI tool concepts and prompt engineering
- Knowledge of the Model Context Protocol specification
Creating/Getting Started
Define MCP servers in your manifest to expose AI-accessible capabilities:
application "ai-tools" { mcp "file-manager" { # Optional configuration }
service "api" { domain = "api.example.com" }}
Generate the MCP server implementation:
raindrop build generate
This creates typed environment bindings and base server structure:
interface Env { FILE_MANAGER: RaindropMcpServer;}
Accessing/Basic Usage
Use the MCP server binding to register tools and handle AI interactions:
export default class extends Service<Env> { async fetch(request: Request): Promise<Response> { const mcpServer = this.env.FILE_MANAGER;
// Register a simple tool mcpServer.tool("get-weather", "Get current weather for a location", async (args, extra) => { const weather = await this.getWeather(args.location); return { content: [{ type: "text", text: `Weather in ${args.location}: ${weather.description}, ${weather.temperature}°F` }] }; });
// Register tool with input validation mcpServer.tool("calculate", "Perform mathematical calculations", { num1: z.number(), num2: z.number(), operation: z.enum(["add", "subtract", "multiply", "divide"]) }, async (args, extra) => { const result = this.calculate(args.num1, args.num2, args.operation); return { content: [{ type: "text", text: `Result: ${result}` }] }; });
return new Response("MCP tools registered"); }}
Connect the server to transport layer:
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
// Connect server to transportconst transport = new StdioServerTransport();await mcpServer.connect(transport);
Core Concepts
MCP servers implement the Model Context Protocol to provide standardized interfaces for AI model interactions.
Tool Registration: Tools are functions that AI models can call with validated parameters and receive structured responses.
Resource Management: Resources represent accessible data or content that models can read through URI-based addressing.
Prompt Templates: Reusable prompt templates with parameter substitution for consistent AI interactions.
Type Safety: Full TypeScript integration with Zod schema validation for parameters and responses.
MCP Server Interface
The RaindropMcpServer
interface provides comprehensive functionality for registering tools, prompts, and resources:
interface RaindropMcpServer { // Connection management connect(transport: Transport): Promise<void>; close(): Promise<void>; isConnected(): boolean;
// Tool registration - multiple overload signatures tool(name: string, cb: ToolCallback): RegisteredTool; tool(name: string, description: string, cb: ToolCallback): RegisteredTool; tool<Args extends ZodRawShape>(name: string, paramsSchemaOrAnnotations: Args | ToolAnnotations, cb: ToolCallback<Args>): RegisteredTool; tool<Args extends ZodRawShape>(name: string, description: string, paramsSchemaOrAnnotations: Args | ToolAnnotations, cb: ToolCallback<Args>): RegisteredTool; tool<Args extends ZodRawShape>(name: string, paramsSchema: Args, annotations: ToolAnnotations, cb: ToolCallback<Args>): RegisteredTool; tool<Args extends ZodRawShape>(name: string, description: string, paramsSchema: Args, annotations: ToolAnnotations, cb: ToolCallback<Args>): RegisteredTool;
// Advanced tool registration with full configuration registerTool<InputArgs extends ZodRawShape, OutputArgs extends ZodRawShape>( name: string, config: { title?: string; description?: string; inputSchema?: InputArgs; outputSchema?: OutputArgs; annotations?: ToolAnnotations; }, cb: ToolCallback<InputArgs> ): RegisteredTool;
// Prompt registration - multiple overload signatures prompt(name: string, cb: PromptCallback): RegisteredPrompt; prompt(name: string, description: string, cb: PromptCallback): RegisteredPrompt; prompt<Args extends PromptArgsRawShape>(name: string, argsSchema: Args, cb: PromptCallback<Args>): RegisteredPrompt; prompt<Args extends PromptArgsRawShape>(name: string, description: string, argsSchema: Args, cb: PromptCallback<Args>): RegisteredPrompt;
// Advanced prompt registration with full configuration registerPrompt<Args extends PromptArgsRawShape>( name: string, config: { title?: string; description?: string; argsSchema?: Args; }, cb: PromptCallback<Args> ): RegisteredPrompt;
// Resource registration - multiple overload signatures resource(name: string, uri: string, readCallback: ReadResourceCallback): RegisteredResource; resource(name: string, uri: string, metadata: ResourceMetadata, readCallback: ReadResourceCallback): RegisteredResource; resource(name: string, template: ResourceTemplate, readCallback: ReadResourceTemplateCallback): RegisteredResourceTemplate; resource(name: string, template: ResourceTemplate, metadata: ResourceMetadata, readCallback: ReadResourceTemplateCallback): RegisteredResourceTemplate;
// Advanced resource registration with full configuration registerResource( name: string, uriOrTemplate: string | ResourceTemplate, config: ResourceMetadata, readCallback: ReadResourceCallback, ): RegisteredResource | RegisteredResourceTemplate;
// Notification methods for dynamic updates sendToolListChanged(): void; sendResourceListChanged(): void; sendPromptListChanged(): void;
// Access to underlying MCP Server for advanced usage readonly server: Server;}
Callback Types
Type-safe callback signatures for different MCP operations with complete request context:
type ToolCallback<Args extends undefined | ZodRawShape = undefined> = Args extends ZodRawShape ? (args: z.objectOutputType<Args, ZodTypeAny>, extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => CallToolResult | Promise<CallToolResult> : (extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => CallToolResult | Promise<CallToolResult>;
type PromptCallback<Args extends undefined | PromptArgsRawShape = undefined> = Args extends PromptArgsRawShape ? (args: z.objectOutputType<Args, ZodTypeAny>, extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => GetPromptResult | Promise<GetPromptResult> : (extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => GetPromptResult | Promise<GetPromptResult>;
// Resource callbacks with full context informationtype ReadResourceCallback = (uri: URL, extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => ReadResourceResult | Promise<ReadResourceResult>;
type ReadResourceTemplateCallback = (uri: URL, variables: Variables, extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => ReadResourceResult | Promise<ReadResourceResult>;
// Additional helper types for resource managementtype ResourceMetadata = Omit<Resource, "uri" | "name">;type PromptArgsRawShape = { [k: string]: ZodType<string, ZodTypeDef, string> | ZodOptional<ZodType<string, ZodTypeDef, string>>;};
Tool Registration
Register AI-accessible tools with parameter validation and response formatting.
tool(name, callback)
Register a basic tool without parameters:
const weatherTool = mcpServer.tool("current-time", async (extra) => { const now = new Date(); return { content: [{ type: "text", text: `Current time: ${now.toISOString()}` }] };});
// Tool can be enabled/disabledweatherTool.disable();weatherTool.enable();
tool(name, description, callback)
Register a tool with description:
mcpServer.tool( "generate-uuid", "Generate a new UUID v4", async (extra) => { const uuid = crypto.randomUUID(); return { content: [{ type: "text", text: uuid }] }; });
tool(name, paramsSchema, callback)
Register a tool with parameter validation:
import { z } from 'zod';
mcpServer.tool("send-email", { recipient: z.string().email(), subject: z.string(), body: z.string(), priority: z.enum(["high", "normal", "low"]).optional()}, async (args, extra) => { // args is fully typed based on schema const emailResult = await sendEmail({ to: args.recipient, subject: args.subject, body: args.body, priority: args.priority || "normal" });
return { content: [{ type: "text", text: `Email sent successfully. Message ID: ${emailResult.messageId}` }] };});
Advanced Tool Registration
Use the registerTool
method for full configuration:
const fileTool = mcpServer.registerTool("read-file", { title: "File Reader", description: "Read contents of a text file", inputSchema: { path: z.string().describe("File path to read"), encoding: z.string().optional().describe("File encoding (default: utf-8)") }, outputSchema: { content: z.string(), size: z.number(), lastModified: z.date() }, annotations: { audience: ["user"], dangerLevel: "low" }}, async (args, extra) => { const fileContent = await readFile(args.path, args.encoding || 'utf-8'); const stats = await getFileStats(args.path);
return { content: [{ type: "text", text: fileContent }], metadata: { size: stats.size, lastModified: stats.mtime } };});
// Update tool configurationfileTool.update({ description: "Read and return the contents of any text file", enabled: true});
// Remove toolfileTool.remove();
Resource Management
Expose data and content through URI-based resource interfaces that AI models can access.
resource(name, uri, callback)
Register a static resource with fixed URI:
mcpServer.resource("config", "file:///app/config.json", async (uri, extra) => { const configPath = uri.pathname; const config = await readFile(configPath, 'utf-8');
return { contents: [{ uri: uri.toString(), mimeType: "application/json", text: config }] };});
resource(name, template, callback)
Register a resource template for dynamic URIs:
mcpServer.resource("user-profile", { uriTemplate: "user://{userId}/profile", name: "User Profile", description: "Get user profile information"}, async (uri, variables, extra) => { const userId = variables.userId; const userProfile = await getUserProfile(userId);
return { contents: [{ uri: uri.toString(), mimeType: "application/json", text: JSON.stringify(userProfile, null, 2) }] };});
Resource with Metadata
Include additional metadata for resource discovery:
mcpServer.resource("database-schema", "schema://main", { description: "Database schema information", mimeType: "application/json", tags: ["database", "schema", "metadata"]}, async (uri, extra) => { const schema = await getDatabaseSchema();
return { contents: [{ uri: uri.toString(), mimeType: "application/json", text: JSON.stringify(schema), metadata: { version: schema.version, lastUpdated: schema.updatedAt } }] };});
Prompt Templates
Create reusable prompt templates with parameter substitution for consistent AI interactions.
prompt(name, callback)
Register a basic prompt without parameters:
mcpServer.prompt("system-info", async (extra) => { const systemInfo = await getSystemInfo();
return { messages: [{ role: "system", content: { type: "text", text: `System Information:- OS: ${systemInfo.os}- Memory: ${systemInfo.memory}- CPU: ${systemInfo.cpu}- Uptime: ${systemInfo.uptime}
Use this information to help with system-related questions.` } }] };});
prompt(name, argsSchema, callback)
Register a parameterized prompt template:
mcpServer.prompt("code-review", { language: z.string().describe("Programming language"), code: z.string().describe("Code to review"), focus: z.enum(["performance", "security", "readability", "bugs"]).optional()}, async (args, extra) => { const focusArea = args.focus || "general";
return { messages: [{ role: "system", content: { type: "text", text: `You are a senior software engineer performing a code review.
Language: ${args.language}Focus Area: ${focusArea}
Please review the following code and provide constructive feedback:` } }, { role: "user", content: { type: "text", text: args.code } }] };});
Connection Management
Handle MCP server lifecycle and transport connections.
connect(transport)
Connect server to a transport layer:
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
// Connect to stdio transportconst stdioTransport = new StdioServerTransport();await mcpServer.connect(stdioTransport);
// Connect to SSE transportconst sseTransport = new SSEServerTransport("/mcp", response);await mcpServer.connect(sseTransport);
Connection Status
Check and manage connection status:
if (mcpServer.isConnected()) { console.log("MCP server is connected");
// Send notifications about changes mcpServer.sendToolListChanged(); mcpServer.sendResourceListChanged(); mcpServer.sendPromptListChanged();}
// Gracefully close connectionawait mcpServer.close();
Code Examples
Complete MCP server implementations demonstrating common patterns and use cases.
File Management MCP Server
// manifest.hclapplication "file-tools" { mcp "file-manager" {}
service "mcp-service" { domain = "files.example.com" }}
import { z } from 'zod';import fs from 'fs/promises';import path from 'path';
export default class extends Service<Env> { async fetch(request: Request): Promise<Response> { const mcpServer = this.env.FILE_MANAGER;
// Register file operations this.registerFileTools(mcpServer); this.registerFileResources(mcpServer); this.registerFilePrompts(mcpServer);
return new Response("File management MCP server configured"); }
private registerFileTools(server: RaindropMcpServer): void { // List directory contents server.tool("list-directory", { path: z.string().describe("Directory path to list"), showHidden: z.boolean().optional().describe("Include hidden files") }, async (args, extra) => { const files = await fs.readdir(args.path, { withFileTypes: true }); const fileList = files .filter(file => args.showHidden || !file.name.startsWith('.')) .map(file => ({ name: file.name, type: file.isDirectory() ? 'directory' : 'file', path: path.join(args.path, file.name) }));
return { content: [{ type: "text", text: JSON.stringify(fileList, null, 2) }] }; });
// Read file contents server.tool("read-file", { path: z.string().describe("File path to read"), encoding: z.string().optional().default("utf-8").describe("File encoding") }, async (args, extra) => { try { const content = await fs.readFile(args.path, args.encoding as BufferEncoding); const stats = await fs.stat(args.path);
return { content: [{ type: "text", text: content.toString() }], metadata: { size: stats.size, lastModified: stats.mtime.toISOString() } }; } catch (error) { return { content: [{ type: "text", text: `Error reading file: ${error.message}` }], isError: true }; } });
// Write file contents server.tool("write-file", { path: z.string().describe("File path to write"), content: z.string().describe("Content to write"), createDirs: z.boolean().optional().describe("Create parent directories if needed") }, async (args, extra) => { try { if (args.createDirs) { await fs.mkdir(path.dirname(args.path), { recursive: true }); }
await fs.writeFile(args.path, args.content); const stats = await fs.stat(args.path);
return { content: [{ type: "text", text: `File written successfully: ${args.path}` }], metadata: { size: stats.size, created: stats.birthtime.toISOString() } }; } catch (error) { return { content: [{ type: "text", text: `Error writing file: ${error.message}` }], isError: true }; } });
// Search files server.tool("search-files", { directory: z.string().describe("Directory to search in"), pattern: z.string().describe("Search pattern (glob)"), content: z.string().optional().describe("Search within file contents") }, async (args, extra) => { const results = await this.searchFiles(args.directory, args.pattern, args.content);
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] }; }); }
private registerFileResources(server: RaindropMcpServer): void { // Dynamic file resource server.resource("file", { uriTemplate: "file://{path}", name: "File Resource", description: "Access file contents by path" }, async (uri, variables, extra) => { const filePath = variables.path;
try { const content = await fs.readFile(filePath, 'utf-8'); const stats = await fs.stat(filePath);
return { contents: [{ uri: uri.toString(), mimeType: this.getMimeType(filePath), text: content, metadata: { size: stats.size, lastModified: stats.mtime.toISOString() } }] }; } catch (error) { return { contents: [{ uri: uri.toString(), mimeType: "text/plain", text: `Error accessing file: ${error.message}` }] }; } });
// Directory listing resource server.resource("directory", { uriTemplate: "dir://{path}", name: "Directory Listing", description: "List directory contents" }, async (uri, variables, extra) => { const dirPath = variables.path;
try { const files = await fs.readdir(dirPath, { withFileTypes: true }); const listing = files.map(file => ({ name: file.name, type: file.isDirectory() ? 'directory' : 'file', path: path.join(dirPath, file.name) }));
return { contents: [{ uri: uri.toString(), mimeType: "application/json", text: JSON.stringify(listing, null, 2) }] }; } catch (error) { return { contents: [{ uri: uri.toString(), mimeType: "text/plain", text: `Error listing directory: ${error.message}` }] }; } }); }
private registerFilePrompts(server: RaindropMcpServer): void { // Code analysis prompt server.prompt("analyze-code", { filePath: z.string().describe("Path to code file"), analysisType: z.enum(["structure", "complexity", "security", "performance"]).describe("Type of analysis") }, async (args, extra) => { const code = await fs.readFile(args.filePath, 'utf-8'); const fileExtension = path.extname(args.filePath);
return { messages: [{ role: "system", content: { type: "text", text: `You are a code analysis expert. Please analyze the following ${fileExtension} code file with focus on ${args.analysisType}.
File: ${args.filePath}
Provide detailed insights about:${this.getAnalysisPrompt(args.analysisType)}` } }, { role: "user", content: { type: "text", text: code } }] }; }); }
private async searchFiles(directory: string, pattern: string, contentSearch?: string): Promise<any[]> { // Implementation would use glob pattern matching and content search // This is a simplified version const files = await fs.readdir(directory, { recursive: true, withFileTypes: true }); return files .filter(file => file.isFile()) .filter(file => file.name.includes(pattern)) .map(file => ({ name: file.name, path: path.join(directory, file.name) })); }
private getMimeType(filePath: string): string { const ext = path.extname(filePath).toLowerCase(); const mimeTypes: Record<string, string> = { '.txt': 'text/plain', '.json': 'application/json', '.js': 'text/javascript', '.ts': 'text/typescript', '.py': 'text/x-python', '.md': 'text/markdown' }; return mimeTypes[ext] || 'text/plain'; }
private getAnalysisPrompt(type: string): string { const prompts = { structure: "- Code organization and architecture\n- Function and class design\n- Module dependencies", complexity: "- Cyclomatic complexity\n- Code readability\n- Potential simplifications", security: "- Security vulnerabilities\n- Input validation\n- Authentication and authorization", performance: "- Performance bottlenecks\n- Memory usage\n- Optimization opportunities" }; return prompts[type] || "General code quality assessment"; }}
Database Query MCP Server
export default class extends Service<Env> { async fetch(request: Request): Promise<Response> { const mcpServer = this.env.DATABASE_TOOLS;
// Query execution tool mcpServer.tool("execute-query", { sql: z.string().describe("SQL query to execute"), params: z.array(z.any()).optional().describe("Query parameters"), readonly: z.boolean().optional().default(true).describe("Execute as read-only") }, async (args, extra) => { if (!args.readonly && !args.sql.match(/^(SELECT|WITH)/i)) { return { content: [{ type: "text", text: "Only SELECT queries allowed in read-only mode" }], isError: true }; }
try { const result = await this.env.DATABASE.prepare(args.sql).bind(...(args.params || [])).all();
return { content: [{ type: "text", text: JSON.stringify({ rows: result.results, rowCount: result.results.length, success: true }, null, 2) }] }; } catch (error) { return { content: [{ type: "text", text: `Database error: ${error.message}` }], isError: true }; } });
// Schema inspection tool mcpServer.tool("describe-table", { tableName: z.string().describe("Name of table to describe") }, async (args, extra) => { const schema = await this.env.DATABASE.prepare( "PRAGMA table_info(?)" ).bind(args.tableName).all();
return { content: [{ type: "text", text: JSON.stringify(schema.results, null, 2) }] }; });
return new Response("Database MCP server configured"); }}
## raindrop.manifest
Configure MCP servers in your manifest to create AI-accessible tool providers:
```hclapplication "ai-platform" { mcp "file-tools" { # MCP server for file system operations }
mcp "database-tools" { # MCP server for database queries and operations }
mcp "api-tools" { # MCP server for external API integrations }
service "mcp-host" { domain = "tools.example.com" # Service can access MCP servers via env.FILE_TOOLS, env.DATABASE_TOOLS, env.API_TOOLS }
actor "tool-manager" { # Actors can also register and manage MCP tools }}