Skip to main content
Glama
index.ts6.62 kB
#!/usr/bin/env node import type { ToolCallDefinition } from './mcp-client'; import { MCPTestClient } from './mcp-client'; type Mode = 'dev' | 'full' | 'both'; interface CLIArgs { mode: Mode; cmd: 'smoke' | 'tools' | 'call' | 'batch'; tool?: string; json?: string; } function parseArgs(argv: string[]): CLIArgs { const args = argv.slice(2); let mode: Mode = 'both'; let cmd: CLIArgs['cmd'] = 'smoke'; let tool: string | undefined; let json: string | undefined; for (let i = 0; i < args.length; i++) { const a = args[i]; if (a === '--mode' && i + 1 < args.length) { const m = args[++i] as Mode; mode = m; } else if (a === 'smoke' || a === 'tools' || a === 'call' || a === 'batch') { cmd = a; } else if (!tool) { tool = a; } else if (!json) { json = a; } } return { mode, cmd, tool, json }; } async function runTools(mode: Exclude<Mode, 'both'>): Promise<void> { const client = new MCPTestClient({ mode }); await client.connect(); const tools = await client.listTools(); console.log(JSON.stringify({ mode, tools })); await client.close(); } async function runCall(mode: Exclude<Mode, 'both'>, tool: string, json?: string): Promise<void> { const client = new MCPTestClient({ mode }); await client.connect(); let args: Record<string, unknown> = {}; if (json) { try { args = JSON.parse(json); } catch (e) { console.error('Invalid JSON for args:', e); process.exit(2); } } try { const res = await client.callTool(tool, args); console.log(JSON.stringify({ mode, tool, args, res })); } catch (e) { console.error(JSON.stringify({ mode, tool, args, error: String(e) }, null, 2)); throw e; } finally { await client.close(); } } async function runBatch(mode: Exclude<Mode, 'both'>, payload?: string): Promise<void> { if (!payload) { console.error( 'Usage: e2e batch "[ { \"tool\": \"name\", \"args\": {...} } ]" [--mode dev|full]' ); process.exit(2); } let parsed: unknown; try { parsed = JSON.parse(payload) as unknown; } catch (e) { console.error('Invalid JSON array for batch payload:', e); process.exit(2); } if (!Array.isArray(parsed)) { console.error('Batch payload must be a JSON array of tool call definitions.'); process.exit(2); } const steps: ToolCallDefinition[] = parsed.map((step, index) => { if (!step || typeof step !== 'object') { console.error(`Invalid step at index ${index}: expected object.`); process.exit(2); } const { tool, args } = step as { tool?: unknown; args?: unknown }; if (typeof tool !== 'string' || tool.length === 0) { console.error(`Invalid step at index ${index}: missing tool name.`); process.exit(2); } if (args !== undefined && (typeof args !== 'object' || args === null || Array.isArray(args))) { console.error(`Invalid step at index ${index}: args must be an object if provided.`); process.exit(2); } return { tool, args: (args as Record<string, unknown>) ?? {}, } satisfies ToolCallDefinition; }); const client = new MCPTestClient({ mode }); await client.connect(); try { const result = await client.callToolsBatch(steps); console.log(JSON.stringify({ mode, ...result })); } catch (e) { console.error(JSON.stringify({ mode, error: String(e) })); throw e; } finally { await client.close(); } } async function runSmoke(mode: Exclude<Mode, 'both'>): Promise<void> { const client = new MCPTestClient({ mode }); await client.connect(); const tools = await client.listTools(); const isDev = mode === 'dev'; const pong = await client.callTool<string>('ping', { message: 'e2e' }); console.log(JSON.stringify({ mode, step: 'ping', pong })); if (isDev) { const shouldNotExist = [ 'create_project', 'update_project_settings', 'delete_project', 'create_build_config', 'clone_build_config', 'update_build_config', 'set_build_configs_paused', 'manage_build_steps', 'manage_build_triggers', 'add_parameter', 'update_parameter', 'delete_parameter', 'create_vcs_root', 'add_vcs_root_to_build', 'authorize_agent', 'assign_agent_to_pool', 'set_agent_enabled', 'bulk_set_agents_enabled', 'move_queued_build_to_top', 'reorder_queued_builds', 'cancel_queued_builds_for_build_type', 'cancel_queued_builds_by_locator', 'pause_queue_for_pool', 'resume_queue_for_pool', 'get_server_metrics', 'list_server_health_items', 'get_server_health_item', ]; for (const name of shouldNotExist) { const exists = tools.includes(name); console.log(JSON.stringify({ mode, step: 'tool_visibility', tool: name, exists })); } } else { const ts = Date.now(); const projectId = `MCP_E2E_${ts}`; const projectName = `MCP E2E ${ts}`; const created = await client.callTool('create_project', { id: projectId, name: projectName, description: 'Temporary project for MCP e2e tests', }); process.stdout.write(`${JSON.stringify({ mode, step: 'create_project', created })}\n`); const listed = await client.callTool('list_projects', { locator: `id:${projectId}` }); const listedCount = typeof listed === 'object' && listed !== null && 'items' in (listed as Record<string, unknown>) ? Array.isArray((listed as { items?: unknown[] }).items) ? ((listed as { items?: unknown[] }).items as unknown[]).length : 0 : 0; process.stdout.write(`${JSON.stringify({ mode, step: 'list_projects', listedCount })}\n`); const deleted = await client.callTool('delete_project', { projectId }); process.stdout.write(`${JSON.stringify({ mode, step: 'delete_project', deleted })}\n`); } await client.close(); } async function main(): Promise<void> { const { mode, cmd, tool, json } = parseArgs(process.argv); const runFor = async (m: Exclude<Mode, 'both'>) => { if (cmd === 'tools') return runTools(m); if (cmd === 'call') { if (!tool) { console.error('Usage: e2e call <toolName> <jsonArgs> [--mode dev|full]'); process.exit(2); } return runCall(m, tool, json); } if (cmd === 'batch') { return runBatch(m, tool); } return runSmoke(m); }; if (mode === 'both') { await runFor('dev'); await runFor('full'); } else { await runFor(mode); } } main().catch((err) => { console.error(err); process.exit(1); });

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