-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add MCP SSE tooling example #596
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -191,3 +191,17 @@ const agent = new Agent({ | |
| // Start the agent with a prompt that will trigger tool usage | ||
| agent.start("What's the weather in Paris, the time in Tokyo, and what is 25 * 4?"); | ||
| ``` | ||
|
|
||
| ## MCP SSE Tool Integration | ||
|
|
||
| PraisonAI can use remote tools exposed via the Model Context Protocol over Server-Sent Events. | ||
|
|
||
| 1. Start the Python SSE server in another terminal: | ||
| ```bash | ||
| python ../../praisonai-agents/tests/mcp-sse-direct-server.py --host 127.0.0.1 --port 8080 | ||
| ``` | ||
|
|
||
| 2. Run the TypeScript example which connects to this server: | ||
| ```bash | ||
| npx ts-node examples/tools/mcp-sse.ts | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The PR description mentions that running If there are known issues or specific environmental configurations required (e.g., firewall settings, specific Python server behavior not covered by the command), it would be very helpful to document them here. An example that fails without clear troubleshooting guidance can be frustrating for users.
Comment on lines
+204
to
+206
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add error handling guidance for the MCP SSE example. The documentation should mention potential network errors (like the ENETUNREACH error mentioned in the PR objectives) and how users can troubleshoot them. 2. Run the TypeScript example which connects to this server:
```bash
npx ts-node examples/tools/mcp-sse.ts+Troubleshooting: In src/praisonai-ts/examples/README-tool-examples.md around lines 204 to 206, |
||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| import { Agent } from '../../src/agent'; | ||
| import { MCP } from '../../src/tools/mcpSse'; | ||
|
|
||
| async function main() { | ||
| // Connect to the running SSE server | ||
| const mcp = new MCP('http://127.0.0.1:8080/sse'); | ||
| try { | ||
| await mcp.initialize(); | ||
| } catch (error) { | ||
| console.error('Failed to connect to MCP SSE server:', error); | ||
| console.error('Please ensure the server is running at http://127.0.0.1:8080/sse'); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| // Create tool functions that call the remote MCP tools | ||
| const toolFunctions: Record<string, (...args: any[]) => Promise<any>> = {}; | ||
|
|
||
| if (mcp.tools.length === 0) { | ||
| console.warn('Warning: No MCP tools available. Make sure the MCP server is running.'); | ||
| } | ||
|
|
||
| for (const tool of mcp) { | ||
| if (!tool || typeof tool.name !== 'string') { | ||
| console.warn('Skipping invalid tool:', tool); | ||
| continue; | ||
| } | ||
|
|
||
| const paramNames = Object.keys(tool.schemaProperties || {}); | ||
| toolFunctions[tool.name] = async (...args: any[]) => { | ||
| const params: Record<string, any> = {}; | ||
|
|
||
| if (args.length === 1 && typeof args[0] === 'object' && args[0] !== null) { | ||
| // If single object argument, use it directly as params | ||
| Object.assign(params, args[0]); | ||
| } else { | ||
| // Map positional arguments with validation | ||
| if (args.length > paramNames.length) { | ||
| console.warn( | ||
| `Tool ${tool.name}: Too many arguments provided. Expected ${paramNames.length}, got ${args.length}` | ||
| ); | ||
| } | ||
| for (let i = 0; i < Math.min(args.length, paramNames.length); i++) { | ||
| params[paramNames[i]] = args[i]; | ||
| } | ||
| } | ||
| return tool.execute(params); | ||
| }; | ||
| } | ||
|
|
||
| const agent = new Agent({ | ||
| instructions: 'Use the tools to greet people and report the weather.', | ||
| name: 'MCPAgent', | ||
| tools: mcp.toOpenAITools(), | ||
| toolFunctions | ||
| }); | ||
|
|
||
| try { | ||
| const result = await agent.start('Say hello to John and tell the weather in London.'); | ||
| console.log('\nFinal Result:', result); | ||
| } catch (error) { | ||
| console.error('Agent execution failed:', error); | ||
| } finally { | ||
| // Clean up MCP connection | ||
| await mcp.close(); | ||
| } | ||
| } | ||
|
|
||
| if (require.main === module) { | ||
| main(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| import { Client } from '@modelcontextprotocol/sdk/client/index.js'; | ||
| import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'; | ||
| import { BaseTool } from './index'; | ||
|
|
||
| export interface MCPToolInfo { | ||
| name: string; | ||
| description?: string; | ||
| inputSchema?: any; | ||
| } | ||
|
|
||
| export class MCPTool extends BaseTool { | ||
| private client: Client; | ||
| private inputSchema: any; | ||
|
|
||
| constructor(info: MCPToolInfo, client: Client) { | ||
| super(info.name, info.description || `Call the ${info.name} tool`); | ||
| this.client = client; | ||
| this.inputSchema = info.inputSchema || { type: 'object', properties: {}, required: [] }; | ||
| } | ||
|
|
||
| get schemaProperties(): Record<string, any> | undefined { | ||
| return this.inputSchema?.properties; | ||
| } | ||
|
|
||
| async execute(args: any = {}): Promise<any> { | ||
| try { | ||
| const result: any = await this.client.callTool({ name: this.name, arguments: args }); | ||
| if (result.structuredContent) { | ||
| return result.structuredContent; | ||
| } | ||
| if (Array.isArray(result.content) && result.content.length > 0) { | ||
| const item = result.content[0]; | ||
| if (typeof item.text === 'string') return item.text; | ||
| } | ||
| return result; | ||
| } catch (error) { | ||
| throw new Error(`Failed to execute tool ${this.name}: ${error instanceof Error ? error.message : String(error)}`); | ||
| } | ||
| } | ||
|
|
||
| toOpenAITool() { | ||
| return { | ||
| type: 'function', | ||
| function: { | ||
| name: this.name, | ||
| description: this.description, | ||
| parameters: this.inputSchema | ||
| } | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| export class MCP implements Iterable<MCPTool> { | ||
| tools: MCPTool[] = []; | ||
| private client: Client | null = null; | ||
|
|
||
| constructor(private url: string, private debug = false) { | ||
| if (debug) { | ||
| console.log(`MCP client initialized for URL: ${url}`); | ||
| } | ||
| } | ||
|
|
||
| async initialize(): Promise<void> { | ||
| if (this.client) { | ||
| if (this.debug) console.log('MCP client already initialized'); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| this.client = new Client({ name: 'praisonai-ts-mcp', version: '1.0.0' }); | ||
| const transport = new SSEClientTransport(new URL(this.url)); | ||
| await this.client.connect(transport); | ||
| const { tools } = await this.client.listTools(); | ||
| this.tools = tools.map((t: any) => new MCPTool({ | ||
| name: t.name, | ||
| description: t.description, | ||
| inputSchema: t.inputSchema | ||
| }, this.client as Client)); | ||
|
|
||
| if (this.debug) console.log(`Initialized MCP with ${this.tools.length} tools`); | ||
| } catch (error) { | ||
| if (this.client) { | ||
| await this.client.close().catch(() => {}); | ||
| this.client = null; | ||
| } | ||
| throw new Error(`Failed to initialize MCP client: ${error instanceof Error ? error.message : 'Unknown error'}`); | ||
| } | ||
| } | ||
|
|
||
| [Symbol.iterator](): Iterator<MCPTool> { | ||
| return this.tools[Symbol.iterator](); | ||
| } | ||
|
|
||
| toOpenAITools() { | ||
| return this.tools.map(t => t.toOpenAITool()); | ||
| } | ||
|
|
||
| async close(): Promise<void> { | ||
| if (this.client) { | ||
| try { | ||
| await this.client.close(); | ||
| } catch (error) { | ||
| if (this.debug) { | ||
| console.warn('Error closing MCP client:', error); | ||
| } | ||
| } finally { | ||
| this.client = null; | ||
| this.tools = []; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| get isConnected(): boolean { | ||
| return this.client !== null; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The path
../../praisonai-agents/tests/mcp-sse-direct-server.pyassumes a specific directory structure relative to thepraisonai-tspackage. This might be confusing or not work for users who have cloned only this repository or have a different setup.Have you considered if this instruction could be made more robust or if a note about the expected directory layout should be added? For instance, mentioning where
praisonai-agentsshould be located relative topraisonai-ts.