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:
Jerome 2025-06-19 16:55:47 +01:00
commit e53be9ec42
No known key found for this signature in database
3 changed files with 136 additions and 28 deletions

View file

@ -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
View 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
}
];

View 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;
}