Add support for in-memory MCP servers
- Add InMemoryTransport support for local servers running in the same process - Update MCPConnectionManager to pass full connection object in callbacks for better context - Filter out local servers from persistence (they auto-connect on startup) - Add 'In-Memory' badge in UI to distinguish local servers - Fix connection state management to properly update connectionsRef - Auto-connect to available in-memory servers on startup This enables testing MCP functionality without external servers by running servers directly in the browser using the InMemoryTransport from the SDK.
This commit is contained in:
parent
c9feacd763
commit
e53be9ec42
3 changed files with 136 additions and 28 deletions
|
|
@ -3,6 +3,7 @@
|
|||
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
||||
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
||||
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
|
||||
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
||||
import {
|
||||
auth,
|
||||
|
|
@ -235,7 +236,7 @@ export class MCPConnectionManager {
|
|||
private healthCheckInterval?: NodeJS.Timeout;
|
||||
private oauthProvider?: MCPOAuthProvider;
|
||||
private onConnectionUpdate?: () => void;
|
||||
private onMessage?: (connectionId: string, client: any, message: any, direction: 'sent' | 'received', extra?: any) => void;
|
||||
private onMessage?: (connection: MCPConnection, client: any, message: any, direction: 'sent' | 'received', extra?: any) => void;
|
||||
|
||||
constructor(id: string, config: MCPServerConfig) {
|
||||
this.connection = {
|
||||
|
|
@ -265,7 +266,7 @@ export class MCPConnectionManager {
|
|||
}
|
||||
|
||||
// Set callback for message monitoring
|
||||
setMessageCallback(callback: (connectionId: string, client: any, message: any, direction: 'sent' | 'received', extra?: any) => void): void {
|
||||
setMessageCallback(callback: (connection: MCPConnection, client: any, message: any, direction: 'sent' | 'received', extra?: any) => void): void {
|
||||
this.onMessage = callback;
|
||||
}
|
||||
|
||||
|
|
@ -286,14 +287,20 @@ export class MCPConnectionManager {
|
|||
// Clear any existing connections
|
||||
await this.disconnect();
|
||||
|
||||
try {
|
||||
await this.tryStreamableHttp();
|
||||
this.connection.transport = 'streamable-http';
|
||||
} catch (error) {
|
||||
// TODO: jerome - if this is a TypeError: failed to fetch, then there is likely a CORS (or
|
||||
// Access-Control-Expose-Headers) issue with the server.
|
||||
await this.trySSE();
|
||||
this.connection.transport = 'sse';
|
||||
// Check if this is a local server
|
||||
if (this.connection.url === 'local') {
|
||||
await this.tryInMemory();
|
||||
this.connection.transport = 'inmemory';
|
||||
} else {
|
||||
try {
|
||||
await this.tryStreamableHttp();
|
||||
this.connection.transport = 'streamable-http';
|
||||
} catch (error) {
|
||||
// TODO: jerome - if this is a TypeError: failed to fetch, then there is likely a CORS (or
|
||||
// Access-Control-Expose-Headers) issue with the server.
|
||||
await this.trySSE();
|
||||
this.connection.transport = 'sse';
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize client capabilities
|
||||
|
|
@ -435,6 +442,27 @@ export class MCPConnectionManager {
|
|||
}
|
||||
}
|
||||
|
||||
private async tryInMemory(): Promise<void> {
|
||||
try {
|
||||
if (!this.connection.config.localServer) {
|
||||
throw new Error('Local server function not provided');
|
||||
}
|
||||
|
||||
// Create linked transport pair
|
||||
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
|
||||
// Create and connect the server
|
||||
const server = this.connection.config.localServer();
|
||||
await server.connect(serverTransport);
|
||||
|
||||
// Initialize the client with its transport
|
||||
await this.initializeClient(clientTransport);
|
||||
} catch (error) {
|
||||
console.log('InMemory connection failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async initializeClient(transport: Transport): Promise<void> {
|
||||
try {
|
||||
const debugTransport = new DebugTransport(transport);
|
||||
|
|
@ -453,13 +481,13 @@ export class MCPConnectionManager {
|
|||
// Set up message callbacks to broadcast to UI after client is created
|
||||
debugTransport.onsendmessage_ = async (message, options) => {
|
||||
if (this.onMessage && this.client) {
|
||||
this.onMessage(this.connection.id, this.client, message, 'sent', { options });
|
||||
this.onMessage(this.connection, this.client, message, 'sent', { options });
|
||||
}
|
||||
};
|
||||
|
||||
debugTransport.onreceivemessage_ = (message, extra) => {
|
||||
if (this.onMessage && this.client) {
|
||||
this.onMessage(this.connection.id, this.client, message, 'received', extra);
|
||||
this.onMessage(this.connection, this.client, message, 'received', extra);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
10
src/mcp/servers/index.ts
Normal file
10
src/mcp/servers/index.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { createServer as createTestServer } from "./test";
|
||||
import type { MCPServerConfig } from "@/types/mcp";
|
||||
|
||||
export const availableServers: MCPServerConfig[] = [
|
||||
{
|
||||
name: "In-Memory Test Server",
|
||||
url: "local",
|
||||
localServer: createTestServer
|
||||
}
|
||||
];
|
||||
24
src/mcp/servers/test/index.ts
Normal file
24
src/mcp/servers/test/index.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
|
||||
|
||||
export function createServer() {
|
||||
// Create an MCP server
|
||||
const server = new McpServer({
|
||||
name: "demo-server",
|
||||
version: "1.0.0"
|
||||
});
|
||||
|
||||
// Add an addition tool
|
||||
server.registerTool("add",
|
||||
{
|
||||
title: "Addition Tool",
|
||||
description: "Add two numbers",
|
||||
inputSchema: { a: z.number(), b: z.number() },
|
||||
},
|
||||
async ({ a, b }) => ({
|
||||
content: [{ type: "text", text: String(a + b) }]
|
||||
})
|
||||
);
|
||||
return server;
|
||||
}
|
||||
Loading…
Reference in a new issue