Skip to main content
Glama
utcp-client-mcp.ts9.09 kB
// utcp-client-mcp.ts (TypeScript MCP-UTCP Bridge Server) import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import path from "path"; import { promises as fs } from "fs"; import { parse as parseDotEnv } from 'dotenv'; import "@utcp/http"; import "@utcp/text"; import "@utcp/mcp"; import "@utcp/cli"; import { UtcpClient, CallTemplateSchema, InMemConcurrentToolRepository, TagSearchStrategy, DefaultVariableSubstitutor, ensureCorePluginsInitialized } from "@utcp/sdk"; import type { UtcpClientConfig } from "@utcp/sdk"; ensureCorePluginsInitialized(); let utcpClient: UtcpClient | null = null; async function main() { console.log("Initializing UTCP-MCP Bridge..."); setupMcpTools(); utcpClient = await initializeUtcpClient(); const bridgeScriptPath = path.resolve(import.meta.dir, import.meta.file); const connectionConfig = { mcpServers: { "typescript-utcp-bridge": { command: "bun", args: ["run", bridgeScriptPath], } } }; console.log("\n✅ Bridge is ready. To connect, use this configuration in your MCP client's config file:"); console.log("================================ MCP CONFIG ================================"); console.log(JSON.stringify(connectionConfig, null, 2)); console.log("=========================================================================="); console.log("\nStarting MCP server on stdio..."); const transport = new StdioServerTransport(); await mcp.connect(transport); } const mcp = new McpServer({ name: "UTCP-Client-MCP-Bridge", version: "1.0.0", }); function setupMcpTools() { mcp.registerTool("register_manual", { title: "Register a UTCP Manual", description: "Registers a new tool provider by providing its call template.", inputSchema: { manual_call_template: CallTemplateSchema.describe("The call template for the UTCP Manual endpoint.") }, }, async (input) => { const client = await initializeUtcpClient(); try { const result = await client.registerManual(input.manual_call_template as any); return { content: [{ type: "text", text: JSON.stringify(result) }] }; } catch (e: any) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: e.message }) }] }; } }); mcp.registerTool("deregister_manual", { title: "Deregister a UTCP Manual", description: "Deregisters a tool provider from the UTCP client.", inputSchema: { manual_name: z.string().describe("The name of the manual to deregister.") }, }, async (input) => { const client = await initializeUtcpClient(); try { const success = await client.deregisterManual(input.manual_name); const message = success ? `Manual '${input.manual_name}' deregistered.` : `Manual '${input.manual_name}' not found.`; return { content: [{ type: "text", text: JSON.stringify({ success, message }) }] }; } catch (e: any) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: e.message }) }] }; } }); mcp.registerTool("call_tool", { title: "Call a UTCP Tool", description: "Calls a registered tool by its full namespaced name.", inputSchema: { tool_name: z.string().describe("The full name of the tool to call."), arguments: z.record(z.string(), z.any()).describe("A JSON object of arguments."), }, }, async (input) => { const client = await initializeUtcpClient(); try { const result = await client.callTool(input.tool_name, input.arguments); return { content: [{ type: "text", text: JSON.stringify({ success: true, tool_name: input.tool_name, result }) }] }; } catch (e: any) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: e.message }) }] }; } }); mcp.registerTool("search_tools", { title: "Search for UTCP Tools", description: "Searches for relevant tools based on a task description.", inputSchema: { task_description: z.string().describe("A natural language description of the task."), limit: z.number().optional().default(10), }, }, async (input) => { const client = await initializeUtcpClient(); try { const tools = await client.searchTools(input.task_description, input.limit); const simplified = tools.map(t => ({ name: t.name, description: t.description, input_schema: t.inputs })); return { content: [{ type: "text", text: JSON.stringify({ tools: simplified }) }] }; } catch (e: any) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: e.message }) }] }; } }); mcp.registerTool("list_tools", { title: "List All Registered UTCP Tools", description: "Returns a list of all tools currently registered.", inputSchema: {}, }, async () => { const client = await initializeUtcpClient(); try { const tools = await client.config.tool_repository.getTools(); const toolInfo = tools.map(t => ({ name: t.name, description: t.description })); return { content: [{ type: "text", text: JSON.stringify({ tools: toolInfo }) }] }; } catch (e: any) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: e.message }) }] }; } }); } async function initializeUtcpClient(): Promise<UtcpClient> { if (utcpClient) { return utcpClient as UtcpClient; } const scriptDir = path.resolve(import.meta.dir); const configPath = path.resolve(scriptDir, '.utcp_config.json'); console.log(`Searching for UTCP config file at: ${configPath}`); let rawConfig: any = {}; try { const configFileContent = await fs.readFile(configPath, 'utf-8'); rawConfig = JSON.parse(configFileContent); console.log("Loaded UTCP client configuration from .utcp_config.json"); } catch (e: any) { if (e.code !== 'ENOENT') { console.warn(`Could not read or parse .utcp_config.json. Error: ${e.message}`); } console.log("No valid .utcp_config.json found. Initializing with default config."); } const toolRepository = new InMemConcurrentToolRepository(); const searchStrategy = new TagSearchStrategy({ tool_search_strategy_type: 'tag_and_description_word_match', ...rawConfig.tool_search_strategy }); const variableSubstitutor = new DefaultVariableSubstitutor(); const loadedVariables: Record<string, string> = {}; if (rawConfig.load_variables_from) { for (const loaderConfig of rawConfig.load_variables_from) { if (loaderConfig.variable_loader_type === 'dotenv' && loaderConfig.env_file_path) { try { const envPath = path.resolve(scriptDir, loaderConfig.env_file_path); const envContent = await fs.readFile(envPath, 'utf-8'); Object.assign(loadedVariables, parseDotEnv(envContent)); console.log(`Successfully loaded variables from ${envPath}`); } catch (e: any) { console.warn(`Could not load .env file from '${loaderConfig.env_file_path}': ${e.message}`); } } } } const finalVariables = { ...loadedVariables, ...(rawConfig.variables || {}) }; const clientConfig: UtcpClientConfig = { variables: finalVariables, load_variables_from: [], tool_repository: toolRepository, tool_search_strategy: searchStrategy, post_processing: rawConfig.post_processing || [], manual_call_templates: [], }; const newClient = new (UtcpClient as any)(clientConfig, variableSubstitutor, scriptDir); if (rawConfig.manual_call_templates && Array.isArray(rawConfig.manual_call_templates)) { console.log(`Registering ${rawConfig.manual_call_templates.length} initial manuals from config...`); for (const manualTemplate of rawConfig.manual_call_templates) { try { if (!manualTemplate.name) { manualTemplate.name = manualTemplate.call_template_type || 'unnamed_manual'; } await newClient.registerManual(manualTemplate); } catch (e: any) { console.error(`Failed to register initial manual '${manualTemplate.name}': ${e.message}`); } } } console.log("UTCP Client manually initialized successfully."); utcpClient = newClient; return utcpClient as UtcpClient; } main().catch(err => { console.error("Failed to start UTCP-MCP Bridge:", err); process.exit(1); });

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/universal-tool-calling-protocol/utcp-mcp'

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