Skip to main content
Glama
mcp-runner.ts3.6 kB
import { spawn } from 'child_process'; import path from 'path'; export type Mode = 'dev' | 'full'; export interface ToolBatchStep { tool: string; args?: Record<string, unknown>; } export interface ToolBatchStepResult { index: number; tool: string; args: Record<string, unknown>; ok: boolean; result?: unknown; error?: string; } export interface ToolBatchResult { results: ToolBatchStepResult[]; completed: boolean; failureIndex?: number; } export async function listTools(mode: Mode): Promise<string[]> { const res = (await runE2E(['tools', '--mode', mode])) as { tools?: string[] }; return res.tools ?? []; } export async function callTool<T = unknown>( mode: Mode, name: string, args: Record<string, unknown> = {} ): Promise<T> { const res = (await runE2E(['call', name, JSON.stringify(args), '--mode', mode])) as { res: T; }; return res.res; } export async function callToolsBatch(mode: Mode, steps: ToolBatchStep[]): Promise<ToolBatchResult> { const res = (await runE2E(['batch', JSON.stringify(steps), '--mode', mode])) as { results?: ToolBatchStepResult[]; completed?: boolean; failureIndex?: number; }; const results = res.results ?? []; const completed = res.completed ?? (results.length === steps.length && results.every((step) => step.ok)); return { results, completed, failureIndex: res.failureIndex }; } export async function callToolsBatchExpect( mode: Mode, steps: ToolBatchStep[] ): Promise<ToolBatchStepResult[]> { const batch = await callToolsBatch(mode, steps); if (!batch.completed) { const failureIndex = batch.failureIndex ?? batch.results.findIndex((step) => !step.ok); const failure = batch.results[failureIndex] ?? null; const errorMessage = failure?.error ?? 'unknown batch failure'; const toolName = failure?.tool ?? 'unknown_tool'; throw new Error(`Batch ${mode} call failed at #${failureIndex}: ${toolName} → ${errorMessage}`); } const failed = batch.results.find((step) => !step.ok); if (failed) { throw new Error( `Batch ${mode} call reported failure: ${failed.tool} → ${failed.error ?? 'unknown error'}` ); } return batch.results; } async function runE2E(args: string[]): Promise<unknown> { const tsx = path.resolve(process.cwd(), 'node_modules/tsx/dist/cli.cjs'); const entry = path.resolve(process.cwd(), 'tests/e2e/index.ts'); const child = spawn(process.execPath, [tsx, entry, ...args], { env: { ...process.env }, stdio: ['ignore', 'pipe', 'pipe'], }); const out: Buffer[] = []; const err: Buffer[] = []; await new Promise<void>((resolve, reject) => { child.stdout.on('data', (c) => out.push(c)); child.stderr.on('data', (c) => err.push(c)); child.on('error', reject); child.on('close', (code) => { if (code !== 0) { // eslint-disable-next-line no-console console.error(Buffer.concat(err).toString('utf8')); reject(new Error(`E2E CLI exited with ${code}`)); } else resolve(); }); }); const outStr = Buffer.concat(out).toString('utf8'); // Attempt robust parse: walk lines from bottom up and parse the first valid JSON line const lines = outStr .split(/\r?\n/) .map((l) => l.trim()) .filter(Boolean); for (let i = lines.length - 1; i >= 0; i--) { const line = lines[i] ?? ''; if (!line.startsWith('{') || !line.endsWith('}')) continue; try { return JSON.parse(line as string) as unknown; } catch { // continue } } throw new Error(`Failed to parse E2E output as JSON. Output:\n${outStr}`); }

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