MCP Service
This content is for the 0.6.2 version. Switch to the latest version for up-to-date documentation.
MCP Service
Overview
MCP (Model Context Protocol) services provide standardized interfaces between AI models and application functionality. Each MCP service exposes tools, prompts, and resources that AI models can access through the Model Context Protocol specification.
MCP services 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 seamless integration with AI models.
Key benefits include standardized AI tool interfaces, automatic parameter validation, resource template support, and direct integration with the Model Context Protocol ecosystem.
Prerequisites
- Active Raindrop project with manifest configuration
- Understanding of TypeScript interfaces and async callback patterns
- Familiarity with AI tool concepts and the Model Context Protocol
- Knowledge of Zod schema validation for parameter typing
Creating/Getting Started
Define MCP services in your manifest to expose AI-accessible capabilities:
application "ai-tools" { mcp_service "tools" { visibility = "protected" authorization_server = "https://authkit.liquidmetal.run" }}
The service generates typed environment bindings for accessing the MCP server:
interface Env { TOOLS: RaindropMcpServer;}
Accessing/Basic Usage
Use the MCP service binding to register tools and handle AI interactions:
import { ActorState } from "@liquidmetal-ai/raindrop-framework";import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { z } from "zod";
export default async (server: McpServer, env: Env, state: ActorState) => { // Register a simple tool without parameters server.tool("current-time", "Get the current timestamp", async (extra) => { return { content: [{ type: "text", text: `Current time: ${new Date().toISOString()}` }] }; });
// Register tool with parameter validation server.tool("calculate", "Perform mathematical calculations", { num1: z.number().describe("First number"), num2: z.number().describe("Second number"), operation: z.enum(["add", "subtract", "multiply", "divide"]).describe("Mathematical operation") }, async (args, extra) => { let result: number; switch (args.operation) { case "add": result = args.num1 + args.num2; break; case "subtract": result = args.num1 - args.num2; break; case "multiply": result = args.num1 * args.num2; break; case "divide": result = args.num1 / args.num2; break; }
return { content: [{ type: "text", text: `${args.num1} ${args.operation} ${args.num2} = ${result}` }] }; });};
Core Concepts
MCP services implement the Model Context Protocol to provide standardized interfaces for AI model interactions through three main components:
Tool Registration: Tools are functions that AI models can call with validated parameters and receive structured responses. Each tool defines its input schema and returns formatted content.
Resource Management: Resources represent accessible data or content that models can read through URI-based addressing. Resources support both static URIs and dynamic templates.
Prompt Templates: Reusable prompt templates with parameter substitution for consistent AI interactions across different contexts.
Type Safety: Full TypeScript integration with Zod schema validation ensures type safety for parameters, responses, and all MCP interactions.
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 methods 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;
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 methods 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;
registerPrompt<Args extends PromptArgsRawShape>( name: string, config: { title?: string; description?: string; argsSchema?: Args; }, cb: PromptCallback<Args> ): RegisteredPrompt;
// Resource registration methods 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;
// Notification methods for dynamic updates sendToolListChanged(): void; sendResourceListChanged(): void; sendPromptListChanged(): void;
// Access to underlying MCP Server readonly server: Server;}
Callback Type Signatures
Type-safe callback signatures provide 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>;
type ReadResourceCallback = (uri: URL, extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => ReadResourceResult | Promise<ReadResourceResult>;
type ReadResourceTemplateCallback = (uri: URL, variables: Variables, extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => ReadResourceResult | Promise<ReadResourceResult>;
Tool Registration
Register AI-accessible tools with parameter validation and response formatting through multiple method signatures.
tool(name, callback)
Register a basic tool without parameters:
const timeTool = server.tool("current-time", async (extra) => { const now = new Date(); return { content: [{ type: "text", text: `Current time: ${now.toISOString()}` }] };});
// Tools can be managed after registrationtimeTool.disable();timeTool.enable();
tool(name, description, callback)
Register a tool with description:
server.tool( "generate-uuid", "Generate a new UUID v4 identifier", async (extra) => { const uuid = crypto.randomUUID(); return { content: [{ type: "text", text: uuid }] }; });
tool(name, paramsSchema, callback)
Register a tool with parameter validation:
server.tool("send-email", { recipient: z.string().email().describe("Email recipient address"), subject: z.string().describe("Email subject line"), body: z.string().describe("Email body content"), priority: z.enum(["high", "normal", "low"]).optional().describe("Message priority")}, async (args, extra) => { 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}` }] };});
registerTool - Advanced Registration
Use registerTool
for full configuration control:
const apiTool = server.registerTool("fetch-data", { title: "Data Fetcher", description: "Fetch data from external API", inputSchema: { url: z.string().url().describe("API endpoint URL"), method: z.enum(["GET", "POST"]).optional().describe("HTTP method") }, outputSchema: { data: z.any(), status: z.number(), headers: z.record(z.string()) }, annotations: { audience: ["user"], dangerLevel: "low" }}, async (args, extra) => { const response = await fetch(args.url, { method: args.method || "GET" });
const data = await response.json();
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }], metadata: { status: response.status, headers: Object.fromEntries(response.headers.entries()) } };});
// Update tool configurationapiTool.update({ description: "Fetch and return data from any REST API endpoint", enabled: true});
// Remove toolapiTool.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:
server.resource("api-status", "status://health", async (uri, extra) => { const healthCheck = await checkSystemHealth();
return { contents: [{ uri: uri.toString(), mimeType: "application/json", text: JSON.stringify(healthCheck, null, 2) }] };});
resource(name, template, callback)
Register a resource template for dynamic URIs:
server.resource("user-profile", { uriTemplate: "user://{userId}/profile", name: "User Profile", description: "Access user profile information by ID"}, 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 and management:
server.resource("api-docs", "docs://api/v1", { description: "API documentation and schema information", mimeType: "application/json", tags: ["api", "documentation", "schema"]}, async (uri, extra) => { const apiDocs = await getApiDocumentation();
return { contents: [{ uri: uri.toString(), mimeType: "application/json", text: JSON.stringify(apiDocs, null, 2), metadata: { version: apiDocs.version, lastUpdated: apiDocs.updatedAt } }] };});
Prompt Templates
Create reusable prompt templates with parameter substitution for consistent AI interactions.
prompt(name, callback)
Register a basic prompt without parameters:
server.prompt("system-info", async (extra) => { const systemInfo = await getSystemInfo();
return { messages: [{ role: "system", content: { type: "text", text: `System Information:- Environment: ${systemInfo.environment}- Region: ${systemInfo.region}- Version: ${systemInfo.version}
Use this information to help with system-related questions.` } }] };});
prompt(name, argsSchema, callback)
Register a parameterized prompt template:
server.prompt("code-review", { language: z.string().describe("Programming language"), code: z.string().describe("Code to review"), focus: z.enum(["performance", "security", "readability", "bugs"]).optional().describe("Review focus area")}, 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 } }] };});
Code Examples
Complete MCP service implementations demonstrating common patterns and real-world use cases.
Smart Memory MCP Service
Based on the monet package implementation pattern:
// raindrop.manifestapplication "smart-tools" { mcp_service "smart-tools" { visibility = "protected" authorization_server = "https://authkit.liquidmetal.run" domain { cname = "smart-tools" } }
smartmemory "app_memory" {}}
import { ActorState } from "@liquidmetal-ai/raindrop-framework";import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { z } from "zod";
// Register smart memory toolsfunction registerSmartMemoryTools(server: McpServer, env: Env, state: ActorState) { // Store information in smart memory server.tool("store-memory", { content: z.string().describe("Content to store"), tags: z.array(z.string()).optional().describe("Tags for categorization") }, async (args, extra) => { const memory = env.APP_MEMORY; const result = await memory.store(args.content, { tags: args.tags || [], timestamp: new Date().toISOString() });
return { content: [{ type: "text", text: `Stored memory with ID: ${result.id}` }] }; });
// Search smart memory server.tool("search-memory", { query: z.string().describe("Search query"), limit: z.number().optional().describe("Maximum results to return") }, async (args, extra) => { const memory = env.APP_MEMORY; const results = await memory.search(args.query, { limit: args.limit || 10 });
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] }; });}
// Register documentation resourcesasync function registerDocumentationResources(server: McpServer, env: Env, state: ActorState) { server.resource("documentation", { uriTemplate: "docs://{docId}", name: "Documentation", description: "Access documentation by document ID" }, async (uri, variables, extra) => { const docId = variables.docId; const doc = await getDocumentation(docId);
return { contents: [{ uri: uri.toString(), mimeType: "text/markdown", text: doc.content }] }; });}
export default async (server: McpServer, env: Env, state: ActorState) => { registerSmartMemoryTools(server, env, state); await registerDocumentationResources(server, env, state);};
API Integration MCP Service
Complete API operations with error handling and response formatting:
import { z } from "zod";
export default async (server: McpServer, env: Env, state: ActorState) => { // Make HTTP requests server.tool("http-request", { url: z.string().url().describe("Request URL"), method: z.enum(["GET", "POST", "PUT", "DELETE"]).optional().describe("HTTP method"), headers: z.record(z.string()).optional().describe("Request headers"), body: z.string().optional().describe("Request body") }, async (args, extra) => { try { const response = await fetch(args.url, { method: args.method || "GET", headers: args.headers, body: args.body });
const responseText = await response.text();
return { content: [{ type: "text", text: responseText }], metadata: { status: response.status, statusText: response.statusText, headers: Object.fromEntries(response.headers.entries()) } }; } catch (error) { return { content: [{ type: "text", text: `Request failed: ${error.message}` }], isError: true }; } });
// Parse JSON data server.tool("parse-json", { json: z.string().describe("JSON string to parse"), path: z.string().optional().describe("JSONPath expression to extract specific data") }, async (args, extra) => { try { const data = JSON.parse(args.json);
if (args.path) { // Simple JSONPath implementation for demonstration const result = extractJsonPath(data, args.path); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; }
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } catch (error) { return { content: [{ type: "text", text: `JSON parsing error: ${error.message}` }], isError: true }; } });
// Format data as table server.tool("format-table", { data: z.array(z.record(z.any())).describe("Array of objects to format as table"), columns: z.array(z.string()).optional().describe("Columns to include") }, async (args, extra) => { if (args.data.length === 0) { return { content: [{ type: "text", text: "No data to format" }] }; }
const columns = args.columns || Object.keys(args.data[0]); const table = formatAsTable(args.data, columns);
return { content: [{ type: "text", text: table }] }; });
// API status resource server.resource("api-status", "status://api", async (uri, extra) => { const status = await checkApiStatus();
return { contents: [{ uri: uri.toString(), mimeType: "application/json", text: JSON.stringify(status, null, 2) }] }; });};
function extractJsonPath(data: any, path: string): any { // Simplified JSONPath implementation const parts = path.split('.'); let result = data;
for (const part of parts) { if (result && typeof result === 'object' && part in result) { result = result[part]; } else { return undefined; } }
return result;}
function formatAsTable(data: any[], columns: string[]): string { const rows = data.map(row => columns.map(col => String(row[col] || '')).join('\t') );
const header = columns.join('\t'); return [header, ...rows].join('\n');}
async function checkApiStatus(): Promise<any> { return { status: "healthy", timestamp: new Date().toISOString(), services: { database: "connected", cache: "connected", external_api: "connected" } };}
raindrop.manifest
Configure MCP services in your manifest to create AI-accessible tool providers with secure authorization:
application "ai-platform" { mcp_service "api-tools" { visibility = "protected" authorization_server = "https://authkit.liquidmetal.run" domain { cname = "api-tools" } }
mcp_service "data-tools" { visibility = "protected" authorization_server = "https://authkit.liquidmetal.run" domain { cname = "data-tools" } }
smartmemory "app_memory" {}
env "API_KEY" { secret = true }}