packages/mcp-client/cli.ts (152 lines of code) (raw):

#!/usr/bin/env node import * as readline from "node:readline/promises"; import { stdin, stdout } from "node:process"; import { join } from "node:path"; import { homedir } from "node:os"; import type { StdioServerParameters } from "@modelcontextprotocol/sdk/client/stdio.js"; import type { InferenceProviderOrPolicy } from "@huggingface/inference"; import { ANSI, urlToServerConfig } from "./src/utils"; import { Agent } from "./src"; import { version as packageVersion } from "./package.json"; import { parseArgs } from "node:util"; import type { ServerConfig } from "./src/types"; const MODEL_ID = process.env.MODEL_ID ?? "Qwen/Qwen2.5-72B-Instruct"; const PROVIDER = (process.env.PROVIDER as InferenceProviderOrPolicy) ?? "nebius"; const ENDPOINT_URL = process.env.ENDPOINT_URL ?? process.env.BASE_URL; const SERVERS: (ServerConfig | StdioServerParameters)[] = [ { // Filesystem "official" mcp-server with access to your Desktop command: "npx", args: [ "-y", "@modelcontextprotocol/server-filesystem", process.platform === "darwin" ? join(homedir(), "Desktop") : homedir(), ], }, { // Playwright MCP command: "npx", args: ["@playwright/mcp@latest"], }, ]; // Handle --url parameters from command line: each URL will be parsed into a ServerConfig object const { values: { url: urls }, } = parseArgs({ options: { url: { type: "string", multiple: true, }, }, }); if (urls?.length) { while (SERVERS.length) { SERVERS.pop(); } for (const url of urls) { try { SERVERS.push(urlToServerConfig(url, process.env.HF_TOKEN)); } catch (error) { console.error(`Error adding server from URL "${url}": ${error.message}`); } } } async function main() { if (process.argv.includes("--version")) { console.log(packageVersion); process.exit(0); } if (!ENDPOINT_URL && !process.env.HF_TOKEN) { console.error(`To use a remote inference provider, a valid HF_TOKEN must be present in the env`); process.exit(1); } const agent = new Agent( ENDPOINT_URL ? { endpointUrl: ENDPOINT_URL, model: MODEL_ID, apiKey: process.env.HF_TOKEN, servers: SERVERS, } : { provider: PROVIDER, model: MODEL_ID, apiKey: process.env.HF_TOKEN, servers: SERVERS, } ); const rl = readline.createInterface({ input: stdin, output: stdout }); let abortController = new AbortController(); let waitingForInput = false; async function waitForInput() { waitingForInput = true; const input = await rl.question("> "); waitingForInput = false; return input; } rl.on("SIGINT", async () => { if (waitingForInput) { // close the whole process await agent.cleanup(); stdout.write("\n"); rl.close(); } else { // otherwise, it means a request is underway abortController.abort(); abortController = new AbortController(); stdout.write("\n"); stdout.write(ANSI.GRAY); stdout.write("Ctrl+C a second time to exit"); stdout.write(ANSI.RESET); stdout.write("\n"); } }); process.on("uncaughtException", (err) => { stdout.write("\n"); rl.close(); throw err; }); await agent.loadTools(); stdout.write(ANSI.BLUE); stdout.write(`Agent loaded with ${agent.availableTools.length} tools:\n`); stdout.write(agent.availableTools.map((t) => `- ${t.function.name}`).join("\n")); stdout.write(ANSI.RESET); stdout.write("\n"); while (true) { const input = await waitForInput(); for await (const chunk of agent.run(input, { abortSignal: abortController.signal })) { if ("choices" in chunk) { const delta = chunk.choices[0]?.delta; if (delta.content) { stdout.write(delta.content); } if (delta.tool_calls) { stdout.write(ANSI.GRAY); for (const deltaToolCall of delta.tool_calls) { if (deltaToolCall.id) { stdout.write(`<Tool ${deltaToolCall.id}>\n`); } if (deltaToolCall.function.name) { stdout.write(deltaToolCall.function.name + " "); } if (deltaToolCall.function.arguments) { stdout.write(deltaToolCall.function.arguments); } } stdout.write(ANSI.RESET); } } else { /// Tool call info stdout.write("\n\n"); stdout.write(ANSI.GREEN); stdout.write(`Tool[${chunk.name}] ${chunk.tool_call_id}\n`); stdout.write(chunk.content); stdout.write(ANSI.RESET); stdout.write("\n\n"); } } stdout.write("\n"); } } main();