-
Notifications
You must be signed in to change notification settings - Fork 2.6k
feat(mcp): enhance server management with resource handling and trans… #15413
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
base: master
Are you sure you want to change the base?
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 | ||
---|---|---|---|---|
|
@@ -13,10 +13,13 @@ | |||
// | ||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 | ||||
// ***************************************************************************** | ||||
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio'; | ||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js'; | ||||
import { MCPServerDescription, MCPServerStatus, ToolInformation } from '../common'; | ||||
import { Emitter } from '@theia/core/lib/common/event'; | ||||
import {StdioClientTransport} from '@modelcontextprotocol/sdk/client/stdio'; | ||||
import {SSEClientTransport} from '@modelcontextprotocol/sdk/client/sse'; | ||||
import {Client} from '@modelcontextprotocol/sdk/client/index.js'; | ||||
import {MCPServerDescription, MCPServerStatus, ToolInformation} from '../common'; | ||||
import {Emitter} from '@theia/core/lib/common/event'; | ||||
|
||||
export type TransportType = 'stdio' | 'sse'; | ||||
|
||||
export class MCPServer { | ||||
private name: string; | ||||
|
@@ -31,13 +34,21 @@ export class MCPServer { | |||
// Event emitter for status updates | ||||
private readonly onDidUpdateStatusEmitter = new Emitter<MCPServerStatus>(); | ||||
readonly onDidUpdateStatus = this.onDidUpdateStatusEmitter.event; | ||||
private readonly transportType: TransportType; | ||||
private readonly sseUrl?: string; | ||||
|
||||
constructor(description: MCPServerDescription) { | ||||
this.name = description.name; | ||||
this.command = description.command; | ||||
this.args = description.args; | ||||
this.env = description.env; | ||||
this.autostart = description.autostart; | ||||
if (this.env?.sseUrl) { | ||||
this.sseUrl = this.env.sseUrl; | ||||
this.transportType = 'sse'; | ||||
} else { | ||||
this.transportType = 'stdio'; | ||||
} | ||||
console.log(this.autostart); | ||||
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.
Suggested change
This was there before, but we could remove it. |
||||
} | ||||
|
||||
|
@@ -84,23 +95,41 @@ export class MCPServer { | |||
if (this.isRunnning() && this.status === MCPServerStatus.Starting) { | ||||
return; | ||||
} | ||||
|
||||
this.setStatus(MCPServerStatus.Starting); | ||||
console.log(`Starting server "${this.name}" with command: ${this.command} and args: ${this.args?.join(' ')} and env: ${JSON.stringify(this.env)}`); | ||||
// Filter process.env to exclude undefined values | ||||
const sanitizedEnv: Record<string, string> = Object.fromEntries( | ||||
Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined) | ||||
); | ||||
|
||||
const mergedEnv: Record<string, string> = { | ||||
...sanitizedEnv, | ||||
...(this.env || {}) | ||||
}; | ||||
const transport = new StdioClientTransport({ | ||||
command: this.command, | ||||
args: this.args, | ||||
env: mergedEnv, | ||||
}); | ||||
transport.onerror = error => { | ||||
console.log(`Starting server "${this.name}" with transport: ${this.transportType}`); | ||||
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. We log two messages before we start a server now. I think we can combine it to one covering both cases. What I mean: I would merge the content of the two messages below into the message here so that we get only one log message including all information (the URL for SSE, the command, args and env for stdio) |
||||
|
||||
let transport; | ||||
|
||||
if (this.transportType === 'sse') { | ||||
if (!this.sseUrl) { | ||||
throw new Error(`SSE transport requires a URL, but none was provided for server "${this.name}"`); | ||||
} | ||||
|
||||
console.log(`Using SSE transport with URL: ${this.sseUrl}`); | ||||
transport = new SSEClientTransport(new URL(this.sseUrl)); | ||||
} else { | ||||
// Default to stdio transport | ||||
console.log(`Using stdio transport with command: ${this.command} and args: ${this.args?.join(' ')} and env: ${JSON.stringify(this.env)}`); | ||||
|
||||
// Filter process.env to exclude undefined values | ||||
const sanitizedEnv: Record<string, string> = Object.fromEntries( | ||||
Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined) | ||||
); | ||||
|
||||
const mergedEnv: Record<string, string> = { | ||||
...sanitizedEnv, | ||||
...(this.env || {}) | ||||
}; | ||||
|
||||
transport = new StdioClientTransport({ | ||||
command: this.command, | ||||
args: this.args, | ||||
env: mergedEnv, | ||||
}); | ||||
} | ||||
|
||||
transport.onerror = (error: Error) => { | ||||
console.error('Error: ' + error); | ||||
this.error = 'Error: ' + error; | ||||
this.setStatus(MCPServerStatus.Errored); | ||||
|
@@ -112,7 +141,7 @@ export class MCPServer { | |||
}, { | ||||
capabilities: {} | ||||
}); | ||||
this.client.onerror = error => { | ||||
this.client.onerror = (error: Error) => { | ||||
console.error('Error in MCP client: ' + error); | ||||
this.error = 'Error in MCP client: ' + error; | ||||
this.setStatus(MCPServerStatus.Errored); | ||||
|
@@ -132,7 +161,7 @@ export class MCPServer { | |||
let args; | ||||
try { | ||||
args = JSON.parse(arg_string); | ||||
} catch (error) { | ||||
} catch (error: unknown) { | ||||
console.error( | ||||
`Failed to parse arguments for calling tool "${toolName}" in MCP server "${this.name}" with command "${this.command}". | ||||
Invalid JSON: ${arg_string}`, | ||||
|
@@ -166,4 +195,13 @@ export class MCPServer { | |||
this.client.close(); | ||||
this.setStatus(MCPServerStatus.NotRunning); | ||||
} | ||||
|
||||
listResources(): ReturnType<Client['listResources']> { | ||||
return this.client.listResources(); | ||||
} | ||||
|
||||
readResource(resourceId: string): ReturnType<Client['readResource']> { | ||||
const params = {uri: resourceId}; | ||||
return this.client.readResource(params); | ||||
} | ||||
} |
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.
This breaks the build