@copilotkit/runtime
Version:
<div align="center"> <a href="https://copilotkit.ai" target="_blank"> <img src="https://github.com/copilotkit/copilotkit/raw/main/assets/banner.png" alt="CopilotKit Logo"> </a>
189 lines (162 loc) • 6.32 kB
text/typescript
import { Action, Parameter } from "@copilotkit/shared";
/**
* Represents a tool provided by an MCP server.
*/
export interface MCPTool {
description?: string;
/** Schema defining parameters, mirroring the MCP structure. */
schema?: {
parameters?: {
properties?: Record<string, any>;
required?: string[];
jsonSchema?: Record<string, any>;
};
};
/** The function to call to execute the tool on the MCP server. */
execute(options: { params: any }): Promise<any>;
}
/**
* Defines the contract for *any* MCP client implementation the user might provide.
*/
export interface MCPClient {
/** A method that returns a map of tool names to MCPTool objects available from the connected MCP server. */
tools(): Promise<Record<string, MCPTool>>;
/** An optional method for cleanup if the underlying client requires explicit disconnection. */
close?(): Promise<void>;
}
/**
* Configuration for connecting to an MCP endpoint.
*/
export interface MCPEndpointConfig {
endpoint: string;
apiKey?: string;
}
/**
* Extracts CopilotKit-compatible parameters from an MCP tool schema.
* @param toolOrSchema The schema object from an MCPTool or the full MCPTool object.
* @returns An array of Parameter objects.
*/
export function extractParametersFromSchema(
toolOrSchema?: MCPTool | MCPTool["schema"],
): Parameter[] {
const parameters: Parameter[] = [];
// Handle either full tool object or just schema
const schema =
"schema" in (toolOrSchema || {})
? (toolOrSchema as MCPTool).schema
: (toolOrSchema as MCPTool["schema"]);
const toolParameters = schema?.parameters || schema?.parameters?.jsonSchema;
const properties = toolParameters?.properties;
const requiredParams = new Set(toolParameters?.required || []);
if (!properties) {
return parameters;
}
for (const paramName in properties) {
if (Object.prototype.hasOwnProperty.call(properties, paramName)) {
const paramDef = properties[paramName];
parameters.push({
name: paramName,
// Infer type, default to string. MCP schemas might have more complex types.
// This might need refinement based on common MCP schema practices.
type: paramDef.type || "string",
description: paramDef.description,
required: requiredParams.has(paramName),
// Attributes might not directly map, handle if necessary
// attributes: paramDef.attributes || undefined,
});
}
}
return parameters;
}
/**
* Converts a map of MCPTools into an array of CopilotKit Actions.
* @param mcpTools A record mapping tool names to MCPTool objects.
* @param mcpEndpoint The endpoint URL from which these tools were fetched.
* @returns An array of Action<any> objects.
*/
export function convertMCPToolsToActions(
mcpTools: Record<string, MCPTool>,
mcpEndpoint: string,
): Action<any>[] {
const actions: Action<any>[] = [];
for (const [toolName, tool] of Object.entries(mcpTools)) {
const parameters = extractParametersFromSchema(tool);
const handler = async (params: any): Promise<any> => {
try {
const result = await tool.execute({ params });
// Ensure the result is a string or stringify it, as required by many LLMs.
// This might need adjustment depending on how different LLMs handle tool results.
return typeof result === "string" ? result : JSON.stringify(result);
} catch (error) {
console.error(
`Error executing MCP tool '${toolName}' from endpoint ${mcpEndpoint}:`,
error,
);
// Re-throw or format the error for the LLM
throw new Error(
`Execution failed for MCP tool '${toolName}': ${
error instanceof Error ? error.message : String(error)
}`,
);
}
};
actions.push({
name: toolName,
description: tool.description || `MCP tool: ${toolName} (from ${mcpEndpoint})`,
parameters: parameters,
handler: handler,
// Add metadata for easier identification/debugging
_isMCPTool: true,
_mcpEndpoint: mcpEndpoint,
} as Action<any> & { _isMCPTool: boolean; _mcpEndpoint: string }); // Type assertion for metadata
}
return actions;
}
/**
* Generate better instructions for using MCP tools
* This is used to enhance the system prompt with tool documentation
*/
export function generateMcpToolInstructions(toolsMap: Record<string, MCPTool>): string {
if (!toolsMap || Object.keys(toolsMap).length === 0) {
return "";
}
const toolEntries = Object.entries(toolsMap);
// Generate documentation for each tool
const toolsDoc = toolEntries
.map(([name, tool]) => {
// Extract schema information if available
let paramsDoc = " No parameters required";
try {
if (tool.schema && typeof tool.schema === "object") {
const schema = tool.schema as any;
// Extract parameters from JSON Schema
if (schema.properties) {
const requiredParams = schema.required || [];
// Build parameter documentation from properties
const paramsList = Object.entries(schema.properties).map(([paramName, propSchema]) => {
const propDetails = propSchema as any;
const requiredMark = requiredParams.includes(paramName) ? "*" : "";
const typeInfo = propDetails.type || "any";
const description = propDetails.description ? ` - ${propDetails.description}` : "";
return ` - ${paramName}${requiredMark} (${typeInfo})${description}`;
});
if (paramsList.length > 0) {
paramsDoc = paramsList.join("\n");
}
}
}
} catch (e) {
console.error(`Error parsing schema for tool ${name}:`, e);
}
return `- ${name}: ${tool.description || ""}
${paramsDoc}`;
})
.join("\n\n");
return `You have access to the following external tools provided by Model Context Protocol (MCP) servers:
${toolsDoc}
When using these tools:
1. Only provide valid parameters according to their type requirements
2. Required parameters are marked with *
3. Format API calls correctly with the expected parameter structure
4. Always check tool responses to determine your next action`;
}