Skip to main content
Glama
mcp-client.ts3.77 kB
import path from 'node:path'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; type Mode = 'dev' | 'full'; export interface MCPClientOptions { mode: Mode; command?: string; args?: string[]; env?: NodeJS.ProcessEnv; } export interface ToolCallDefinition { tool: string; args?: Record<string, unknown>; } export interface ToolCallStepResult { index: number; tool: string; args: Record<string, unknown>; ok: boolean; result?: unknown; error?: string; } export interface ToolCallBatchResult { results: ToolCallStepResult[]; completed: boolean; failureIndex?: number; } export class MCPTestClient { private client: Client; private transport: StdioClientTransport; private mode: Mode; constructor(options: MCPClientOptions) { this.mode = options.mode; const command = options.command ?? process.execPath; // node executable const tsxCli = path.resolve(process.cwd(), 'node_modules/tsx/dist/cli.cjs'); const serverEntry = path.resolve(process.cwd(), 'src/index.ts'); const args = options.args ?? [tsxCli, serverEntry]; const baseEnv: Record<string, string> = {}; for (const [k, v] of Object.entries({ ...process.env, ...options.env })) { if (typeof v === 'string') baseEnv[k] = v; } baseEnv['MCP_MODE'] = options.mode; this.transport = new StdioClientTransport({ command, args, env: baseEnv }); this.client = new Client( { name: 'mcp-e2e-client', version: '0.1.0' }, { capabilities: { tools: {} }, } ); } async connect(): Promise<void> { await this.client.connect(this.transport); } async listTools(): Promise<string[]> { const res = await this.client.listTools({}); return (res.tools ?? []).map((t) => t.name); } async callTool<T = unknown>(name: string, args: Record<string, unknown> = {}): Promise<T> { const res: { content?: Array<{ type: string; text?: string }> } = (await this.client.callTool({ name, arguments: args, })) as { content?: Array<{ type: string; text?: string }> }; const first = res.content?.[0]; if (first != null && first.type === 'text' && typeof first.text === 'string') { try { return JSON.parse(first.text) as T; } catch { return first.text as unknown as T; } } return {} as T; } async callToolsBatch(steps: ToolCallDefinition[]): Promise<ToolCallBatchResult> { const results: ToolCallStepResult[] = []; let failureIndex: number | undefined; for (let i = 0; i < steps.length; i++) { const step = steps[i]!; const args = step.args ?? {}; try { const result = await this.callTool(step.tool, args); const ok = this.isSuccessfulResult(result); results.push({ index: i, tool: step.tool, args, ok, result }); if (!ok) { failureIndex = i; break; } } catch (error) { const message = error instanceof Error ? error.message : String(error); results.push({ index: i, tool: step.tool, args, ok: false, error: message }); failureIndex = i; break; } } return { results, completed: failureIndex === undefined && results.length === steps.length, failureIndex, }; } async close(): Promise<void> { await this.transport.close(); } getMode(): Mode { return this.mode; } private isSuccessfulResult(result: unknown): boolean { if ( typeof result === 'object' && result !== null && 'success' in (result as Record<string, unknown>) ) { return Boolean((result as { success?: unknown }).success); } return true; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Daghis/teamcity-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server