MCP Base
by jsmiff
- client
// Generic MCP Client using the official MCP SDK
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
/**
* Generic MCPClient
* This client provides a base interface to interact with any MCP server
*/
class MCPClient {
private client: Client;
private connected: boolean = false;
private serverUrl: string;
/**
* Creates a new instance of the MCP Client
* @param options Configuration options for the client
*/
constructor(
options: {
serverUrl?: string;
serverCommand?: string;
serverArgs?: string[];
transportType?: "stdio" | "sse";
} = {}
) {
// Default options
const defaults = {
serverUrl: process.env.CLIENT_SERVER_URL || "http://localhost:3000",
transportType: "sse" as const,
serverCommand: "node",
serverArgs: ["server/server.js"],
};
const config = { ...defaults, ...options };
this.serverUrl = config.serverUrl;
// Create the MCP client
this.client = new Client(
{
name: "MCP Client",
version: "1.0.0",
},
{
capabilities: {
resources: {},
tools: {},
prompts: {},
},
}
);
// Set up connection based on transport type
if (config.transportType === "stdio") {
this.setupStdioTransport(config.serverCommand, config.serverArgs);
} else {
this.setupSSETransport(config.serverUrl);
}
}
/**
* Set up a stdio transport for the client
*/
private setupStdioTransport(command: string, args: string[]) {
const transport = new StdioClientTransport({
command,
args,
});
// Connect to the transport when needed
this.connectTransport = async () => {
if (!this.connected) {
await this.client.connect(transport);
this.connected = true;
}
};
}
/**
* Set up an SSE transport for the client
*/
private setupSSETransport(serverUrl: string) {
const sseUrl = new URL(`${serverUrl}/sse`);
const transport = new SSEClientTransport(sseUrl);
// Connect to the transport when needed
this.connectTransport = async () => {
if (!this.connected) {
await this.client.connect(transport);
this.connected = true;
}
};
}
/**
* Ensure the client is connected to the transport
* This will be called before any operation that requires connection
*/
private connectTransport!: () => Promise<void>;
/**
* Disconnect the client from the server
*/
async disconnect() {
if (this.connected) {
await this.client.close();
this.connected = false;
}
}
/**
* Call a tool on the server
* @param name Tool name
* @param args Tool arguments
* @returns Tool result
*/
async callTool(name: string, args: Record<string, any> = {}): Promise<any> {
await this.connectTransport();
try {
const result = await this.client.callTool({
name,
arguments: args,
});
// Parse the result content if it's JSON
try {
return JSON.parse((result as any).content[0].text);
} catch (e) {
// If parsing fails, return the raw text
return (result as any).content[0].text;
}
} catch (error) {
console.error(`Error calling tool ${name}:`, error);
throw error;
}
}
/**
* Read a resource from the server
* @param uri Resource URI
* @returns Resource content
*/
async readResource(uri: string): Promise<any> {
await this.connectTransport();
try {
const resource: any = await this.client.readResource({
uri,
});
// Parse the result content if it's JSON
try {
return JSON.parse(resource.contents[0].text);
} catch (e) {
// If parsing fails, return the raw text
return resource.contents[0].text;
}
} catch (error) {
console.error(`Error reading resource ${uri}:`, error);
throw error;
}
}
/**
* Get a prompt from the server
* @param name Prompt name
* @param args Prompt arguments
* @returns Prompt content
*/
async getPrompt(name: string, args: Record<string, any> = {}): Promise<any> {
await this.connectTransport();
try {
const prompt = await this.client.getPrompt({
name,
arguments: args,
});
return prompt;
} catch (error) {
console.error(`Error getting prompt ${name}:`, error);
throw error;
}
}
/**
* List all available prompts from the server
* @returns List of prompts
*/
async listPrompts(): Promise<any[]> {
await this.connectTransport();
try {
const prompts: any = await this.client.listPrompts();
return prompts;
} catch (error) {
console.error("Error listing prompts:", error);
throw error;
}
}
/**
* Check if the server is alive
* @returns True if the server is responsive
*/
async ping(): Promise<boolean> {
try {
// Use the health endpoint instead of the MCP protocol for ping
const url = new URL("/health", this.serverUrl);
const response = await fetch(url.toString());
if (!response.ok) {
throw new Error(`Server responded with status: ${response.status}`);
}
const data = await response.json();
return data.status === "ok";
} catch (error) {
console.error("Server ping failed:", error);
return false;
}
}
}
export default MCPClient;
// Helper types
type CallToolResult = {
content: {
text: string;
}[];
};