@agentscope-ai/agentscope 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/index.d.mts +234 -0
- package/dist/agent/index.d.ts +234 -0
- package/dist/agent/index.js +1412 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/index.mjs +1375 -0
- package/dist/agent/index.mjs.map +1 -0
- package/dist/base-BOx3UzOl.d.mts +41 -0
- package/dist/base-BoIps2RL.d.ts +41 -0
- package/dist/base-C7jwyH4Z.d.mts +52 -0
- package/dist/base-Cwi4bjze.d.ts +127 -0
- package/dist/base-DYlBMCy_.d.mts +127 -0
- package/dist/base-NX-knWOv.d.ts +52 -0
- package/dist/block-VsnHrllL.d.mts +48 -0
- package/dist/block-VsnHrllL.d.ts +48 -0
- package/dist/event/index.d.mts +181 -0
- package/dist/event/index.d.ts +181 -0
- package/dist/event/index.js +58 -0
- package/dist/event/index.js.map +1 -0
- package/dist/event/index.mjs +33 -0
- package/dist/event/index.mjs.map +1 -0
- package/dist/formatter/index.d.mts +187 -0
- package/dist/formatter/index.d.ts +187 -0
- package/dist/formatter/index.js +647 -0
- package/dist/formatter/index.js.map +1 -0
- package/dist/formatter/index.mjs +616 -0
- package/dist/formatter/index.mjs.map +1 -0
- package/dist/index-BTJDlKvQ.d.mts +195 -0
- package/dist/index-BcatlwXQ.d.ts +195 -0
- package/dist/index-CAxQAkiP.d.mts +21 -0
- package/dist/index-CAxQAkiP.d.ts +21 -0
- package/dist/mcp/index.d.mts +9 -0
- package/dist/mcp/index.d.ts +9 -0
- package/dist/mcp/index.js +432 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/index.mjs +408 -0
- package/dist/mcp/index.mjs.map +1 -0
- package/dist/message/index.d.mts +10 -0
- package/dist/message/index.d.ts +10 -0
- package/dist/message/index.js +67 -0
- package/dist/message/index.js.map +1 -0
- package/dist/message/index.mjs +37 -0
- package/dist/message/index.mjs.map +1 -0
- package/dist/message-CkN21KaY.d.mts +99 -0
- package/dist/message-CzLeTlua.d.ts +99 -0
- package/dist/model/index.d.mts +377 -0
- package/dist/model/index.d.ts +377 -0
- package/dist/model/index.js +1880 -0
- package/dist/model/index.js.map +1 -0
- package/dist/model/index.mjs +1849 -0
- package/dist/model/index.mjs.map +1 -0
- package/dist/storage/index.d.mts +68 -0
- package/dist/storage/index.d.ts +68 -0
- package/dist/storage/index.js +250 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/index.mjs +212 -0
- package/dist/storage/index.mjs.map +1 -0
- package/dist/tool/index.d.mts +311 -0
- package/dist/tool/index.d.ts +311 -0
- package/dist/tool/index.js +1494 -0
- package/dist/tool/index.js.map +1 -0
- package/dist/tool/index.mjs +1447 -0
- package/dist/tool/index.mjs.map +1 -0
- package/dist/toolkit-CEpulFi0.d.ts +99 -0
- package/dist/toolkit-CGEZSZPa.d.mts +99 -0
- package/jest.config.js +11 -0
- package/package.json +92 -0
- package/src/_utils/common.ts +104 -0
- package/src/_utils/index.ts +1 -0
- package/src/agent/agent-base.ts +0 -0
- package/src/agent/agent.test.ts +1028 -0
- package/src/agent/agent.ts +1032 -0
- package/src/agent/index.ts +2 -0
- package/src/agent/interfaces.ts +23 -0
- package/src/agent/test-compression.ts +72 -0
- package/src/event/index.ts +250 -0
- package/src/formatter/base.ts +133 -0
- package/src/formatter/dashscope-chat-formatter.test.ts +372 -0
- package/src/formatter/dashscope-chat-formatter.ts +163 -0
- package/src/formatter/deepseek-chat-formatter.ts +130 -0
- package/src/formatter/index.ts +5 -0
- package/src/formatter/ollama-chat-formatter.ts +67 -0
- package/src/formatter/openai-chat-formatter.test.ts +263 -0
- package/src/formatter/openai-chat-formatter.ts +301 -0
- package/src/formatter/openai.md +767 -0
- package/src/mcp/base.ts +114 -0
- package/src/mcp/http.test.ts +303 -0
- package/src/mcp/http.ts +224 -0
- package/src/mcp/index.ts +2 -0
- package/src/mcp/stdio.test.ts +91 -0
- package/src/mcp/stdio.ts +119 -0
- package/src/message/block.ts +60 -0
- package/src/message/enums.ts +4 -0
- package/src/message/index.ts +12 -0
- package/src/message/message.test.ts +80 -0
- package/src/message/message.ts +131 -0
- package/src/model/base.ts +226 -0
- package/src/model/dashscope-model.test.ts +335 -0
- package/src/model/dashscope-model.ts +441 -0
- package/src/model/deepseek-model.test.ts +279 -0
- package/src/model/deepseek-model.ts +401 -0
- package/src/model/index.ts +7 -0
- package/src/model/ollama-model.test.ts +307 -0
- package/src/model/ollama-model.ts +356 -0
- package/src/model/openai-model.ts +327 -0
- package/src/model/response.ts +22 -0
- package/src/model/usage.ts +12 -0
- package/src/storage/base.ts +52 -0
- package/src/storage/file-system.test.ts +587 -0
- package/src/storage/file-system.ts +269 -0
- package/src/storage/index.ts +2 -0
- package/src/tool/base.ts +23 -0
- package/src/tool/bash.test.ts +174 -0
- package/src/tool/bash.ts +152 -0
- package/src/tool/edit.test.ts +83 -0
- package/src/tool/edit.ts +95 -0
- package/src/tool/glob.test.ts +63 -0
- package/src/tool/glob.ts +166 -0
- package/src/tool/grep.test.ts +74 -0
- package/src/tool/grep.ts +256 -0
- package/src/tool/index.ts +10 -0
- package/src/tool/read.test.ts +77 -0
- package/src/tool/read.ts +117 -0
- package/src/tool/response.ts +82 -0
- package/src/tool/task.test.ts +299 -0
- package/src/tool/task.ts +399 -0
- package/src/tool/toolkit.test.ts +636 -0
- package/src/tool/toolkit.ts +601 -0
- package/src/tool/write.test.ts +52 -0
- package/src/tool/write.ts +57 -0
- package/src/type/index.ts +52 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.cjs.json +11 -0
- package/tsconfig.esm.json +10 -0
- package/tsconfig.json +14 -0
- package/tsup.config.ts +20 -0
- package/typedoc.json +52 -0
package/src/mcp/base.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import { CallToolRequest, CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import z from 'zod';
|
|
4
|
+
|
|
5
|
+
import { Tool, ToolResponse } from '../tool';
|
|
6
|
+
import { createToolResponse } from '../tool/response';
|
|
7
|
+
import { ToolInputSchema } from '../type';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Type definition for getting a client instance
|
|
11
|
+
*/
|
|
12
|
+
type GetClient = () => Promise<Client>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Type definition for releasing a client instance
|
|
16
|
+
*/
|
|
17
|
+
type ReleaseClient = (client: Client) => Promise<void>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* MCPTool class that wraps an MCP tool and provides a callable interface
|
|
21
|
+
*/
|
|
22
|
+
export class MCPTool implements Tool {
|
|
23
|
+
name: string;
|
|
24
|
+
description: string;
|
|
25
|
+
inputSchema: z.ZodObject | ToolInputSchema;
|
|
26
|
+
requireUserConfirm = false;
|
|
27
|
+
call: (input: Record<string, unknown>) => Promise<ToolResponse>;
|
|
28
|
+
|
|
29
|
+
private getClient: GetClient;
|
|
30
|
+
private releaseClient: ReleaseClient;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Initialize an MCPTool instance
|
|
34
|
+
* @param root0
|
|
35
|
+
* @param root0.name
|
|
36
|
+
* @param root0.description
|
|
37
|
+
* @param root0.inputSchema
|
|
38
|
+
* @param root0.getClient
|
|
39
|
+
* @param root0.releaseClient
|
|
40
|
+
*/
|
|
41
|
+
constructor({
|
|
42
|
+
name,
|
|
43
|
+
description,
|
|
44
|
+
inputSchema,
|
|
45
|
+
getClient,
|
|
46
|
+
releaseClient,
|
|
47
|
+
}: {
|
|
48
|
+
name: string;
|
|
49
|
+
description: string;
|
|
50
|
+
inputSchema: z.ZodObject | ToolInputSchema;
|
|
51
|
+
getClient: GetClient;
|
|
52
|
+
releaseClient: ReleaseClient;
|
|
53
|
+
}) {
|
|
54
|
+
this.name = name;
|
|
55
|
+
this.description = description;
|
|
56
|
+
this.inputSchema = inputSchema;
|
|
57
|
+
this.getClient = getClient;
|
|
58
|
+
this.releaseClient = releaseClient;
|
|
59
|
+
this.call = this._call.bind(this);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Call the MCP tool with the specified input arguments. This method sends a request to the MCP server to execute
|
|
64
|
+
* the tool and returns the result as a ToolResponse.
|
|
65
|
+
*
|
|
66
|
+
* @param arguments
|
|
67
|
+
* @param input
|
|
68
|
+
* @returns A ToolResponse object containing the result of the tool execution, or an error message if the call fails.
|
|
69
|
+
*/
|
|
70
|
+
async _call(input: Record<string, unknown>) {
|
|
71
|
+
const client = await this.getClient();
|
|
72
|
+
try {
|
|
73
|
+
const request: CallToolRequest = {
|
|
74
|
+
method: 'tools/call',
|
|
75
|
+
params: {
|
|
76
|
+
name: this.name,
|
|
77
|
+
arguments: input,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
const result = await client.request(request, CallToolResultSchema);
|
|
81
|
+
|
|
82
|
+
const content: ToolResponse['content'] = [];
|
|
83
|
+
result.content.forEach(item => {
|
|
84
|
+
if (item.type === 'text') {
|
|
85
|
+
content.push({ type: 'text', text: item.text, id: crypto.randomUUID() });
|
|
86
|
+
} else if (item.type === 'image' || item.type === 'audio') {
|
|
87
|
+
content.push({
|
|
88
|
+
id: crypto.randomUUID(),
|
|
89
|
+
type: 'data',
|
|
90
|
+
source: { type: 'base64', mediaType: item.mimeType, data: item.data },
|
|
91
|
+
});
|
|
92
|
+
} else {
|
|
93
|
+
console.warn(
|
|
94
|
+
`Unsupported content type '${item.type}' in tool result, skipping...`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
return createToolResponse({ content, state: 'success' });
|
|
99
|
+
} catch (error) {
|
|
100
|
+
return createToolResponse({
|
|
101
|
+
content: [
|
|
102
|
+
{
|
|
103
|
+
id: crypto.randomUUID(),
|
|
104
|
+
type: 'text',
|
|
105
|
+
text: `Error calling tool '${this.name}': ${error}`,
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
state: 'error',
|
|
109
|
+
});
|
|
110
|
+
} finally {
|
|
111
|
+
await this.releaseClient(client);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import * as http from 'http';
|
|
2
|
+
|
|
3
|
+
import { HTTPMCPClient } from './http';
|
|
4
|
+
import { Toolkit, ToolResponse } from '../tool';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates a minimal MCP HTTP server for testing purposes.
|
|
8
|
+
*
|
|
9
|
+
* @param port - The port number to listen on
|
|
10
|
+
* @returns An HTTP server instance
|
|
11
|
+
*/
|
|
12
|
+
function createTestMCPServer(port: number): http.Server {
|
|
13
|
+
const sessions = new Map<string, Record<string, unknown>>();
|
|
14
|
+
let sessionCounter = 0;
|
|
15
|
+
|
|
16
|
+
const server = http.createServer(async (req, res) => {
|
|
17
|
+
// Handle CORS
|
|
18
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
19
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
|
|
20
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Mcp-Session-Id');
|
|
21
|
+
|
|
22
|
+
if (req.method === 'OPTIONS') {
|
|
23
|
+
res.writeHead(200);
|
|
24
|
+
res.end();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let body = '';
|
|
29
|
+
req.on('data', chunk => {
|
|
30
|
+
body += chunk.toString();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
req.on('end', async () => {
|
|
34
|
+
try {
|
|
35
|
+
const message = JSON.parse(body);
|
|
36
|
+
let response: Record<string, unknown>;
|
|
37
|
+
|
|
38
|
+
switch (message.method) {
|
|
39
|
+
case 'initialize': {
|
|
40
|
+
sessionCounter++;
|
|
41
|
+
const sessionId = `session-${sessionCounter}`;
|
|
42
|
+
sessions.set(sessionId, {});
|
|
43
|
+
response = {
|
|
44
|
+
jsonrpc: '2.0',
|
|
45
|
+
id: message.id,
|
|
46
|
+
result: {
|
|
47
|
+
protocolVersion: '2024-11-05',
|
|
48
|
+
capabilities: { tools: {} },
|
|
49
|
+
serverInfo: {
|
|
50
|
+
name: 'test-mcp-server',
|
|
51
|
+
version: '1.0.0',
|
|
52
|
+
},
|
|
53
|
+
sessionId,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
case 'tools/list': {
|
|
60
|
+
response = {
|
|
61
|
+
jsonrpc: '2.0',
|
|
62
|
+
id: message.id,
|
|
63
|
+
result: {
|
|
64
|
+
tools: [
|
|
65
|
+
{
|
|
66
|
+
name: 'add',
|
|
67
|
+
description: 'Adds two numbers together',
|
|
68
|
+
inputSchema: {
|
|
69
|
+
type: 'object',
|
|
70
|
+
properties: {
|
|
71
|
+
a: {
|
|
72
|
+
type: 'number',
|
|
73
|
+
description: 'First number',
|
|
74
|
+
},
|
|
75
|
+
b: {
|
|
76
|
+
type: 'number',
|
|
77
|
+
description: 'Second number',
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
required: ['a', 'b'],
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
case 'tools/call': {
|
|
90
|
+
if (message.params.name === 'add') {
|
|
91
|
+
const { a, b } = message.params.arguments;
|
|
92
|
+
const result = a + b;
|
|
93
|
+
response = {
|
|
94
|
+
jsonrpc: '2.0',
|
|
95
|
+
id: message.id,
|
|
96
|
+
result: {
|
|
97
|
+
content: [
|
|
98
|
+
{
|
|
99
|
+
type: 'text',
|
|
100
|
+
text: `Result: ${a} + ${b} = ${result}`,
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
} else {
|
|
106
|
+
response = {
|
|
107
|
+
jsonrpc: '2.0',
|
|
108
|
+
id: message.id,
|
|
109
|
+
error: {
|
|
110
|
+
code: -32601,
|
|
111
|
+
message: 'Tool not found',
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
default: {
|
|
119
|
+
response = {
|
|
120
|
+
jsonrpc: '2.0',
|
|
121
|
+
id: message.id,
|
|
122
|
+
error: {
|
|
123
|
+
code: -32601,
|
|
124
|
+
message: 'Method not found',
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
131
|
+
res.end(JSON.stringify(response));
|
|
132
|
+
} catch {
|
|
133
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
134
|
+
res.end(
|
|
135
|
+
JSON.stringify({
|
|
136
|
+
jsonrpc: '2.0',
|
|
137
|
+
id: null,
|
|
138
|
+
error: {
|
|
139
|
+
code: -32700,
|
|
140
|
+
message: 'Parse error',
|
|
141
|
+
},
|
|
142
|
+
})
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
server.listen(port);
|
|
149
|
+
return server;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
describe('HTTPStatefulMCPClient', () => {
|
|
153
|
+
const TEST_PORT = 13579;
|
|
154
|
+
const TEST_URL = `http://localhost:${TEST_PORT}/mcp`;
|
|
155
|
+
let testServer: http.Server;
|
|
156
|
+
|
|
157
|
+
beforeAll(() => {
|
|
158
|
+
testServer = createTestMCPServer(TEST_PORT);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
afterAll(done => {
|
|
162
|
+
testServer.close(done);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('Create HTTP MCP client, list and execute tools', async () => {
|
|
166
|
+
const client = new HTTPMCPClient({
|
|
167
|
+
name: 'test-mcp-server',
|
|
168
|
+
transportType: 'streamable-http',
|
|
169
|
+
url: TEST_URL,
|
|
170
|
+
stateful: true,
|
|
171
|
+
});
|
|
172
|
+
await client.connect();
|
|
173
|
+
|
|
174
|
+
const tools = await client.listTools();
|
|
175
|
+
expect(tools.length).toBeGreaterThan(0);
|
|
176
|
+
|
|
177
|
+
const func = await client.getCallableFunction({ name: 'add' });
|
|
178
|
+
const res = await func.call({ a: 5, b: 3 });
|
|
179
|
+
expect(res.content.length).toBeGreaterThan(0);
|
|
180
|
+
expect(res.content[0].type).toBe('text');
|
|
181
|
+
if (res.content[0].type === 'text') {
|
|
182
|
+
expect(res.content[0].text).toContain('8');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
await client.close();
|
|
186
|
+
|
|
187
|
+
// Try to reconnect and list tools again
|
|
188
|
+
await client.connect();
|
|
189
|
+
const tools2 = await client.listTools();
|
|
190
|
+
expect(tools2.length).toBeGreaterThan(0);
|
|
191
|
+
|
|
192
|
+
await client.close();
|
|
193
|
+
}, 10000);
|
|
194
|
+
|
|
195
|
+
test('Test toolkit works with HTTPStatefulMCPClient', async () => {
|
|
196
|
+
const client = new HTTPMCPClient({
|
|
197
|
+
name: 'test-mcp-server',
|
|
198
|
+
transportType: 'streamable-http',
|
|
199
|
+
url: TEST_URL,
|
|
200
|
+
stateful: true,
|
|
201
|
+
});
|
|
202
|
+
await client.connect();
|
|
203
|
+
|
|
204
|
+
const toolkit = new Toolkit();
|
|
205
|
+
await toolkit.registerMCPClient({ client, enabledTools: ['add'] });
|
|
206
|
+
|
|
207
|
+
const schema = toolkit.getJSONSchemas();
|
|
208
|
+
expect(schema.length).toBe(2);
|
|
209
|
+
expect(schema[1].type).toBe('function');
|
|
210
|
+
expect(schema[1].function.name).toBe('add');
|
|
211
|
+
expect(schema[1].function.parameters).toBeDefined();
|
|
212
|
+
|
|
213
|
+
const res = toolkit.callToolFunction({
|
|
214
|
+
id: '123',
|
|
215
|
+
name: 'add',
|
|
216
|
+
type: 'tool_call',
|
|
217
|
+
input: `{"a": 10, "b": 20}`,
|
|
218
|
+
});
|
|
219
|
+
for await (const item of res) {
|
|
220
|
+
expect(item.content.length).toBeGreaterThan(0);
|
|
221
|
+
expect(item.content[0].type).toBe('text');
|
|
222
|
+
if (item.content[0].type === 'text') {
|
|
223
|
+
expect(item.content[0].text).toContain('30');
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
await client.close();
|
|
228
|
+
}, 10000);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe('HTTPStatelessMCPClient', () => {
|
|
232
|
+
const TEST_PORT = 13580;
|
|
233
|
+
const TEST_URL = `http://localhost:${TEST_PORT}/mcp`;
|
|
234
|
+
let testServer: http.Server;
|
|
235
|
+
|
|
236
|
+
beforeAll(() => {
|
|
237
|
+
testServer = createTestMCPServer(TEST_PORT);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
afterAll(done => {
|
|
241
|
+
testServer.close(done);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test('Create stateless HTTP MCP client, list and execute tools without explicit connect/close', async () => {
|
|
245
|
+
const client = new HTTPMCPClient({
|
|
246
|
+
name: 'test-mcp-server',
|
|
247
|
+
transportType: 'streamable-http',
|
|
248
|
+
url: TEST_URL,
|
|
249
|
+
stateful: false,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// connect() and close() are no-ops for stateless clients
|
|
253
|
+
await client.connect();
|
|
254
|
+
|
|
255
|
+
const tools = await client.listTools();
|
|
256
|
+
expect(tools.length).toBeGreaterThan(0);
|
|
257
|
+
|
|
258
|
+
const func = await client.getCallableFunction({ name: 'add' });
|
|
259
|
+
const res = await func.call({ a: 7, b: 13 });
|
|
260
|
+
expect(res.content.length).toBeGreaterThan(0);
|
|
261
|
+
expect(res.content[0].type).toBe('text');
|
|
262
|
+
if (res.content[0].type === 'text') {
|
|
263
|
+
expect(res.content[0].text).toContain('20');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
await client.close();
|
|
267
|
+
}, 10000);
|
|
268
|
+
|
|
269
|
+
test('Test toolkit works with HTTPStatelessMCPClient', async () => {
|
|
270
|
+
const client = new HTTPMCPClient({
|
|
271
|
+
name: 'test-mcp-server',
|
|
272
|
+
transportType: 'streamable-http',
|
|
273
|
+
url: TEST_URL,
|
|
274
|
+
stateful: false,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const toolkit = new Toolkit();
|
|
278
|
+
await toolkit.registerMCPClient({ client, enabledTools: ['add'] });
|
|
279
|
+
|
|
280
|
+
const schema = toolkit.getJSONSchemas();
|
|
281
|
+
expect(schema.length).toBe(2);
|
|
282
|
+
expect(schema[1].type).toBe('function');
|
|
283
|
+
expect(schema[1].function.name).toBe('add');
|
|
284
|
+
expect(schema[1].function.parameters).toBeDefined();
|
|
285
|
+
|
|
286
|
+
const res = toolkit.callToolFunction({
|
|
287
|
+
id: '123',
|
|
288
|
+
name: 'add',
|
|
289
|
+
type: 'tool_call',
|
|
290
|
+
input: `{"a": 15, "b": 25}`,
|
|
291
|
+
});
|
|
292
|
+
const collectedRes: ToolResponse[] = [];
|
|
293
|
+
for await (const item of res) {
|
|
294
|
+
collectedRes.push(item);
|
|
295
|
+
expect(item.content.length).toBeGreaterThan(0);
|
|
296
|
+
expect(item.content[0].type).toBe('text');
|
|
297
|
+
if (item.content[0].type === 'text') {
|
|
298
|
+
expect(item.content[0].text).toContain('40');
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
expect(collectedRes.length).toBe(1);
|
|
302
|
+
}, 10000);
|
|
303
|
+
});
|
package/src/mcp/http.ts
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import {
|
|
3
|
+
SSEClientTransport,
|
|
4
|
+
SSEClientTransportOptions,
|
|
5
|
+
} from '@modelcontextprotocol/sdk/client/sse.js';
|
|
6
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
7
|
+
import { StreamableHTTPClientTransportOptions } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
8
|
+
import type { RequestOptions } from '@modelcontextprotocol/sdk/shared/protocol.js';
|
|
9
|
+
import { ListToolsRequest, ListToolsResultSchema, Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
10
|
+
|
|
11
|
+
import { MCPTool } from './base';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The HTTP MCP client class that connects to an MCP server using either Streamable HTTP or Server-Sent Events (SSE)
|
|
15
|
+
* transport.
|
|
16
|
+
* Note the client is stateful, meaning that developers should manually call the `connect()` and `close()` methods to
|
|
17
|
+
* manage the connection lifecycle.
|
|
18
|
+
*/
|
|
19
|
+
export class HTTPMCPClient {
|
|
20
|
+
name: string;
|
|
21
|
+
private requestOptions?: RequestOptions;
|
|
22
|
+
private client?: Client;
|
|
23
|
+
private transport?: StreamableHTTPClientTransport | SSEClientTransport;
|
|
24
|
+
private transportType: 'streamable-http' | 'sse';
|
|
25
|
+
private url: string;
|
|
26
|
+
private transportOpts?: StreamableHTTPClientTransportOptions | SSEClientTransportOptions;
|
|
27
|
+
private stateful: boolean;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Initialize the HTTPStatefulMCPClient with the specified transport type, URL, and options.
|
|
31
|
+
*
|
|
32
|
+
* @param root0
|
|
33
|
+
* @param root0.name
|
|
34
|
+
* @param root0.transportType
|
|
35
|
+
* @param root0.url
|
|
36
|
+
* @param root0.stateful
|
|
37
|
+
* @param root0.transportOpts
|
|
38
|
+
* @param root0.requestOptions
|
|
39
|
+
*/
|
|
40
|
+
constructor({
|
|
41
|
+
name,
|
|
42
|
+
transportType,
|
|
43
|
+
url,
|
|
44
|
+
stateful = true,
|
|
45
|
+
transportOpts,
|
|
46
|
+
requestOptions,
|
|
47
|
+
}: {
|
|
48
|
+
name: string;
|
|
49
|
+
transportType: 'streamable-http' | 'sse';
|
|
50
|
+
url: string;
|
|
51
|
+
stateful?: boolean;
|
|
52
|
+
transportOpts?: StreamableHTTPClientTransportOptions | SSEClientTransportOptions;
|
|
53
|
+
requestOptions?: RequestOptions;
|
|
54
|
+
}) {
|
|
55
|
+
this.name = name;
|
|
56
|
+
this.transportType = transportType;
|
|
57
|
+
this.transportOpts = transportOpts;
|
|
58
|
+
this.requestOptions = requestOptions;
|
|
59
|
+
this.url = url;
|
|
60
|
+
this.stateful = stateful;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Connect to the MCP server with the specified transport and URL. This method must be called before making any
|
|
65
|
+
* requests to the server.
|
|
66
|
+
*/
|
|
67
|
+
async connect() {
|
|
68
|
+
if (!this.stateful) {
|
|
69
|
+
console.log(
|
|
70
|
+
`MCP client '${this.name}' initialized with stateful=false will connect and close the connection automatically for each request, no need to call 'connect()' method explicitly.`
|
|
71
|
+
);
|
|
72
|
+
} else {
|
|
73
|
+
await this._connect();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The internal method to establish the connection to the MCP server. It initializes the appropriate transport
|
|
79
|
+
* based on the specified transport type and creates a new Client instance to manage the connection and requests.
|
|
80
|
+
*/
|
|
81
|
+
protected async _connect() {
|
|
82
|
+
const baseUrl = new URL(this.url);
|
|
83
|
+
if (this.transportType === 'streamable-http') {
|
|
84
|
+
this.transport = new StreamableHTTPClientTransport(baseUrl, this.transportOpts);
|
|
85
|
+
} else {
|
|
86
|
+
this.transport = new SSEClientTransport(baseUrl, this.transportOpts);
|
|
87
|
+
}
|
|
88
|
+
this.client = new Client({
|
|
89
|
+
name: this.name,
|
|
90
|
+
version: '1.0.0',
|
|
91
|
+
});
|
|
92
|
+
await this.client.connect(this.transport, this.requestOptions);
|
|
93
|
+
console.log(`MCP client '${this.name}' is connected`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* List all tools available on the MCP server.
|
|
98
|
+
*
|
|
99
|
+
* @returns An array of MCPTool instances representing the tools available on the server.
|
|
100
|
+
*/
|
|
101
|
+
async listTools(): Promise<MCPTool[]> {
|
|
102
|
+
let listClient: Client;
|
|
103
|
+
|
|
104
|
+
if (this.stateful) {
|
|
105
|
+
if (!this.client) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`Client not initialized, call 'connect()' method first for the MCP client named '${this.name}'`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
listClient = this.client;
|
|
111
|
+
} else {
|
|
112
|
+
listClient = await this._createClient();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const toolsRequest: ListToolsRequest = {
|
|
117
|
+
method: 'tools/list',
|
|
118
|
+
params: {},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const toolsResult = await listClient.request(toolsRequest, ListToolsResultSchema);
|
|
122
|
+
if (toolsResult.tools === undefined) {
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return toolsResult.tools.map((tool: Tool) => {
|
|
127
|
+
if (this.stateful) {
|
|
128
|
+
return new MCPTool({
|
|
129
|
+
name: tool.name,
|
|
130
|
+
description: tool.description || '',
|
|
131
|
+
inputSchema: tool.inputSchema,
|
|
132
|
+
getClient: async () => this.client!,
|
|
133
|
+
releaseClient: async () => {},
|
|
134
|
+
});
|
|
135
|
+
} else {
|
|
136
|
+
return new MCPTool({
|
|
137
|
+
name: tool.name,
|
|
138
|
+
description: tool.description || '',
|
|
139
|
+
inputSchema: tool.inputSchema,
|
|
140
|
+
getClient: () => this._createClient(),
|
|
141
|
+
releaseClient: async (c: Client) => {
|
|
142
|
+
await c.close();
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
} finally {
|
|
148
|
+
if (!this.stateful) {
|
|
149
|
+
await listClient.close();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Close the connection to the MCP server and clean up any resources used by the client.
|
|
156
|
+
*/
|
|
157
|
+
async close() {
|
|
158
|
+
if (!this.stateful) {
|
|
159
|
+
console.log(
|
|
160
|
+
`MCP client '${this.name}' initialized with stateful=false will connect and close the connection automatically for each request, no need to call 'close()' method explicitly.`
|
|
161
|
+
);
|
|
162
|
+
} else {
|
|
163
|
+
await this._close();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* The internal method to close the connection to the MCP server.
|
|
169
|
+
*/
|
|
170
|
+
protected async _close() {
|
|
171
|
+
try {
|
|
172
|
+
await this.client?.close();
|
|
173
|
+
} finally {
|
|
174
|
+
console.log(`MCP client '${this.name} is closed.'`);
|
|
175
|
+
this.client = undefined;
|
|
176
|
+
this.transport = undefined;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Create a new client instance without storing it in this.client.
|
|
182
|
+
* Used for stateless operations where each request needs its own client.
|
|
183
|
+
* @returns A new connected Client instance
|
|
184
|
+
*/
|
|
185
|
+
protected async _createClient(): Promise<Client> {
|
|
186
|
+
const baseUrl = new URL(this.url);
|
|
187
|
+
const transport =
|
|
188
|
+
this.transportType === 'streamable-http'
|
|
189
|
+
? new StreamableHTTPClientTransport(baseUrl, this.transportOpts)
|
|
190
|
+
: new SSEClientTransport(baseUrl, this.transportOpts);
|
|
191
|
+
|
|
192
|
+
const client = new Client({
|
|
193
|
+
name: this.name,
|
|
194
|
+
version: '1.0.0',
|
|
195
|
+
});
|
|
196
|
+
await client.connect(transport, this.requestOptions);
|
|
197
|
+
return client;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Get a callable function for a specific tool by its name.
|
|
202
|
+
* @param root0
|
|
203
|
+
* @param root0.name
|
|
204
|
+
* @returns An instance of MCPTool that can be called to execute the tool's functionality.
|
|
205
|
+
*/
|
|
206
|
+
async getCallableFunction({ name }: { name: string }) {
|
|
207
|
+
if (this.stateful && !this.client) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
`Client not initialized, call 'connect()' method first for the MCP client named '${this.name}'`
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const tools = await this.listTools();
|
|
214
|
+
const targetTool = tools.find(tool => tool.name === name);
|
|
215
|
+
|
|
216
|
+
if (!targetTool) {
|
|
217
|
+
throw new Error(
|
|
218
|
+
`Tool '${name}' not found in MCP server '${this.name}'. Available tools: ${tools.map(t => t.name).join(', ')}`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return targetTool;
|
|
223
|
+
}
|
|
224
|
+
}
|
package/src/mcp/index.ts
ADDED