Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,21 @@ Example config structure:
"transport": {
"command": "server2-command",
"args": ["--option1", "value1"],
"envs": {
"SECRET_API_KEY": "SECRET_API_VALUE"
}
}
},
{
"name": "Server 3",
"transport": {
"command": "server3-command",
"args": ["--option1", "value1"],
"env": ["SECRET_API_KEY"]
}
},
{
"name": "Example Server 3",
"name": "Example Server 4",
"transport": {
"type": "sse",
"url": "http://localhost:8080/sse"
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
{
"name": "mcp-proxy-server",
"version": "0.1.0",
"version": "0.1.2",
"author": "Adam Wattis",
"license": "MIT",
"description": "An MCP proxy server that aggregates and serves multiple MCP resource servers through a single interface",
"private": true,
"type": "module",
"bin": {
"mcp-proxy-server": "./build/index.js"
Expand Down
110 changes: 63 additions & 47 deletions src/client.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,87 @@
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
import { ServerConfig } from './config.js';

const sleep = (time: number) => new Promise<void>(resolve => setTimeout(() => resolve(), time))
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
import { ServerConfig } from "./config.js";

const sleep = (time: number) =>
new Promise<void>((resolve) => setTimeout(() => resolve(), time));
export interface ConnectedClient {
client: Client;
cleanup: () => Promise<void>;
name: string;
}

const createClient = (server: ServerConfig): { client: Client | undefined, transport: Transport | undefined } => {

let transport: Transport | null = null
const createClient = (
server: ServerConfig
): { client: Client | undefined; transport: Transport | undefined } => {
let transport: Transport | null = null;
try {
if (server.transport.type === 'sse') {
if (server.transport.type === "sse") {
transport = new SSEClientTransport(new URL(server.transport.url));
} else {
transport = new StdioClientTransport({
command: server.transport.command,
args: server.transport.args,
env: server.transport.env ? server.transport.env.reduce((o, v) => ({
[v]: process.env[v] || ''
}), {}) : undefined
env: server.transport.env
? server.transport.env.reduce(
(o, v) => ({
[v]: process.env[v] || "",
...o,
}),
server.transport.envs ?? {}
)
: undefined,
});
}
} catch (error) {
console.error(`Failed to create transport ${server.transport.type || 'stdio'} to ${server.name}:`, error);
console.error(
`Failed to create transport ${server.transport.type || "stdio"} to ${
server.name
}:`,
error
);
}

if (!transport) {
console.warn(`Transport ${server.name} not available.`)
return { transport: undefined, client: undefined }
console.warn(`Transport ${server.name} not available.`);
return { transport: undefined, client: undefined };
}

const client = new Client({
name: 'mcp-proxy-client',
version: '1.0.0',
}, {
capabilities: {
prompts: {},
resources: { subscribe: true },
tools: {}
const client = new Client(
{
name: "mcp-proxy-client",
version: "1.0.0",
},
{
capabilities: {
prompts: {},
resources: { subscribe: true },
tools: {},
},
}
});
);

return { client, transport }
}
return { client, transport };
};

export const createClients = async (servers: ServerConfig[]): Promise<ConnectedClient[]> => {
export const createClients = async (
servers: ServerConfig[]
): Promise<ConnectedClient[]> => {
const clients: ConnectedClient[] = [];

for (const server of servers) {
console.log(`Connecting to server: ${server.name}`);

const waitFor = 2500
const retries = 3
let count = 0
let retry = true
const waitFor = 2500;
const retries = 3;
let count = 0;
let retry = true;

while (retry) {

const { client, transport } = createClient(server)
const { client, transport } = createClient(server);
if (!client || !transport) {
break
break;
}

try {
Expand All @@ -76,26 +93,25 @@ export const createClients = async (servers: ServerConfig[]): Promise<ConnectedC
name: server.name,
cleanup: async () => {
await transport.close();
}
},
});

break

break;
} catch (error) {
console.error(`Failed to connect to ${server.name}:`, error);
count++
retry = (count < retries)
count++;
retry = count < retries;
if (retry) {
try {
await client.close()
} catch { }
console.log(`Retry connection to ${server.name} in ${waitFor}ms (${count}/${retries})`);
await sleep(waitFor)
await client.close();
} catch {}
console.log(
`Retry connection to ${server.name} in ${waitFor}ms (${count}/${retries})`
);
await sleep(waitFor);
}
}

}

}

return clients;
Expand Down
29 changes: 15 additions & 14 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { readFile } from 'fs/promises';
import { resolve } from 'path';

import { readFile } from "fs/promises";
import { resolve } from "path";

export type TransportConfigStdio = {
type?: 'stdio'
type?: "stdio";
command: string;
args?: string[];
env?: string[]
}
env?: string[];
envs?: Record<string, string>;
};

export type TransportConfigSSE = {
type: 'sse'
url: string
}
type: "sse";
url: string;
};

export type TransportConfig = TransportConfigSSE | TransportConfigStdio
export type TransportConfig = TransportConfigSSE | TransportConfigStdio;
export interface ServerConfig {
name: string;
transport: TransportConfig;
Expand All @@ -26,12 +26,13 @@ export interface Config {

export const loadConfig = async (): Promise<Config> => {
try {
const configPath = resolve(process.cwd(), 'config.json');
const fileContents = await readFile(configPath, 'utf-8');
const configPath =
process.env.MCP_CONFIG_PATH || resolve(process.cwd(), "config.json");
const fileContents = await readFile(configPath, "utf-8");
return JSON.parse(fileContents);
} catch (error) {
console.error('Error loading config.json:', error);
console.error("Error loading config.json:", error);
// Return empty config if file doesn't exist
return { servers: [] };
}
};
};