@stacksjs/ai 0.70.55 → 0.70.56
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/package.json +10 -7
- package/src/agents/claude/index.ts +0 -310
- package/src/agents/index.ts +0 -7
- package/src/buddy.ts +0 -619
- package/src/drivers/anthropic/index.ts +0 -430
- package/src/drivers/claude-agent-sdk/index.ts +0 -370
- package/src/drivers/index.ts +0 -14
- package/src/drivers/ollama/index.ts +0 -514
- package/src/drivers/openai/index.ts +0 -529
- package/src/image.ts +0 -607
- package/src/index.ts +0 -53
- package/src/mcp.ts +0 -658
- package/src/personalization.ts +0 -490
- package/src/search.ts +0 -555
- package/src/text.ts +0 -79
- package/src/types.ts +0 -229
- package/src/utils/client-bedrock-runtime.ts +0 -51
- package/src/utils/client-bedrock.ts +0 -75
- package/src/utils/model-access.ts +0 -32
- package/src/utils/retry.ts +0 -124
- package/src/utils/tokens.ts +0 -159
- package/src/utils/usage.ts +0 -98
- package/src/utils/vision.ts +0 -119
- /package/dist/{src/agents → agents}/claude/index.d.ts +0 -0
- /package/dist/{src/agents → agents}/index.d.ts +0 -0
- /package/dist/{src/buddy.d.ts → buddy.d.ts} +0 -0
- /package/dist/{src/drivers → drivers}/anthropic/index.d.ts +0 -0
- /package/dist/{src/drivers → drivers}/claude-agent-sdk/index.d.ts +0 -0
- /package/dist/{src/drivers → drivers}/index.d.ts +0 -0
- /package/dist/{src/drivers → drivers}/ollama/index.d.ts +0 -0
- /package/dist/{src/drivers → drivers}/openai/index.d.ts +0 -0
- /package/dist/{src/image.d.ts → image.d.ts} +0 -0
- /package/dist/{src/index.d.ts → index.d.ts} +0 -0
- /package/dist/{src/index.js → index.js} +0 -0
- /package/dist/{src/mcp.d.ts → mcp.d.ts} +0 -0
- /package/dist/{src/personalization.d.ts → personalization.d.ts} +0 -0
- /package/dist/{src/search.d.ts → search.d.ts} +0 -0
- /package/dist/{src/text.d.ts → text.d.ts} +0 -0
- /package/dist/{src/types.d.ts → types.d.ts} +0 -0
- /package/dist/{src/utils → utils}/client-bedrock-runtime.d.ts +0 -0
- /package/dist/{src/utils → utils}/client-bedrock.d.ts +0 -0
- /package/dist/{src/utils → utils}/model-access.d.ts +0 -0
- /package/dist/{src/utils → utils}/retry.d.ts +0 -0
- /package/dist/{src/utils → utils}/tokens.d.ts +0 -0
- /package/dist/{src/utils → utils}/usage.d.ts +0 -0
- /package/dist/{src/utils → utils}/vision.d.ts +0 -0
package/src/mcp.ts
DELETED
|
@@ -1,658 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP (Model Context Protocol) Module
|
|
3
|
-
*
|
|
4
|
-
* Implements an MCP client for connecting to MCP servers, discovering tools,
|
|
5
|
-
* and invoking them through AI providers. This enables AI models to interact
|
|
6
|
-
* with external services via a standardized protocol.
|
|
7
|
-
*
|
|
8
|
-
* @see https://modelcontextprotocol.io
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
// ============================================================================
|
|
12
|
-
// Types
|
|
13
|
-
// ============================================================================
|
|
14
|
-
|
|
15
|
-
export interface MCPServerConfig {
|
|
16
|
-
name: string
|
|
17
|
-
transport: MCPTransport
|
|
18
|
-
capabilities?: MCPCapabilities
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export type MCPTransport =
|
|
22
|
-
| { type: 'stdio'; command: string; args?: string[]; env?: Record<string, string> }
|
|
23
|
-
| { type: 'sse'; url: string; headers?: Record<string, string> }
|
|
24
|
-
| { type: 'streamable-http'; url: string; headers?: Record<string, string> }
|
|
25
|
-
|
|
26
|
-
export interface MCPCapabilities {
|
|
27
|
-
tools?: boolean
|
|
28
|
-
resources?: boolean
|
|
29
|
-
prompts?: boolean
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface MCPTool {
|
|
33
|
-
name: string
|
|
34
|
-
description: string
|
|
35
|
-
inputSchema: Record<string, unknown>
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface MCPResource {
|
|
39
|
-
uri: string
|
|
40
|
-
name: string
|
|
41
|
-
description?: string
|
|
42
|
-
mimeType?: string
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface MCPPrompt {
|
|
46
|
-
name: string
|
|
47
|
-
description?: string
|
|
48
|
-
arguments?: Array<{
|
|
49
|
-
name: string
|
|
50
|
-
description?: string
|
|
51
|
-
required?: boolean
|
|
52
|
-
}>
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export interface MCPToolCallResult {
|
|
56
|
-
content: Array<{
|
|
57
|
-
type: 'text' | 'image' | 'resource'
|
|
58
|
-
text?: string
|
|
59
|
-
data?: string
|
|
60
|
-
mimeType?: string
|
|
61
|
-
}>
|
|
62
|
-
isError?: boolean
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export interface MCPResourceContent {
|
|
66
|
-
uri: string
|
|
67
|
-
mimeType?: string
|
|
68
|
-
text?: string
|
|
69
|
-
blob?: string
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// ============================================================================
|
|
73
|
-
// MCP Client
|
|
74
|
-
// ============================================================================
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* MCP Client for connecting to Model Context Protocol servers.
|
|
78
|
-
*
|
|
79
|
-
* Supports stdio and SSE/streamable-http transports.
|
|
80
|
-
* Provides tool discovery, invocation, resource reading, and prompt listing.
|
|
81
|
-
*/
|
|
82
|
-
export class MCPClient {
|
|
83
|
-
private config: MCPServerConfig
|
|
84
|
-
private process: ReturnType<typeof import('bun').spawn> | null = null
|
|
85
|
-
private requestId = 0
|
|
86
|
-
private pendingRequests = new Map<number, { resolve: (value: any) => void; reject: (error: Error) => void }>()
|
|
87
|
-
private tools: MCPTool[] = []
|
|
88
|
-
private resources: MCPResource[] = []
|
|
89
|
-
private prompts: MCPPrompt[] = []
|
|
90
|
-
private initialized = false
|
|
91
|
-
private buffer = ''
|
|
92
|
-
|
|
93
|
-
constructor(config: MCPServerConfig) {
|
|
94
|
-
this.config = config
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Connect to the MCP server and initialize the session.
|
|
99
|
-
*/
|
|
100
|
-
async connect(): Promise<void> {
|
|
101
|
-
if (this.config.transport.type === 'stdio') {
|
|
102
|
-
await this.connectStdio()
|
|
103
|
-
}
|
|
104
|
-
else if (this.config.transport.type === 'sse' || this.config.transport.type === 'streamable-http') {
|
|
105
|
-
await this.connectHTTP()
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
throw new Error(`Unsupported transport type: ${(this.config.transport as any).type}`)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
this.initialized = true
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
private async connectStdio(): Promise<void> {
|
|
115
|
-
const transport = this.config.transport as { type: 'stdio'; command: string; args?: string[]; env?: Record<string, string> }
|
|
116
|
-
const { spawn } = await import('bun')
|
|
117
|
-
|
|
118
|
-
this.process = spawn([transport.command, ...(transport.args || [])], {
|
|
119
|
-
stdin: 'pipe',
|
|
120
|
-
stdout: 'pipe',
|
|
121
|
-
stderr: 'pipe',
|
|
122
|
-
env: { ...process.env, ...transport.env },
|
|
123
|
-
}) as any
|
|
124
|
-
|
|
125
|
-
// Start reading stdout for responses
|
|
126
|
-
this.readStdout()
|
|
127
|
-
|
|
128
|
-
// Send initialize request
|
|
129
|
-
await this.sendRequest('initialize', {
|
|
130
|
-
protocolVersion: '2024-11-05',
|
|
131
|
-
capabilities: {},
|
|
132
|
-
clientInfo: { name: 'stacks-ai', version: '1.0.0' },
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
// Send initialized notification
|
|
136
|
-
await this.sendNotification('notifications/initialized', {})
|
|
137
|
-
|
|
138
|
-
// Discover capabilities
|
|
139
|
-
await this.discoverCapabilities()
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
private async connectHTTP(): Promise<void> {
|
|
143
|
-
const transport = this.config.transport as { type: 'sse' | 'streamable-http'; url: string; headers?: Record<string, string> }
|
|
144
|
-
|
|
145
|
-
// Send initialize via HTTP
|
|
146
|
-
const response = await fetch(transport.url, {
|
|
147
|
-
method: 'POST',
|
|
148
|
-
headers: {
|
|
149
|
-
'Content-Type': 'application/json',
|
|
150
|
-
...transport.headers,
|
|
151
|
-
},
|
|
152
|
-
body: JSON.stringify({
|
|
153
|
-
jsonrpc: '2.0',
|
|
154
|
-
id: this.nextId(),
|
|
155
|
-
method: 'initialize',
|
|
156
|
-
params: {
|
|
157
|
-
protocolVersion: '2024-11-05',
|
|
158
|
-
capabilities: {},
|
|
159
|
-
clientInfo: { name: 'stacks-ai', version: '1.0.0' },
|
|
160
|
-
},
|
|
161
|
-
}),
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
if (!response.ok) {
|
|
165
|
-
const error = await response.text()
|
|
166
|
-
throw new Error(`MCP HTTP connection error: ${error}`)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
await this.discoverCapabilities()
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
private async readStdout(): Promise<void> {
|
|
173
|
-
if (!this.process) return
|
|
174
|
-
|
|
175
|
-
const reader = (this.process as any).stdout?.getReader()
|
|
176
|
-
if (!reader) return
|
|
177
|
-
|
|
178
|
-
const decoder = new TextDecoder()
|
|
179
|
-
|
|
180
|
-
try {
|
|
181
|
-
while (true) {
|
|
182
|
-
const { done, value } = await reader.read()
|
|
183
|
-
if (done) break
|
|
184
|
-
|
|
185
|
-
this.buffer += decoder.decode(value, { stream: true })
|
|
186
|
-
this.processBuffer()
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
catch {
|
|
190
|
-
// Process exited
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
private processBuffer(): void {
|
|
195
|
-
const lines = this.buffer.split('\n')
|
|
196
|
-
this.buffer = lines.pop() || ''
|
|
197
|
-
|
|
198
|
-
for (const line of lines) {
|
|
199
|
-
if (!line.trim()) continue
|
|
200
|
-
|
|
201
|
-
try {
|
|
202
|
-
const message = JSON.parse(line)
|
|
203
|
-
if (message.id !== undefined && this.pendingRequests.has(message.id)) {
|
|
204
|
-
const pending = this.pendingRequests.get(message.id)!
|
|
205
|
-
this.pendingRequests.delete(message.id)
|
|
206
|
-
|
|
207
|
-
if (message.error) {
|
|
208
|
-
pending.reject(new Error(message.error.message || 'MCP error'))
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
pending.resolve(message.result)
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
catch {
|
|
216
|
-
// Skip invalid JSON
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
private nextId(): number {
|
|
222
|
-
return ++this.requestId
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
private async sendRequest(method: string, params: Record<string, unknown> = {}): Promise<any> {
|
|
226
|
-
const id = this.nextId()
|
|
227
|
-
|
|
228
|
-
const message = JSON.stringify({
|
|
229
|
-
jsonrpc: '2.0',
|
|
230
|
-
id,
|
|
231
|
-
method,
|
|
232
|
-
params,
|
|
233
|
-
}) + '\n'
|
|
234
|
-
|
|
235
|
-
if (this.config.transport.type === 'stdio' && this.process) {
|
|
236
|
-
const writer = (this.process as any).stdin?.getWriter()
|
|
237
|
-
if (writer) {
|
|
238
|
-
await writer.write(new TextEncoder().encode(message))
|
|
239
|
-
writer.releaseLock()
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return new Promise((resolve, reject) => {
|
|
243
|
-
const timeout = setTimeout(() => {
|
|
244
|
-
this.pendingRequests.delete(id)
|
|
245
|
-
reject(new Error(`MCP request timeout: ${method}`))
|
|
246
|
-
}, 30000)
|
|
247
|
-
|
|
248
|
-
this.pendingRequests.set(id, {
|
|
249
|
-
resolve: (value: any) => {
|
|
250
|
-
clearTimeout(timeout)
|
|
251
|
-
resolve(value)
|
|
252
|
-
},
|
|
253
|
-
reject: (error: Error) => {
|
|
254
|
-
clearTimeout(timeout)
|
|
255
|
-
reject(error)
|
|
256
|
-
},
|
|
257
|
-
})
|
|
258
|
-
})
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
if (this.config.transport.type === 'sse' || this.config.transport.type === 'streamable-http') {
|
|
262
|
-
const transport = this.config.transport as { url: string; headers?: Record<string, string> }
|
|
263
|
-
|
|
264
|
-
const response = await fetch(transport.url, {
|
|
265
|
-
method: 'POST',
|
|
266
|
-
headers: {
|
|
267
|
-
'Content-Type': 'application/json',
|
|
268
|
-
...transport.headers,
|
|
269
|
-
},
|
|
270
|
-
body: message,
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
if (!response.ok) {
|
|
274
|
-
const error = await response.text()
|
|
275
|
-
throw new Error(`MCP request error: ${error}`)
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const result = (await response.json()) as any
|
|
279
|
-
if (result.error) {
|
|
280
|
-
throw new Error(result.error.message || 'MCP error')
|
|
281
|
-
}
|
|
282
|
-
return result.result
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
throw new Error('No transport available')
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
private async sendNotification(method: string, params: Record<string, unknown> = {}): Promise<void> {
|
|
289
|
-
const message = JSON.stringify({
|
|
290
|
-
jsonrpc: '2.0',
|
|
291
|
-
method,
|
|
292
|
-
params,
|
|
293
|
-
}) + '\n'
|
|
294
|
-
|
|
295
|
-
if (this.config.transport.type === 'stdio' && this.process) {
|
|
296
|
-
const writer = (this.process as any).stdin?.getWriter()
|
|
297
|
-
if (writer) {
|
|
298
|
-
await writer.write(new TextEncoder().encode(message))
|
|
299
|
-
writer.releaseLock()
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
private async discoverCapabilities(): Promise<void> {
|
|
305
|
-
const capabilities = this.config.capabilities || { tools: true, resources: true, prompts: true }
|
|
306
|
-
|
|
307
|
-
if (capabilities.tools !== false) {
|
|
308
|
-
try {
|
|
309
|
-
const result = await this.sendRequest('tools/list')
|
|
310
|
-
this.tools = result?.tools || []
|
|
311
|
-
}
|
|
312
|
-
catch {
|
|
313
|
-
this.tools = []
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (capabilities.resources !== false) {
|
|
318
|
-
try {
|
|
319
|
-
const result = await this.sendRequest('resources/list')
|
|
320
|
-
this.resources = result?.resources || []
|
|
321
|
-
}
|
|
322
|
-
catch {
|
|
323
|
-
this.resources = []
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
if (capabilities.prompts !== false) {
|
|
328
|
-
try {
|
|
329
|
-
const result = await this.sendRequest('prompts/list')
|
|
330
|
-
this.prompts = result?.prompts || []
|
|
331
|
-
}
|
|
332
|
-
catch {
|
|
333
|
-
this.prompts = []
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// ========================================================================
|
|
339
|
-
// Public API
|
|
340
|
-
// ========================================================================
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
* List available tools from the MCP server.
|
|
344
|
-
*/
|
|
345
|
-
listTools(): MCPTool[] {
|
|
346
|
-
return this.tools
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* List available resources from the MCP server.
|
|
351
|
-
*/
|
|
352
|
-
listResources(): MCPResource[] {
|
|
353
|
-
return this.resources
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* List available prompts from the MCP server.
|
|
358
|
-
*/
|
|
359
|
-
listPrompts(): MCPPrompt[] {
|
|
360
|
-
return this.prompts
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Call a tool on the MCP server.
|
|
365
|
-
*/
|
|
366
|
-
async callTool(name: string, args: Record<string, unknown> = {}): Promise<MCPToolCallResult> {
|
|
367
|
-
if (!this.initialized) {
|
|
368
|
-
throw new Error('MCP client not connected. Call connect() first.')
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
const result = await this.sendRequest('tools/call', { name, arguments: args })
|
|
372
|
-
return result as MCPToolCallResult
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* Read a resource from the MCP server.
|
|
377
|
-
*/
|
|
378
|
-
async readResource(uri: string): Promise<MCPResourceContent> {
|
|
379
|
-
if (!this.initialized) {
|
|
380
|
-
throw new Error('MCP client not connected. Call connect() first.')
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
const result = await this.sendRequest('resources/read', { uri })
|
|
384
|
-
const contents = result?.contents?.[0]
|
|
385
|
-
return {
|
|
386
|
-
uri: contents?.uri || uri,
|
|
387
|
-
mimeType: contents?.mimeType,
|
|
388
|
-
text: contents?.text,
|
|
389
|
-
blob: contents?.blob,
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* Get a prompt from the MCP server with arguments filled in.
|
|
395
|
-
*/
|
|
396
|
-
async getPrompt(name: string, args: Record<string, string> = {}): Promise<{
|
|
397
|
-
description?: string
|
|
398
|
-
messages: Array<{ role: string; content: { type: string; text: string } }>
|
|
399
|
-
}> {
|
|
400
|
-
if (!this.initialized) {
|
|
401
|
-
throw new Error('MCP client not connected. Call connect() first.')
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
return this.sendRequest('prompts/get', { name, arguments: args })
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* Convert MCP tools to the format expected by AI providers (e.g., Anthropic tool_use).
|
|
409
|
-
*/
|
|
410
|
-
toAnthropicTools(): Array<{ name: string; description: string; input_schema: Record<string, unknown> }> {
|
|
411
|
-
return this.tools.map(tool => ({
|
|
412
|
-
name: tool.name,
|
|
413
|
-
description: tool.description,
|
|
414
|
-
input_schema: tool.inputSchema,
|
|
415
|
-
}))
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
/**
|
|
419
|
-
* Convert MCP tools to OpenAI function calling format.
|
|
420
|
-
*/
|
|
421
|
-
toOpenAITools(): Array<{ type: 'function'; function: { name: string; description: string; parameters: Record<string, unknown> } }> {
|
|
422
|
-
return this.tools.map(tool => ({
|
|
423
|
-
type: 'function' as const,
|
|
424
|
-
function: {
|
|
425
|
-
name: tool.name,
|
|
426
|
-
description: tool.description,
|
|
427
|
-
parameters: tool.inputSchema,
|
|
428
|
-
},
|
|
429
|
-
}))
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* Disconnect from the MCP server.
|
|
434
|
-
*
|
|
435
|
-
* Reject every still-pending request before clearing them — otherwise
|
|
436
|
-
* callers that `await client.callTool(...)` past a disconnect hang
|
|
437
|
-
* forever waiting for a response that will never arrive (the timeout
|
|
438
|
-
* inside sendRequest would have fired, but only if disconnect was
|
|
439
|
-
* called less than 30s into a request).
|
|
440
|
-
*/
|
|
441
|
-
async disconnect(): Promise<void> {
|
|
442
|
-
if (this.process) {
|
|
443
|
-
(this.process as any).kill()
|
|
444
|
-
this.process = null
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
for (const pending of this.pendingRequests.values()) {
|
|
448
|
-
try { pending.reject(new Error('MCP client disconnected')) } catch { /* ignore */ }
|
|
449
|
-
}
|
|
450
|
-
this.pendingRequests.clear()
|
|
451
|
-
|
|
452
|
-
this.initialized = false
|
|
453
|
-
this.tools = []
|
|
454
|
-
this.resources = []
|
|
455
|
-
this.prompts = []
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
/**
|
|
459
|
-
* Check if the client is connected.
|
|
460
|
-
*/
|
|
461
|
-
get isConnected(): boolean {
|
|
462
|
-
return this.initialized
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
/**
|
|
466
|
-
* Get the server name.
|
|
467
|
-
*/
|
|
468
|
-
get serverName(): string {
|
|
469
|
-
return this.config.name
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// ============================================================================
|
|
474
|
-
// MCP Manager (Multi-Server)
|
|
475
|
-
// ============================================================================
|
|
476
|
-
|
|
477
|
-
/**
|
|
478
|
-
* Manages connections to multiple MCP servers.
|
|
479
|
-
*/
|
|
480
|
-
export class MCPManager {
|
|
481
|
-
private clients = new Map<string, MCPClient>()
|
|
482
|
-
|
|
483
|
-
/**
|
|
484
|
-
* Add and connect to an MCP server.
|
|
485
|
-
*/
|
|
486
|
-
async addServer(config: MCPServerConfig): Promise<MCPClient> {
|
|
487
|
-
if (this.clients.has(config.name)) {
|
|
488
|
-
throw new Error(`MCP server already registered: ${config.name}`)
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
const client = new MCPClient(config)
|
|
492
|
-
await client.connect()
|
|
493
|
-
this.clients.set(config.name, client)
|
|
494
|
-
return client
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* Remove and disconnect from an MCP server.
|
|
499
|
-
*/
|
|
500
|
-
async removeServer(name: string): Promise<void> {
|
|
501
|
-
const client = this.clients.get(name)
|
|
502
|
-
if (client) {
|
|
503
|
-
await client.disconnect()
|
|
504
|
-
this.clients.delete(name)
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
/**
|
|
509
|
-
* Get a connected MCP client by server name.
|
|
510
|
-
*/
|
|
511
|
-
getServer(name: string): MCPClient | undefined {
|
|
512
|
-
return this.clients.get(name)
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
/**
|
|
516
|
-
* List all connected server names.
|
|
517
|
-
*/
|
|
518
|
-
listServers(): string[] {
|
|
519
|
-
return Array.from(this.clients.keys())
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
/**
|
|
523
|
-
* Get all tools from all connected servers.
|
|
524
|
-
* Tool names are prefixed with the server name to avoid conflicts.
|
|
525
|
-
*/
|
|
526
|
-
getAllTools(): Array<MCPTool & { serverName: string }> {
|
|
527
|
-
const allTools: Array<MCPTool & { serverName: string }> = []
|
|
528
|
-
|
|
529
|
-
for (const [name, client] of this.clients) {
|
|
530
|
-
for (const tool of client.listTools()) {
|
|
531
|
-
allTools.push({ ...tool, serverName: name })
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
return allTools
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
/**
|
|
539
|
-
* Call a tool, routing to the correct server.
|
|
540
|
-
* Tool name format: "serverName/toolName" or just "toolName" if unique.
|
|
541
|
-
*/
|
|
542
|
-
async callTool(toolPath: string, args: Record<string, unknown> = {}): Promise<MCPToolCallResult> {
|
|
543
|
-
if (toolPath.includes('/')) {
|
|
544
|
-
const [serverName, toolName] = toolPath.split('/', 2)
|
|
545
|
-
const client = this.clients.get(serverName)
|
|
546
|
-
if (!client) throw new Error(`MCP server not found: ${serverName}`)
|
|
547
|
-
return client.callTool(toolName, args)
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
// Search all servers for the tool
|
|
551
|
-
for (const [, client] of this.clients) {
|
|
552
|
-
const tools = client.listTools()
|
|
553
|
-
if (tools.some(t => t.name === toolPath)) {
|
|
554
|
-
return client.callTool(toolPath, args)
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
throw new Error(`MCP tool not found: ${toolPath}`)
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
/**
|
|
562
|
-
* Convert all tools to Anthropic format with server-prefixed names.
|
|
563
|
-
*/
|
|
564
|
-
toAnthropicTools(): Array<{ name: string; description: string; input_schema: Record<string, unknown> }> {
|
|
565
|
-
const tools: Array<{ name: string; description: string; input_schema: Record<string, unknown> }> = []
|
|
566
|
-
|
|
567
|
-
for (const [serverName, client] of this.clients) {
|
|
568
|
-
for (const tool of client.listTools()) {
|
|
569
|
-
tools.push({
|
|
570
|
-
name: `${serverName}__${tool.name}`,
|
|
571
|
-
description: `[${serverName}] ${tool.description}`,
|
|
572
|
-
input_schema: tool.inputSchema,
|
|
573
|
-
})
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
return tools
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
/**
|
|
581
|
-
* Convert all tools to OpenAI format with server-prefixed names.
|
|
582
|
-
*/
|
|
583
|
-
toOpenAITools(): Array<{ type: 'function'; function: { name: string; description: string; parameters: Record<string, unknown> } }> {
|
|
584
|
-
const tools: Array<{ type: 'function'; function: { name: string; description: string; parameters: Record<string, unknown> } }> = []
|
|
585
|
-
|
|
586
|
-
for (const [serverName, client] of this.clients) {
|
|
587
|
-
for (const tool of client.listTools()) {
|
|
588
|
-
tools.push({
|
|
589
|
-
type: 'function',
|
|
590
|
-
function: {
|
|
591
|
-
name: `${serverName}__${tool.name}`,
|
|
592
|
-
description: `[${serverName}] ${tool.description}`,
|
|
593
|
-
parameters: tool.inputSchema,
|
|
594
|
-
},
|
|
595
|
-
})
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
return tools
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
/**
|
|
603
|
-
* Disconnect from all servers.
|
|
604
|
-
*/
|
|
605
|
-
async disconnectAll(): Promise<void> {
|
|
606
|
-
const disconnects = Array.from(this.clients.values()).map(c => c.disconnect())
|
|
607
|
-
await Promise.all(disconnects)
|
|
608
|
-
this.clients.clear()
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
// ============================================================================
|
|
613
|
-
// Convenience Functions
|
|
614
|
-
// ============================================================================
|
|
615
|
-
|
|
616
|
-
/**
|
|
617
|
-
* Create and connect to an MCP server using stdio transport.
|
|
618
|
-
*/
|
|
619
|
-
export async function connectStdio(
|
|
620
|
-
name: string,
|
|
621
|
-
command: string,
|
|
622
|
-
args?: string[],
|
|
623
|
-
env?: Record<string, string>,
|
|
624
|
-
): Promise<MCPClient> {
|
|
625
|
-
const client = new MCPClient({
|
|
626
|
-
name,
|
|
627
|
-
transport: { type: 'stdio', command, args, env },
|
|
628
|
-
})
|
|
629
|
-
await client.connect()
|
|
630
|
-
return client
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
/**
|
|
634
|
-
* Create and connect to an MCP server using HTTP transport.
|
|
635
|
-
*/
|
|
636
|
-
export async function connectHTTP(
|
|
637
|
-
name: string,
|
|
638
|
-
url: string,
|
|
639
|
-
headers?: Record<string, string>,
|
|
640
|
-
): Promise<MCPClient> {
|
|
641
|
-
const client = new MCPClient({
|
|
642
|
-
name,
|
|
643
|
-
transport: { type: 'streamable-http', url, headers },
|
|
644
|
-
})
|
|
645
|
-
await client.connect()
|
|
646
|
-
return client
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
// ============================================================================
|
|
650
|
-
// Exports
|
|
651
|
-
// ============================================================================
|
|
652
|
-
|
|
653
|
-
export const mcp = {
|
|
654
|
-
MCPClient,
|
|
655
|
-
MCPManager,
|
|
656
|
-
connectStdio,
|
|
657
|
-
connectHTTP,
|
|
658
|
-
}
|