MCP Protocol Reference
This document describes how Orchestre implements the Model Context Protocol (MCP) for communication with Claude Code.
Overview
The Model Context Protocol (MCP) enables Claude Code to communicate with external tools and services. Orchestre implements MCP to provide its orchestration capabilities.
Protocol Flow
Message Format
Request Structure
typescript
interface MCPRequest {
jsonrpc: "2.0";
id: string | number;
method: string;
params?: any;
}Response Structure
typescript
interface MCPResponse {
jsonrpc: "2.0";
id: string | number;
result?: {
content: Array<{
type: "text";
text: string;
}>;
isError?: boolean;
};
error?: {
code: number;
message: string;
data?: any;
};
}Tool Registration
Tool Definition
typescript
interface ToolDefinition {
name: string;
description: string;
inputSchema: {
type: "object";
properties: Record<string, any>;
required?: string[];
};
}Example Tool Registration
typescript
server.addTool({
name: "initialize_project",
description: "Initialize a new project with a template",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Project name"
},
template: {
type: "string",
description: "Template to use",
enum: ["makerkit-nextjs", "cloudflare-hono", "react-native-expo"]
}
},
required: ["name", "template"]
}
});Tool Implementation
Basic Pattern
typescript
async function handleTool(params: any): Promise<ToolResponse> {
try {
// Validate input
const validated = schema.parse(params);
// Process request
const result = await processLogic(validated);
// Return formatted response
return {
content: [{
type: "text",
text: JSON.stringify(result, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: JSON.stringify({
error: error.message,
details: error.stack
}, null, 2)
}],
isError: true
};
}
}Error Handling
typescript
// Validation error
if (!params.requiredField) {
return {
content: [{
type: "text",
text: JSON.stringify({
error: "Missing required field: requiredField",
code: "VALIDATION_ERROR"
}, null, 2)
}],
isError: true
};
}
// API error
try {
const result = await externalAPI.call();
} catch (apiError) {
return {
content: [{
type: "text",
text: JSON.stringify({
error: "External API failed",
details: apiError.message,
code: "API_ERROR"
}, null, 2)
}],
isError: true
};
}Available Methods
Tool Discovery
Method: tools/list
Response:
json
{
"tools": [
{
"name": "initialize_project",
"description": "Initialize a new project with a template",
"inputSchema": { ... }
},
// ... more tools
]
}Tool Invocation
Method: tools/call
Request:
json
{
"name": "analyze_project",
"arguments": {
"requirements": "Build a SaaS application"
}
}Response:
json
{
"content": [
{
"type": "text",
"text": "{\n \"analysis\": { ... }\n}"
}
]
}Orchestre-Specific Extensions
Multi-Part Responses
For large responses, Orchestre may split content:
typescript
return {
content: [
{
type: "text",
text: "## Part 1: Analysis\n..."
},
{
type: "text",
text: "## Part 2: Implementation\n..."
}
]
};Progress Indicators
For long-running operations:
typescript
// Not directly supported by MCP
// Orchestre returns status in response
return {
content: [{
type: "text",
text: JSON.stringify({
status: "in_progress",
message: "Analyzing project structure...",
progress: 0.3
}, null, 2)
}]
};Configuration
Server Initialization
typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new Server(
{
name: "orchestre",
version: "3.0.0",
},
{
capabilities: {
tools: {},
},
}
);
const transport = new StdioServerTransport();
await server.connect(transport);Environment Variables
typescript
// Access in tool implementations
const apiKey = process.env.GEMINI_API_KEY;
const debugMode = process.env.ORCHESTRE_DEBUG === "true";Best Practices
1. Consistent Response Format
Always return structured JSON:
typescript
return {
content: [{
type: "text",
text: JSON.stringify({
success: true,
data: result,
metadata: {
timestamp: new Date().toISOString(),
version: "1.0.0"
}
}, null, 2)
}]
};2. Comprehensive Error Information
Include helpful error details:
typescript
catch (error) {
return {
content: [{
type: "text",
text: JSON.stringify({
error: error.message,
code: error.code || "UNKNOWN_ERROR",
suggestion: getSuggestionForError(error),
documentation: getDocumentationLink(error)
}, null, 2)
}],
isError: true
};
}3. Input Validation
Use schemas for validation:
typescript
import { z } from "zod";
const inputSchema = z.object({
name: z.string().min(1).max(100),
options: z.object({
feature: z.boolean().optional(),
template: z.enum(["basic", "advanced"]).optional()
}).optional()
});
// In tool handler
const validated = inputSchema.parse(params);4. Timeout Handling
Implement timeouts for external calls:
typescript
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error("Operation timed out")), 30000)
);
try {
const result = await Promise.race([
externalOperation(),
timeout
]);
} catch (error) {
// Handle timeout or operation error
}Debugging
Enable Debug Logging
typescript
function debugLog(message: string, data?: any) {
if (process.env.ORCHESTRE_DEBUG) {
console.error(`[Orchestre Debug] ${message}`,
data ? JSON.stringify(data, null, 2) : "");
}
}
// Use in tools
debugLog("Tool invoked", { name: toolName, params });Request/Response Logging
typescript
server.use((req, res, next) => {
debugLog("MCP Request", req);
const originalSend = res.send;
res.send = function(data) {
debugLog("MCP Response", data);
return originalSend.call(this, data);
};
next();
});Security Considerations
Input Sanitization
typescript
// Sanitize file paths
const safePath = path.normalize(userPath)
.replace(/^(\.\.(\/|\\|$))+/, '');
// Validate against directory traversal
if (!safePath.startsWith(projectRoot)) {
throw new Error("Invalid path");
}API Key Protection
typescript
// Never log API keys
const sanitizedEnv = {
...process.env,
GEMINI_API_KEY: process.env.GEMINI_API_KEY ? "[REDACTED]" : undefined,
OPENAI_API_KEY: process.env.OPENAI_API_KEY ? "[REDACTED]" : undefined
};Integration Examples
Creating a New Tool
typescript
// 1. Define the tool
const newTool: ToolDefinition = {
name: "custom_analysis",
description: "Perform custom analysis",
inputSchema: {
type: "object",
properties: {
target: { type: "string" },
depth: { type: "number", minimum: 1, maximum: 5 }
},
required: ["target"]
}
};
// 2. Implement handler
async function handleCustomAnalysis(params: any) {
const { target, depth = 3 } = params;
// Implementation
const result = await analyze(target, depth);
return {
content: [{
type: "text",
text: JSON.stringify(result, null, 2)
}]
};
}
// 3. Register with server
server.addTool(newTool, handleCustomAnalysis);Calling Other Tools
typescript
// Within a tool implementation
async function complexOperation(params: any) {
// First, analyze
const analysis = await handleAnalyzeProject({
requirements: params.description
});
// Then, generate plan
const plan = await handleGeneratePlan({
analysis: JSON.parse(analysis.content[0].text)
});
return plan;
}Testing
Unit Testing Tools
typescript
import { describe, it, expect } from "vitest";
describe("initialize_project tool", () => {
it("should create project with valid params", async () => {
const result = await handleInitializeProject({
name: "test-project",
template: "cloudflare-hono"
});
expect(result.isError).toBeFalsy();
expect(result.content[0].type).toBe("text");
const data = JSON.parse(result.content[0].text);
expect(data.success).toBe(true);
});
it("should handle missing template", async () => {
const result = await handleInitializeProject({
name: "test-project"
});
expect(result.isError).toBe(true);
});
});Integration Testing
typescript
// Test with actual MCP server
import { spawn } from "child_process";
const server = spawn("node", ["dist/server.js"]);
// Send MCP request
server.stdin.write(JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "tools/call",
params: {
name: "analyze_project",
arguments: { requirements: "test" }
}
}));
// Read response
server.stdout.on("data", (data) => {
const response = JSON.parse(data.toString());
// Validate response
});Performance Considerations
Response Size
Keep responses reasonable:
typescript
const MAX_RESPONSE_SIZE = 50000; // characters
if (result.length > MAX_RESPONSE_SIZE) {
// Summarize or paginate
return {
content: [{
type: "text",
text: JSON.stringify({
summary: summarize(result),
truncated: true,
size: result.length
}, null, 2)
}]
};
}Async Operations
Use streaming where possible:
typescript
// For large file operations
async function* processLargeFile(path: string) {
const stream = fs.createReadStream(path);
for await (const chunk of stream) {
yield processChunk(chunk);
}
}Versioning
Protocol Version
Orchestre implements MCP version 0.1.0
Compatibility
typescript
// Check client version
if (clientVersion < "0.1.0") {
return {
content: [{
type: "text",
text: JSON.stringify({
error: "Client version not supported",
minimum: "0.1.0"
}, null, 2)
}],
isError: true
};
}