MCP (Model Context Protocol) - Developer Guide
Overview
The MCP module in indusagi provides Model Context Protocol support, enabling applications to connect with external tools, APIs, and services through a standardized interface.
Architecture
indusagi/mcp
├── MCPClient (Single server connection)
├── MCPClientPool (Multiple server management)
├── MCPServer (Create custom MCP servers)
├── MCPToolFactory (Convert MCP tools to AgentTools)
├── Config (Server configuration management)
├── SchemaConverter (JSON Schema ↔ TypeBox conversion)
└── Types (Protocol definitions)
Core Components
1. MCPClient - Single Server Connection
Manages connection to a single MCP server.
Import:
import { MCPClient, type MCPClientOptions } from "indusagi/mcp";
Creating a client:
const client = new MCPClient({
name: "filesystem",
config: {
command: "npx",
args: ["-y", "@modelcontextprotocol/server-filesystem", "."],
},
timeout: 60000,
logger: (msg) => console.log(`[MCP] ${msg.level}: ${msg.message}`),
enableServerLogs: true,
});
await client.connect();
Listing tools:
const tools = await client.listTools();
console.log(`Available tools: ${tools.map(t => t.name).join(", ")}`);
Executing a tool:
const result = await client.callTool("read_file", {
path: "./README.md",
});
console.log(result);
Listing resources:
const resources = await client.listResources();
Reading resources:
const content = await client.readResource("file:///path/to/file");
Disconnecting:
await client.disconnect();
2. MCPClientPool - Multiple Server Management
Orchestrates connections to multiple MCP servers.
Import:
import { MCPClientPool, type MCPClientPoolOptions } from "indusagi/mcp";
Creating a pool:
const pool = new MCPClientPool({
servers: [
{
name: "filesystem",
config: {
command: "npx",
args: ["-y", "@modelcontextprotocol/server-filesystem", "."],
},
},
{
name: "github",
config: {
command: "npx",
args: ["-y", "@modelcontextprotocol/server-github"],
},
env: {
GITHUB_TOKEN: process.env.GITHUB_TOKEN || "",
},
},
],
});
Connecting all servers:
await pool.connectAll();
// Errors per-server are logged but don't fail the entire operation
Getting all clients:
for (const client of pool.getAllClients()) {
console.log(`Connected to: ${client.serverName}`);
}
Getting server status:
const status = pool.getStatus();
console.log(status);
// [
// { name: "filesystem", connected: true, toolCount: 8 },
// { name: "github", connected: true, toolCount: 12 }
// ]
Adding a server dynamically:
await pool.addServer({
name: "postgres",
config: {
command: "npx",
args: ["-y", "@modelcontextprotocol/server-postgres"],
},
env: {
DATABASE_URL: "postgresql://...",
},
});
Disconnecting all servers:
await pool.disconnectAll();
3. MCPServer - Create Custom MCP Servers
Build custom MCP servers to expose your own tools.
Import:
import { MCPServer, createMCPServer, type MCPServerOptions } from "indusagi/mcp";
Creating a custom server:
const server = createMCPServer({
name: "my-calculator",
version: "1.0.0",
});
// Add tools
server.addTool({
name: "add",
description: "Add two numbers",
inputSchema: {
type: "object",
properties: {
a: { type: "number" },
b: { type: "number" },
},
required: ["a", "b"],
},
handler: async ({ a, b }) => ({
result: a + b,
}),
});
// Start stdio transport
await server.startStdio();
Using with NodeJs child_process:
const { spawn } = require("child_process");
const proc = spawn("node", ["my-calculator-server.js"]);
// Server now accepts MCP protocol messages via stdio
4. MCPToolFactory - Convert Tools
Convert MCP tools to agent tools.
Import:
import {
createMCPAgentToolFactory,
registerMCPToolsInRegistry,
createMCPToolsMap,
} from "indusagi/mcp";
Creating agent tools from MCP tools:
import { ToolRegistry } from "indusagi/agent";
const registry = new ToolRegistry();
const client = /* ... MCPClient instance ... */;
// Get MCP tools
const mcpTools = await client.listTools();
// Register all tools
const count = await registerMCPToolsInRegistry(registry, client, mcpTools);
console.log(`Registered ${count} tools from ${client.serverName}`);
Manual tool conversion:
const mcpTool = {
name: "read_file",
description: "Read file contents",
inputSchema: {
type: "object",
properties: {
path: { type: "string" },
},
required: ["path"],
},
};
const factory = createMCPAgentToolFactory(mcpTool, client);
const agentTool = factory();
// Now you can use agentTool with agents
Creating a map of tools:
const toolsMap = createMCPToolsMap(client, mcpTools);
// {
// "filesystem/read_file": AgentTool,
// "filesystem/write_file": AgentTool,
// ...
// }
5. Configuration Management
Import:
import {
loadMCPConfig,
saveConfig,
ensureUserConfigDir,
getUserConfigPath,
getProjectConfigPath,
} from "indusagi/mcp";
Loading configuration:
// Loads from:
// 1. Project config: .mcp.json in current directory
// 2. User config: ~/.mcp.json or ~/.config/mcp/config.json
// 3. Environment: MCP_CONFIG_PATH env var
const configs = loadMCPConfig(process.cwd());
Saving configuration:
const config = {
name: "my-server",
config: {
command: "npx",
args: ["-y", "@modelcontextprotocol/server-example"],
},
};
// Save to user config
await saveConfig(config, { scope: "user" });
// Save to project config
await saveConfig(config, { scope: "project" });
Configuration file locations:
const userPath = getUserConfigPath(); // ~/.mcp.json
const projectPath = getProjectConfigPath(); // .mcp.json
Convenience Functions
Initialize MCP
One-line initialization:
import { initializeMCP } from "indusagi/mcp";
import { ToolRegistry } from "indusagi/agent";
const registry = new ToolRegistry();
const { pool, toolCount } = await initializeMCP(registry, process.cwd());
console.log(`Connected to ${pool.getAllClients().length} servers with ${toolCount} tools`);
Schema Conversion
Convert JSON Schema to TypeBox and vice versa:
import {
jsonSchemaToTypeBox,
convertMCPInputSchema,
convertMCPOutputSchema,
} from "indusagi/mcp";
const jsonSchema = {
type: "object",
properties: {
name: { type: "string" },
age: { type: "number" },
},
required: ["name"],
};
const typeboxSchema = jsonSchemaToTypeBox(jsonSchema);
Error Handling
Import error types:
import { MCPError, MCPErrorCode, isMCPError } from "indusagi/mcp";
Handling errors:
try {
await client.callTool("nonexistent_tool", {});
} catch (error) {
if (isMCPError(error)) {
if (error.code === MCPErrorCode.TOOL_NOT_FOUND) {
console.log("Tool not found");
} else if (error.code === MCPErrorCode.INVALID_PARAMETERS) {
console.log("Invalid parameters");
}
}
}
Available error codes:
enum MCPErrorCode {
CONNECTION_FAILED = "CONNECTION_FAILED",
TIMEOUT = "TIMEOUT",
TOOL_NOT_FOUND = "TOOL_NOT_FOUND",
INVALID_PARAMETERS = "INVALID_PARAMETERS",
EXECUTION_ERROR = "EXECUTION_ERROR",
SERVER_ERROR = "SERVER_ERROR",
NOT_CONNECTED = "NOT_CONNECTED",
CONFIG_ERROR = "CONFIG_ERROR",
SCHEMA_CONVERSION_ERROR = "SCHEMA_CONVERSION_ERROR",
}
Advanced Usage
Custom Tool Registration
class CustomToolRegistry extends ToolRegistry {
async registerMCPTools(pool: MCPClientPool) {
for (const client of pool.getAllClients()) {
const tools = await client.listTools();
for (const tool of tools) {
// Custom registration logic
const factory = createMCPAgentToolFactory(tool, client);
const agentTool = factory();
this.register(agentTool);
}
}
}
}
Server Monitoring
// Monitor connection status
const status = pool.getStatus();
const connected = status.filter(s => s.connected);
const disconnected = status.filter(s => !s.connected);
console.log(`${connected.length}/${status.length} servers connected`);
if (disconnected.length > 0) {
console.warn(`Disconnected servers: ${disconnected.map(s => s.name).join(", ")}`);
}
Dynamic Server Addition
// Add servers based on available plugins
const pluginServers = await discoverPluginServers();
for (const server of pluginServers) {
await pool.addServer(server);
}
Testing
Mock MCP Server for Testing:
import { MCPServer } from "indusagi/mcp";
// Create test server
const testServer = new MCPServer({
name: "test-server",
version: "1.0.0",
});
testServer.addTool({
name: "test_tool",
description: "Test tool",
inputSchema: { type: "object" },
handler: async () => ({ result: "test" }),
});
// Use in tests
Performance Considerations
Connection Pooling
- Reuse
MCPClientPoolacross requests - Share tool registry between requests
- Don't create new pools per request
Tool Caching
// Cache tool list
const toolsCache = new Map<string, MCPToolDefinition[]>();
async function getCachedTools(client: MCPClient) {
if (!toolsCache.has(client.serverName)) {
const tools = await client.listTools();
toolsCache.set(client.serverName, tools);
}
return toolsCache.get(client.serverName);
}
Timeout Management
// Set appropriate timeouts
const client = new MCPClient({
name: "slow-server",
config: { /* ... */ },
timeout: 120000, // 2 minutes for slow operations
});
Troubleshooting
Server Connection Issues
try {
await client.connect();
} catch (error) {
if (error.code === MCPErrorCode.CONNECTION_FAILED) {
console.error("Failed to connect:", error.message);
// Check if command exists
// Check if dependencies are installed
}
}
Tool Execution Failures
try {
const result = await client.callTool(toolName, params);
} catch (error) {
if (error.code === MCPErrorCode.INVALID_PARAMETERS) {
console.error("Invalid parameters for tool:", toolName);
// Log the tool schema
}
}
API Reference
MCPClient
class MCPClient {
constructor(options: MCPClientOptions);
connect(): Promise<void>;
disconnect(): Promise<void>;
listTools(): Promise<MCPToolDefinition[]>;
callTool(name: string, params: any): Promise<any>;
listResources(): Promise<MCPResource[]>;
readResource(uri: string): Promise<string>;
listPrompts(): Promise<MCPPrompt[]>;
getPrompt(name: string, params?: Record<string, string>): Promise<string>;
get serverName(): string;
get isConnected(): boolean;
}
MCPClientPool
class MCPClientPool {
constructor(options: MCPClientPoolOptions);
connectAll(): Promise<void>;
disconnectAll(): Promise<void>;
addServer(config: MCPConnectionOptions): Promise<void>;
removeServer(name: string): Promise<void>;
getAllClients(): MCPClient[];
getClient(name: string): MCPClient | undefined;
getStatus(): MCPServerStatus[];
}
Version: 0.12.15
Last Updated: March 2026
Status: · Production Ready
