Skip to content

Commit 8ec9619

Browse files
committed
feat: Add basic Model Context Protocol (MCP) support
1 parent 92a5f88 commit 8ec9619

File tree

9 files changed

+448
-0
lines changed

9 files changed

+448
-0
lines changed
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/**
2+
* Model Context Protocol (MCP) Client
3+
*
4+
* This module implements a client for the Model Context Protocol (MCP), which allows
5+
* applications to provide standardized context to Large Language Models (LLMs).
6+
*/
7+
8+
import fetch from 'node-fetch';
9+
10+
/**
11+
* Configuration for an MCP server
12+
*/
13+
export interface McpServerConfig {
14+
/** Unique name for this MCP server */
15+
name: string;
16+
/** URL of the MCP server */
17+
url: string;
18+
/** Optional authentication configuration */
19+
auth?: {
20+
/** Authentication type (currently only 'bearer' is supported) */
21+
type: 'bearer';
22+
/** Authentication token */
23+
token: string;
24+
};
25+
}
26+
27+
/**
28+
* MCP Resource descriptor
29+
*/
30+
export interface McpResource {
31+
/** Resource URI in the format 'scheme://path' */
32+
uri: string;
33+
/** Optional metadata about the resource */
34+
metadata?: Record<string, unknown>;
35+
}
36+
37+
/**
38+
* MCP Client class for interacting with MCP servers
39+
*/
40+
export class McpClient {
41+
private servers: Map<string, McpServerConfig> = new Map();
42+
43+
/**
44+
* Create a new MCP client
45+
* @param servers Optional array of server configurations to add
46+
*/
47+
constructor(servers: McpServerConfig[] = []) {
48+
servers.forEach(server => this.addServer(server));
49+
}
50+
51+
/**
52+
* Add an MCP server to the client
53+
* @param server Server configuration
54+
*/
55+
addServer(server: McpServerConfig): void {
56+
this.servers.set(server.name, server);
57+
}
58+
59+
/**
60+
* Remove an MCP server from the client
61+
* @param name Name of the server to remove
62+
*/
63+
removeServer(name: string): void {
64+
this.servers.delete(name);
65+
}
66+
67+
/**
68+
* Get all configured servers
69+
* @returns Array of server configurations
70+
*/
71+
getServers(): McpServerConfig[] {
72+
return Array.from(this.servers.values());
73+
}
74+
75+
/**
76+
* Fetch a resource from an MCP server
77+
* @param uri Resource URI in the format 'scheme://path'
78+
* @returns The resource content as a string
79+
*/
80+
async fetchResource(uri: string): Promise<string> {
81+
// Parse the URI to determine which server to use
82+
const serverName = this.getServerNameFromUri(uri);
83+
if (!serverName) {
84+
throw new Error(`Could not determine server from URI: ${uri}`);
85+
}
86+
87+
const server = this.servers.get(serverName);
88+
if (!server) {
89+
throw new Error(`Server not found: ${serverName}`);
90+
}
91+
92+
// Extract the path from the URI
93+
const path = this.getPathFromUri(uri);
94+
95+
// Construct the full URL
96+
const url = new URL(path, server.url);
97+
98+
// Prepare headers
99+
const headers: Record<string, string> = {
100+
'Accept': 'application/json',
101+
};
102+
103+
// Add authentication if configured
104+
if (server.auth) {
105+
if (server.auth.type === 'bearer') {
106+
headers['Authorization'] = `Bearer ${server.auth.token}`;
107+
}
108+
}
109+
110+
// Make the request
111+
const response = await fetch(url.toString(), {
112+
method: 'GET',
113+
headers,
114+
});
115+
116+
if (!response.ok) {
117+
throw new Error(`Failed to fetch resource: ${response.status} ${response.statusText}`);
118+
}
119+
120+
return await response.text();
121+
}
122+
123+
/**
124+
* Get available resources from all configured servers
125+
* @returns Array of available resources
126+
*/
127+
async getAvailableResources(): Promise<McpResource[]> {
128+
const resources: McpResource[] = [];
129+
130+
for (const server of this.servers.values()) {
131+
try {
132+
// Fetch resources from this server
133+
const url = new URL('/.well-known/mcp/resources', server.url);
134+
135+
// Prepare headers
136+
const headers: Record<string, string> = {
137+
'Accept': 'application/json',
138+
};
139+
140+
// Add authentication if configured
141+
if (server.auth) {
142+
if (server.auth.type === 'bearer') {
143+
headers['Authorization'] = `Bearer ${server.auth.token}`;
144+
}
145+
}
146+
147+
// Make the request
148+
const response = await fetch(url.toString(), {
149+
method: 'GET',
150+
headers,
151+
});
152+
153+
if (response.ok) {
154+
const data = await response.json() as { resources: McpResource[] };
155+
resources.push(...data.resources);
156+
}
157+
} catch (error) {
158+
console.error(`Failed to fetch resources from server ${server.name}:`, error);
159+
}
160+
}
161+
162+
return resources;
163+
}
164+
165+
/**
166+
* Extract the server name from a resource URI
167+
* @param uri Resource URI in the format 'scheme://path'
168+
* @returns The server name or undefined if not found
169+
* @private
170+
*/
171+
private getServerNameFromUri(uri: string): string | undefined {
172+
// For simplicity, we'll use the first part of the URI as the server name
173+
// In a real implementation, this would be more sophisticated
174+
const match = uri.match(/^([^:]+):\/\//);
175+
return match ? match[1] : undefined;
176+
}
177+
178+
/**
179+
* Extract the path from a resource URI
180+
* @param uri Resource URI in the format 'scheme://path'
181+
* @returns The path part of the URI
182+
* @private
183+
*/
184+
private getPathFromUri(uri: string): string {
185+
// Remove the scheme:// part
186+
return uri.replace(/^[^:]+:\/\//, '');
187+
}
188+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Model Context Protocol (MCP) Integration
3+
*
4+
* This module provides integration with the Model Context Protocol (MCP),
5+
* allowing MyCoder to use context from MCP-compatible servers.
6+
*/
7+
8+
export * from './client';
9+
export * from './types';
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* Type definitions for MCP integration
3+
*/
4+
5+
/**
6+
* MCP configuration in mycoder.config.js
7+
*/
8+
export interface McpConfig {
9+
/** Array of MCP server configurations */
10+
servers?: McpServerConfig[];
11+
/** Default resources to load automatically */
12+
defaultResources?: string[];
13+
}
14+
15+
/**
16+
* Configuration for an MCP server
17+
*/
18+
export interface McpServerConfig {
19+
/** Unique name for this MCP server */
20+
name: string;
21+
/** URL of the MCP server */
22+
url: string;
23+
/** Optional authentication configuration */
24+
auth?: {
25+
/** Authentication type (currently only 'bearer' is supported) */
26+
type: 'bearer';
27+
/** Authentication token */
28+
token: string;
29+
};
30+
}
31+
32+
/**
33+
* MCP Resource descriptor
34+
*/
35+
export interface McpResource {
36+
/** Resource URI in the format 'scheme://path' */
37+
uri: string;
38+
/** Optional metadata about the resource */
39+
metadata?: Record<string, unknown>;
40+
}
41+
42+
/**
43+
* MCP Tool descriptor
44+
*/
45+
export interface McpTool {
46+
/** Tool ID */
47+
id: string;
48+
/** Human-readable name */
49+
name: string;
50+
/** Description of what the tool does */
51+
description: string;
52+
/** Server that provides this tool */
53+
server: string;
54+
/** Tool parameters schema */
55+
parameters?: Record<string, unknown>;
56+
}

packages/agent/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ export * from './core/toolAgent/toolExecutor.js';
3535
export * from './core/toolAgent/tokenTracking.js';
3636
export * from './core/toolAgent/types.js';
3737
export * from './core/llm/provider.js';
38+
// MCP
39+
export * from './core/mcp/index.js';
3840

3941
// Utils
4042
export * from './tools/getTools.js';

packages/agent/src/tools/getTools.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,20 @@ import { sequenceCompleteTool } from './system/sequenceComplete.js';
1414
import { shellMessageTool } from './system/shellMessage.js';
1515
import { shellStartTool } from './system/shellStart.js';
1616
import { sleepTool } from './system/sleep.js';
17+
import { McpTool } from './mcp.js';
1718

1819
// Import these separately to avoid circular dependencies
1920

21+
import { McpConfig } from '../core/mcp/types.js';
22+
2023
interface GetToolsOptions {
2124
userPrompt?: boolean;
25+
mcpConfig?: McpConfig;
2226
}
2327

2428
export function getTools(options?: GetToolsOptions): Tool[] {
2529
const userPrompt = options?.userPrompt !== false; // Default to true if not specified
30+
const mcpConfig = options?.mcpConfig || { servers: [], defaultResources: [] };
2631

2732
// Force cast to Tool type to avoid TypeScript issues
2833
const tools: Tool[] = [
@@ -45,5 +50,11 @@ export function getTools(options?: GetToolsOptions): Tool[] {
4550
tools.push(userPromptTool as unknown as Tool);
4651
}
4752

53+
// Add MCP tool if we have any servers configured
54+
if (mcpConfig.servers && mcpConfig.servers.length > 0) {
55+
const mcpTool = new McpTool(mcpConfig);
56+
tools.push(mcpTool as unknown as Tool);
57+
}
58+
4859
return tools;
4960
}

0 commit comments

Comments
 (0)