Skip to content

Commit 90ebfdd

Browse files
Merge pull request #596 from MervinPraison/codex/add-mcp-tool-to-agent-creation
Add MCP SSE tooling example
2 parents d3304e3 + 385700f commit 90ebfdd

5 files changed

Lines changed: 204 additions & 2 deletions

File tree

src/praisonai-ts/examples/README-tool-examples.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,17 @@ const agent = new Agent({
191191
// Start the agent with a prompt that will trigger tool usage
192192
agent.start("What's the weather in Paris, the time in Tokyo, and what is 25 * 4?");
193193
```
194+
195+
## MCP SSE Tool Integration
196+
197+
PraisonAI can use remote tools exposed via the Model Context Protocol over Server-Sent Events.
198+
199+
1. Start the Python SSE server in another terminal:
200+
```bash
201+
python ../../praisonai-agents/tests/mcp-sse-direct-server.py --host 127.0.0.1 --port 8080
202+
```
203+
204+
2. Run the TypeScript example which connects to this server:
205+
```bash
206+
npx ts-node examples/tools/mcp-sse.ts
207+
```
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { Agent } from '../../src/agent';
2+
import { MCP } from '../../src/tools/mcpSse';
3+
4+
async function main() {
5+
// Connect to the running SSE server
6+
const mcp = new MCP('http://127.0.0.1:8080/sse');
7+
try {
8+
await mcp.initialize();
9+
} catch (error) {
10+
console.error('Failed to connect to MCP SSE server:', error);
11+
console.error('Please ensure the server is running at http://127.0.0.1:8080/sse');
12+
process.exit(1);
13+
}
14+
15+
// Create tool functions that call the remote MCP tools
16+
const toolFunctions: Record<string, (...args: any[]) => Promise<any>> = {};
17+
18+
if (mcp.tools.length === 0) {
19+
console.warn('Warning: No MCP tools available. Make sure the MCP server is running.');
20+
}
21+
22+
for (const tool of mcp) {
23+
if (!tool || typeof tool.name !== 'string') {
24+
console.warn('Skipping invalid tool:', tool);
25+
continue;
26+
}
27+
28+
const paramNames = Object.keys(tool.schemaProperties || {});
29+
toolFunctions[tool.name] = async (...args: any[]) => {
30+
const params: Record<string, any> = {};
31+
32+
if (args.length === 1 && typeof args[0] === 'object' && args[0] !== null) {
33+
// If single object argument, use it directly as params
34+
Object.assign(params, args[0]);
35+
} else {
36+
// Map positional arguments with validation
37+
if (args.length > paramNames.length) {
38+
console.warn(
39+
`Tool ${tool.name}: Too many arguments provided. Expected ${paramNames.length}, got ${args.length}`
40+
);
41+
}
42+
for (let i = 0; i < Math.min(args.length, paramNames.length); i++) {
43+
params[paramNames[i]] = args[i];
44+
}
45+
}
46+
return tool.execute(params);
47+
};
48+
}
49+
50+
const agent = new Agent({
51+
instructions: 'Use the tools to greet people and report the weather.',
52+
name: 'MCPAgent',
53+
tools: mcp.toOpenAITools(),
54+
toolFunctions
55+
});
56+
57+
try {
58+
const result = await agent.start('Say hello to John and tell the weather in London.');
59+
console.log('\nFinal Result:', result);
60+
} catch (error) {
61+
console.error('Agent execution failed:', error);
62+
} finally {
63+
// Clean up MCP connection
64+
await mcp.close();
65+
}
66+
}
67+
68+
if (require.main === module) {
69+
main();
70+
}

src/praisonai-ts/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@
6262
"fast-xml-parser": "^4.5.1",
6363
"node-fetch": "^2.6.9",
6464
"openai": "^4.81.0",
65-
"praisonai": "^1.0.19"
65+
"praisonai": "^1.0.19",
66+
"@modelcontextprotocol/sdk": "^1.12.1"
6667
},
6768
"optionalDependencies": {
6869
"boxen": "^7.1.1",

src/praisonai-ts/src/tools/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ export class BaseTool implements Tool {
2020
}
2121

2222
// Export all tool modules
23-
export * from './arxivTools';
23+
export * from './arxivTools';
24+
export * from './mcpSse';
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2+
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
3+
import { BaseTool } from './index';
4+
5+
export interface MCPToolInfo {
6+
name: string;
7+
description?: string;
8+
inputSchema?: any;
9+
}
10+
11+
export class MCPTool extends BaseTool {
12+
private client: Client;
13+
private inputSchema: any;
14+
15+
constructor(info: MCPToolInfo, client: Client) {
16+
super(info.name, info.description || `Call the ${info.name} tool`);
17+
this.client = client;
18+
this.inputSchema = info.inputSchema || { type: 'object', properties: {}, required: [] };
19+
}
20+
21+
get schemaProperties(): Record<string, any> | undefined {
22+
return this.inputSchema?.properties;
23+
}
24+
25+
async execute(args: any = {}): Promise<any> {
26+
try {
27+
const result: any = await this.client.callTool({ name: this.name, arguments: args });
28+
if (result.structuredContent) {
29+
return result.structuredContent;
30+
}
31+
if (Array.isArray(result.content) && result.content.length > 0) {
32+
const item = result.content[0];
33+
if (typeof item.text === 'string') return item.text;
34+
}
35+
return result;
36+
} catch (error) {
37+
throw new Error(`Failed to execute tool ${this.name}: ${error instanceof Error ? error.message : String(error)}`);
38+
}
39+
}
40+
41+
toOpenAITool() {
42+
return {
43+
type: 'function',
44+
function: {
45+
name: this.name,
46+
description: this.description,
47+
parameters: this.inputSchema
48+
}
49+
};
50+
}
51+
}
52+
53+
export class MCP implements Iterable<MCPTool> {
54+
tools: MCPTool[] = [];
55+
private client: Client | null = null;
56+
57+
constructor(private url: string, private debug = false) {
58+
if (debug) {
59+
console.log(`MCP client initialized for URL: ${url}`);
60+
}
61+
}
62+
63+
async initialize(): Promise<void> {
64+
if (this.client) {
65+
if (this.debug) console.log('MCP client already initialized');
66+
return;
67+
}
68+
69+
try {
70+
this.client = new Client({ name: 'praisonai-ts-mcp', version: '1.0.0' });
71+
const transport = new SSEClientTransport(new URL(this.url));
72+
await this.client.connect(transport);
73+
const { tools } = await this.client.listTools();
74+
this.tools = tools.map((t: any) => new MCPTool({
75+
name: t.name,
76+
description: t.description,
77+
inputSchema: t.inputSchema
78+
}, this.client as Client));
79+
80+
if (this.debug) console.log(`Initialized MCP with ${this.tools.length} tools`);
81+
} catch (error) {
82+
if (this.client) {
83+
await this.client.close().catch(() => {});
84+
this.client = null;
85+
}
86+
throw new Error(`Failed to initialize MCP client: ${error instanceof Error ? error.message : 'Unknown error'}`);
87+
}
88+
}
89+
90+
[Symbol.iterator](): Iterator<MCPTool> {
91+
return this.tools[Symbol.iterator]();
92+
}
93+
94+
toOpenAITools() {
95+
return this.tools.map(t => t.toOpenAITool());
96+
}
97+
98+
async close(): Promise<void> {
99+
if (this.client) {
100+
try {
101+
await this.client.close();
102+
} catch (error) {
103+
if (this.debug) {
104+
console.warn('Error closing MCP client:', error);
105+
}
106+
} finally {
107+
this.client = null;
108+
this.tools = [];
109+
}
110+
}
111+
}
112+
113+
get isConnected(): boolean {
114+
return this.client !== null;
115+
}
116+
}

0 commit comments

Comments
 (0)