Skip to content

Commit 62afd69

Browse files
committed
feat: integrate MCP tools with agent and add remote tool support
1 parent b815eb7 commit 62afd69

File tree

6 files changed

+164
-30
lines changed

6 files changed

+164
-30
lines changed

examples/mcp/agent.js

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,27 @@
1-
// Exemplo de uso do Agent com MCPTool para validar conexão e troca de mensagens com servidores MCP
2-
const { Agent } = require('../../src');
1+
// Exemplo de agente integrado ao MCP server de weather
2+
const { Agent, Runner } = require('../../src');
33

4-
// Substitua por URLs reais dos hosts MCP que estejam rodando (pode apontar para localhost:3000)
5-
const MCP_SERVERS = [
6-
'http://localhost:3000',
7-
// 'http://localhost:3002'
8-
];
4+
(async () => {
5+
// URL do MCP server (ajuste conforme necessário)
6+
const mcpWeatherUrl = 'http://localhost:3001/sse';
97

10-
async function main() {
11-
// Cria agente com MCPTool
8+
// Instancia o agente com integração MCP
129
const agent = new Agent({
13-
name: 'Agente-MCP-Exemplo',
14-
instructions: 'Teste de integração MCP.',
15-
mcpServers: MCP_SERVERS
10+
name: 'WeatherAgent',
11+
instructions: 'Consulte informações meteorológicas usando ferramentas remotas.',
12+
mcpServers: [mcpWeatherUrl]
1613
});
1714

18-
// Conecta a todos os servidores MCP
19-
await agent.mcpTool.connectAll();
20-
console.log('Conectado aos servidores MCP:', MCP_SERVERS);
15+
// Inicializa o agente e aguarda o carregamento das ferramentas MCP
16+
await agent.init();
2117

22-
// Envia mensagem de teste para cada servidor MCP
23-
for (const url of MCP_SERVERS) {
24-
const input = 'Mensagem de teste para o servidor MCP: ' + url;
25-
const resposta = await agent.mcpTool.sendMessage(url, input);
26-
console.log(`Resposta do MCP (${url}):`, resposta);
27-
}
28-
}
18+
// Exibe as ferramentas disponíveis
19+
console.log('Ferramentas disponíveis:', agent.tools.map(t => t.name));
2920

30-
main().catch(err => {
31-
console.error('Erro no exemplo MCP:', err);
32-
});
21+
// Input natural do usuário
22+
const input = 'Quais alertas meteorológicos existem para NY?';
23+
24+
// Executa o agente, que decide qual ferramenta usar
25+
const result = await Runner.run(agent, input);
26+
console.log('Saída final do agente:', result.finalOutput);
27+
})();

examples/mcp/client.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
44
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
55
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
66

7-
87
let client = undefined
98
const url = "http://localhost:3001/sse";
109

src/agent.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
const OpenAIProvider = require('./providers/OpenAIProvider');
22

33

4+
const { MCPManager } = require('./mcp');
5+
const Tool = require('./tool');
6+
47
class Agent {
58
/**
69
* Create a new agent
@@ -48,12 +51,55 @@ class Agent {
4851
this.outputType = outputType;
4952
this.handoffDescription = handoffDescription;
5053

54+
// Inicializa MCPManager se houver MCPs
55+
this.mcpServers = Array.isArray(arguments[0]?.mcpServers) ? arguments[0].mcpServers : [];
56+
this.mcpManager = this.mcpServers.length > 0 ? new MCPManager(this.mcpServers) : null;
57+
5158
// Initialize the provider
5259
this.llmProvider = this.initializeProvider(provider, {
5360
...providerConfig,
5461
model: this.model,
5562
...this.modelSettings
5663
});
64+
65+
// Se houver MCPs, inicializa ferramentas remotas automaticamente (não bloqueia construtor)
66+
if (this.mcpManager) {
67+
(async () => {
68+
await this.initializeMCPTools();
69+
})();
70+
}
71+
}
72+
73+
/**
74+
* Inicializa ferramentas MCP remotas e integra ao array this.tools
75+
*/
76+
/**
77+
* Inicializa o agente e aguarda o carregamento das ferramentas MCP (se houver MCPs).
78+
* Uso recomendado: await agent.init();
79+
* Torna transparente para o usuário a espera pelo carregamento das ferramentas remotas.
80+
*/
81+
async init({ maxTentativas = 10, intervaloMs = 500 } = {}) {
82+
if (!this.mcpManager) return; // Não há MCPs, nada a esperar
83+
let tentativas = 0;
84+
while (this.tools.length === 0 && tentativas < maxTentativas) {
85+
await new Promise(r => setTimeout(r, intervaloMs));
86+
tentativas++;
87+
}
88+
if (this.tools.length === 0) {
89+
throw new Error('Ferramentas MCP não carregadas após aguardar.');
90+
}
91+
}
92+
93+
async initializeMCPTools() {
94+
if (!this.mcpManager) return;
95+
await this.mcpManager.connectAll();
96+
const mcpToolsInfo = await this.mcpManager.listAllTools();
97+
// Cria Tool para cada ferramenta MCP
98+
const mcpTools = mcpToolsInfo.map(toolInfo =>
99+
Tool.fromMCP(toolInfo, this.mcpManager.getClientForTool(toolInfo))
100+
);
101+
// Integra ferramentas MCP às ferramentas locais
102+
this.tools = [...this.tools, ...mcpTools];
57103
}
58104

59105
/**

src/mcp.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
3+
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
4+
5+
// Classe para gerenciar conexões e listagem de ferramentas dos MCPs
6+
export class MCPManager {
7+
constructor(servers = []) {
8+
this.servers = servers; // Array de URLs dos MCPs
9+
this.clients = []; // Array de clientes MCP conectados
10+
this.toolsMap = {}; // Mapeamento toolId -> client
11+
}
12+
13+
async connectAll() {
14+
for (const url of this.servers) {
15+
let client;
16+
let transport;
17+
try {
18+
client = new Client({ name: 'streamable-http-client', version: '1.0.0' });
19+
transport = new StreamableHTTPClientTransport(new URL(url));
20+
await client.connect(transport);
21+
// Se conectar, adiciona
22+
this.clients.push(client);
23+
continue;
24+
} catch (error) {
25+
// Fallback para SSE
26+
try {
27+
client = new Client({ name: 'sse-client', version: '1.0.0' });
28+
transport = new SSEClientTransport(new URL(url));
29+
await client.connect(transport);
30+
this.clients.push(client);
31+
} catch (err) {
32+
console.error(`Falha ao conectar ao MCP em ${url}:`, err);
33+
}
34+
}
35+
}
36+
}
37+
38+
async listAllTools() {
39+
let tools = [];
40+
for (const client of this.clients) {
41+
try {
42+
const response = await client.listTools();
43+
// Compatibilidade: pode ser .tools ou array direto
44+
const clientTools = Array.isArray(response) ? response : response.tools || [];
45+
for (const tool of clientTools) {
46+
// Supondo que tool.name seja único
47+
this.toolsMap[tool.name] = client;
48+
tools.push(tool);
49+
}
50+
} catch (err) {
51+
console.error('Erro ao listar ferramentas do MCP:', err);
52+
}
53+
}
54+
return tools;
55+
}
56+
57+
getClientForTool(toolInfo) {
58+
// toolInfo.name é o identificador único
59+
return this.toolsMap[toolInfo.name];
60+
}
61+
}

src/runner.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,11 @@ class Runner {
122122
messages,
123123
handoffAgent.modelSettings
124124
);
125-
125+
126126
// Set final output and add response to messages
127127
finalOutput = handoffResponse.content;
128128
messages.push(handoffResponse);
129-
129+
130130
// Exit the main loop since we have a final response
131131
currentTurn = maxTurns;
132132
break;
@@ -173,14 +173,18 @@ class Runner {
173173
throw new Error(`Max turns (${maxTurns}) exceeded`);
174174
}
175175

176-
return new RunResult({
176+
177+
const result = new RunResult({
177178
input,
178179
messages,
179180
finalOutput,
180181
lastAgent: currentAgent,
181182
inputGuardrailResults,
182183
outputGuardrailResults: []
183184
});
185+
setImmediate(() => process.exit(0));
186+
return result;
187+
184188
}
185189

186190
/**

src/tool.js

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,41 @@ class Tool {
88
* @param {string} options.description - Descrição do que a ferramenta faz
99
* @param {Object} [options.parameters={}] - JSON Schema dos parâmetros da ferramenta
1010
* @param {Function} options.execute - Função a ser executada quando a ferramenta é chamada
11+
* @param {boolean} [options.isRemote=false] - Se é uma ferramenta remota MCP
12+
* @param {Object} [options.mcpClient] - Cliente MCP associado (se remota)
1113
*/
12-
constructor({ name, description, parameters = { type: 'object', properties: {} }, execute }) {
14+
constructor({ name, description, parameters = { type: 'object', properties: {} }, execute, isRemote = false, mcpClient = null }) {
1315
this.name = name;
1416
this.description = description;
1517
this.parameters = parameters;
1618
this._execute = execute;
19+
this.isRemote = isRemote;
20+
this.mcpClient = mcpClient;
21+
}
22+
23+
/**
24+
* Fábrica para criar Tool a partir de toolInfo MCP
25+
* @param {Object} toolInfo - Info retornada do MCP
26+
* @param {Object} mcpClient - Instância do cliente MCP
27+
* @returns {Tool}
28+
*/
29+
static fromMCP(toolInfo, mcpClient) {
30+
return new Tool({
31+
name: toolInfo.name,
32+
description: toolInfo.description,
33+
parameters: toolInfo.inputSchema || { type: 'object', properties: {} },
34+
isRemote: true,
35+
mcpClient,
36+
execute: async (args) => {
37+
// Executa remotamente via MCP
38+
const result = await mcpClient.callTool({
39+
name: toolInfo.name,
40+
arguments: args
41+
});
42+
// Retorna resultado bruto ou ajusta conforme necessário
43+
return result.content ? result.content[0]?.text ?? result : result;
44+
}
45+
});
1746
}
1847

1948
/**

0 commit comments

Comments
 (0)