MCP (Model Context Protocol)
MCP (Model Context Protocol)
The Model Context Protocol (MCP) is an open standard that enables seamless integration between AI applications and external data sources and tools. The library provides a complete MCP client implementation with support for both local (stdio) and remote (HTTP/HTTPS) servers.
Overview
MCP allows you to:
- Discover tools automatically from MCP servers
- Connect to local servers via stdio (process spawning)
- Connect to remote servers via HTTP/HTTPS (StreamableHTTP)
- Manage multiple servers simultaneously
- Auto-reconnect with exponential backoff
- Namespace tools to prevent conflicts
- Serializable client metadata for diagnostics and host-managed restart flows
Quick Start
1. Install or provision an MCP server
npm install oneringai
# Client dependencies are included. Provision the selected MCP server separately.
2. Connect to a Local MCP Server
import { MCPRegistry, Agent, Connector, Vendor } from 'oneringai';
// Setup connector for LLM
Connector.create({
name: 'openai',
vendor: Vendor.OpenAI,
auth: { type: 'api_key', apiKey: process.env.OPENAI_API_KEY! },
});
// Create MCP client for filesystem server
const client = MCPRegistry.create({
name: 'filesystem',
transport: 'stdio',
transportConfig: {
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-filesystem', process.cwd()],
},
});
// Connect to the server
await client.connect();
console.log(`Connected! Available tools: ${client.tools.length}`);
// Create agent and register MCP tools
const agent = Agent.create({ connector: 'openai', model: 'gpt-4.1' });
client.registerTools(agent.tools);
// Agent can now use MCP tools
const response = await agent.run('List all TypeScript files in the current directory');
console.log(response.output_text);
3. Connect to a Remote MCP Server
// Create HTTP/HTTPS MCP client
const remoteClient = MCPRegistry.create({
name: 'remote-api',
transport: 'https',
transportConfig: {
url: 'https://mcp.example.com/api',
token: process.env.MCP_TOKEN,
headers: {
'X-Client-Version': '1.0.0',
},
reconnection: {
maxRetries: 5,
initialReconnectionDelay: 1000,
maxReconnectionDelay: 30000,
},
},
});
await remoteClient.connect();
remoteClient.registerTools(agent.tools);
Configuration File
Create oneringai.config.json to declare MCP servers:
{
"version": "1.0",
"mcp": {
"servers": [
{
"name": "filesystem",
"displayName": "Filesystem Server",
"transport": "stdio",
"transportConfig": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"]
},
"autoConnect": true,
"toolNamespace": "mcp:fs",
"permissions": {
"defaultScope": "session",
"defaultRiskLevel": "medium"
}
},
{
"name": "github",
"displayName": "GitHub API",
"transport": "https",
"transportConfig": {
"url": "https://mcp.example.com/github",
"token": "${GITHUB_TOKEN}"
},
"autoConnect": false,
"toolNamespace": "mcp:github"
}
]
}
}
Load and use the configuration:
import { MCPRegistry, Agent } from 'oneringai';
// Reads JSON, interpolates ${ENV_VAR} values, and creates all declared clients.
const clients = await MCPRegistry.loadFromConfigFile('./oneringai.config.json');
// Connect every currently disconnected client in the registry.
await MCPRegistry.connectAll();
// Create agent and register tools
const agent = Agent.create({ connector: 'openai', model: 'gpt-4.1' });
for (const client of clients) {
if (client.isConnected()) {
client.registerTools(agent.tools);
}
}
loadFromConfigFile() creates clients but does not connect them. Currently,
MCPRegistry.connectAll() connects all disconnected clients; it does not
filter on the declared autoConnect flag. Connect selected clients individually
if your application needs that distinction. Also use
transportConfig.timeoutMs for the HTTP abort timeout—the top-level
requestTimeoutMs field is currently not applied by MCPClient.
MCPRegistry API
The static registry manages all MCP client connections:
// Create a client
const client = MCPRegistry.create({
name: 'my-server',
transport: 'stdio',
transportConfig: { /* ... */ },
});
// Get a client
const clientVariant2 = MCPRegistry.get('my-server');
// Check if exists
if (MCPRegistry.has('my-server')) {
// ...
}
// List all servers
const serverNames = MCPRegistry.list();
// Get server info
const info = MCPRegistry.getInfo('my-server');
// { name, state, connected, toolCount }
// Get all server info
const allInfo = MCPRegistry.getAllInfo();
// Lifecycle management
await MCPRegistry.connectAll();
await MCPRegistry.disconnectAll();
MCPRegistry.destroyAll();
MCPClient API
Each client manages a connection to one MCP server:
Connection Management
// Connect to server
await client.connect();
// Disconnect
await client.disconnect();
// Reconnect
await client.reconnect();
// Check connection status
const isConnected = client.isConnected();
// Ping server (health check)
const alive = await client.ping();
Tool Operations
// List available tools
const tools = await client.listTools();
console.log(tools.map(t => `${t.name}: ${t.description}`));
// Call a tool directly
const result = await client.callTool('read_file', {
path: './README.md'
});
console.log(result.content);
// Register tools with agent
client.registerTools(agent.tools);
// Unregister tools
client.unregisterTools(agent.tools);
Resource Operations
// List available resources
const resources = await client.listResources();
// Read a resource
const content = await client.readResource('file:///path/to/file');
console.log(content.text);
Current limitation: the client currently initializes negotiated capabilities as
an empty object and does not register an SDK notification handler for resource
updates. As a result, subscribeResource() cannot pass its capability check and
no resource:updated event is emitted. Treat resource subscriptions as
unavailable until that wiring is implemented; listResources() and
readResource() remain usable.
Prompt Operations
// List available prompts
const prompts = await client.listPrompts();
// Get a prompt
const promptResult = await client.getPrompt('summarize', {
length: 'short',
});
// Use prompt messages
for (const msg of promptResult.messages) {
console.log(`${msg.role}: ${msg.content.text}`);
}
Event Monitoring
Listen to connection and execution events:
// Connection events
client.on('connected', () => {
console.log('Connected to MCP server');
});
client.on('disconnected', () => {
console.log('Disconnected from MCP server');
});
client.on('reconnecting', (attempt) => {
console.log(`Reconnecting... attempt ${attempt}`);
});
client.on('failed', (error) => {
console.error('Connection failed:', error);
});
// Tool execution events
client.on('tool:called', (name, args) => {
console.log(`Tool called: ${name}`, args);
});
client.on('tool:result', (name, result) => {
console.log(`Tool result: ${name}`, result);
});
// Error events
client.on('error', (error) => {
console.error('MCP error:', error);
});
Transport Types
Stdio Transport
For local MCP servers (spawns a process):
const client = MCPRegistry.create({
name: 'local-server',
transport: 'stdio',
transportConfig: {
command: 'npx', // or 'node', 'python', etc.
args: ['-y', '@modelcontextprotocol/server-filesystem', '/path'],
env: {
NODE_ENV: 'production',
CUSTOM_VAR: 'value',
},
cwd: '/working/directory', // Optional working directory
},
});
Best for:
- Local file system access
- Database connections (PostgreSQL, SQLite)
- Development and testing
HTTP/HTTPS Transport
For remote MCP servers (StreamableHTTP with SSE):
const client = MCPRegistry.create({
name: 'remote-server',
transport: 'https',
transportConfig: {
url: 'https://mcp.example.com/api',
token: process.env.MCP_TOKEN, // Bearer token
headers: {
'X-Client-Version': '1.0.0',
'X-Custom-Header': 'value',
},
timeoutMs: 30000, // Request timeout (no default — omitting this applies no timeout at all)
sessionId: 'optional-session-id', // For reconnection
reconnection: {
maxReconnectionDelay: 30000, // Max delay between retries (default: 30000)
initialReconnectionDelay: 1000, // Initial delay (default: 1000)
reconnectionDelayGrowFactor: 1.5, // Backoff factor (default: 1.5)
maxRetries: 5, // Max attempts (default: 2)
},
},
});
Best for:
- Cloud-hosted services
- Production deployments
- Team collaboration
- Remote API access
Tool Namespacing
MCP tools are automatically namespaced to prevent conflicts:
// Default namespace: mcp:{server-name}:{tool-name} (sanitized to mcp_{server-name}_{tool-name}
// for tool-name compatibility — colons are not allowed in tool names)
// Example: mcp_filesystem_read_file, mcp_github_create_issue
// Custom namespace
const client = MCPRegistry.create({
name: 'fs',
toolNamespace: 'files',
// ...
});
// Tools: files_read_file, files_write_file, etc.
// Check registered tools
const toolNames = agent.listTools();
console.log(toolNames.filter(name => name.startsWith('mcp_')));
Multi-Server Example
Connect to multiple MCP servers simultaneously:
import { MCPRegistry, Agent, Connector, Vendor } from 'oneringai';
// Setup connector
Connector.create({
name: 'openai',
vendor: Vendor.OpenAI,
auth: { type: 'api_key', apiKey: process.env.OPENAI_API_KEY! },
});
// Create multiple clients
const fsClient = MCPRegistry.create({
name: 'filesystem',
transport: 'stdio',
transportConfig: {
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-filesystem', process.cwd()],
},
});
const githubClient = MCPRegistry.create({
name: 'github',
transport: 'https',
transportConfig: {
url: 'https://mcp.example.com/github',
token: process.env.GITHUB_TOKEN,
},
});
const dbClient = MCPRegistry.create({
name: 'postgres',
transport: 'stdio',
transportConfig: {
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-postgres'],
env: {
DATABASE_URL: process.env.DATABASE_URL!,
},
},
});
// Connect all
await Promise.all([
fsClient.connect(),
githubClient.connect(),
dbClient.connect(),
]);
// Create agent and register all tools
const agent = Agent.create({ connector: 'openai', model: 'gpt-4.1' });
fsClient.registerTools(agent.tools);
githubClient.registerTools(agent.tools);
dbClient.registerTools(agent.tools);
console.log(`Total tools: ${agent.listTools().length}`);
// Agent can now use tools from all servers
await agent.run('Query the database, analyze files, and create a GitHub issue with the results');
Finding MCP Servers
The MCP steering group's reference-server repository currently maintains a small educational set: Everything, Fetch, Filesystem, Git, Memory, Sequential Thinking, and Time. These demonstrate protocol and SDK features and are not presented as production-ready services. Browse published packages in the official MCP Registry and evaluate each server's permissions, maintenance, and threat model. Older GitHub, Google Drive, Slack, PostgreSQL, SQLite, and Brave Search reference servers should not be described as current official packages.
Error Handling
import {
MCPError,
MCPConnectionError,
MCPToolError,
} from 'oneringai';
try {
await client.connect();
} catch (error) {
if (error instanceof MCPConnectionError) {
console.error('Failed to connect:', error.message);
// Retry or use fallback
} else if (error instanceof MCPToolError) {
console.error('Tool execution failed:', error.toolName);
} else if (error instanceof MCPError) {
console.error('Protocol error:', error.message);
}
}
State Persistence
Save and restore MCP client state:
// Get current state
const state = client.getState();
console.log(state);
// {
// name: 'filesystem',
// state: 'connected',
// capabilities: {},
// subscribedResources: [],
// lastConnectedAt: 1234567890,
// connectionAttempts: 0
// }
// Save to storage
await storage.save('mcp-state', state);
// Load diagnostic/subscription metadata
const savedState = await storage.load('mcp-state');
const newClient = MCPRegistry.create(config);
newClient.loadState(savedState);
await newClient.connect();
loadState() restores only the local subscription URI set and reconnect-attempt
counter. It does not restore a live transport, capabilities, tools, or server
session, and connect() does not replay resources/subscribe requests. If a
future client version exposes working subscriptions, the host must still
re-subscribe explicitly after connecting.
Best Practices
- Use Configuration Files - Declare servers in
oneringai.config.jsonfor easier management; remember thatautoConnectis not currently enforced byconnectAll() - Handle Reconnection - Enable
autoReconnectfor production deployments - Monitor Events - Listen to connection events for observability
- Use Namespaces - Set custom
toolNamespaceto organize tools clearly - Error Handling - Always wrap MCP operations in try/catch
- Clean Up - Call
client.disconnect()when done - Health Checks - Use
client.ping()for monitoring - Permission Control - Set appropriate
defaultScopefor security - Set HTTP Timeouts at the transport - Use
transportConfig.timeoutMs;requestTimeoutMsis not currently wired into requests
Troubleshooting
Connection Issues
// Enable detailed error logging
client.on('error', (error) => {
console.error('MCP Error:', error);
console.error('Stack:', error.stack);
});
// Check connection state
console.log('State:', client.state);
console.log('Connected:', client.isConnected());
// Manual reconnect
if (!client.isConnected()) {
await client.reconnect();
}
Tool Discovery
// List all discovered tools
const tools = await client.listTools();
console.log('Available tools:');
tools.forEach(tool => {
console.log(` ${tool.name}: ${tool.description}`);
console.log(' Input schema:', JSON.stringify(tool.inputSchema, null, 2));
});
// Check server capabilities
console.log('Capabilities:', client.capabilities);
Debug Mode
// Log all tool calls
client.on('tool:called', (name, args) => {
console.log(`[DEBUG] Tool called: ${name}`);
console.log('[DEBUG] Args:', JSON.stringify(args, null, 2));
});
client.on('tool:result', (name, result) => {
console.log(`[DEBUG] Tool result: ${name}`);
console.log('[DEBUG] Result:', JSON.stringify(result, null, 2));
});
Transport Extension Boundary
The public MCPServerConfig currently accepts only stdio, http, and
https; MCPClient constructs the corresponding SDK transport internally.
Implementing the SDK's Transport interface alone is therefore not enough to
inject a custom transport into this integration. Add an adapter in the library or
use the MCP SDK client directly when another transport is required.