Files
oh-my-pi/packages/coding-agent/src/capability/mcp.ts
T
roboomp ab8fb13eea fix(mcp): deduplicated aliased server connections
Deduplicated semantically identical MCP endpoints across provider-specific names while preserving provider priority and canonical direct names.

Kept the first registration on sanitized tool-name collisions and logged both origins.

Fixes #6786
2026-07-27 10:22:39 +00:00

98 lines
3.0 KiB
TypeScript

/**
* MCP (Model Context Protocol) Servers Capability
*
* Canonical shape for MCP server configurations, regardless of source format.
* All providers translate their native format to this shape.
*/
import { defineCapability } from ".";
import type { SourceMeta } from "./types";
/**
* Canonical MCP server configuration.
*/
export interface MCPServer {
/** Server name (unique key) */
name: string;
/** Whether this server is enabled (default: true) */
enabled?: boolean;
/** Connection timeout in milliseconds */
timeout?: number;
/** Command to run (for stdio transport) */
command?: string;
/** Command arguments */
args?: string[];
/** Environment variables */
env?: Record<string, string>;
/** Working directory for stdio transport */
cwd?: string;
/** URL (for HTTP/SSE transport) */
url?: string;
/** HTTP headers (for HTTP transport) */
headers?: Record<string, string>;
/** Authentication configuration */
auth?: {
type: "oauth" | "apikey";
credentialId?: string;
tokenUrl?: string;
clientId?: string;
clientSecret?: string;
resource?: string;
};
/** OAuth configuration (clientId, clientSecret, redirectUri, callbackPort, callbackPath, prompt) for servers requiring explicit client credentials */
oauth?: {
clientId?: string;
clientSecret?: string;
redirectUri?: string;
callbackPort?: number;
callbackPath?: string;
prompt?: string;
};
/** Transport type */
transport?: "stdio" | "sse" | "http";
/** Source metadata (added by loader) */
_source: SourceMeta;
}
/** Compare the transport inputs that determine which MCP endpoint gets connected. */
function isSameMCPConnection(left: MCPServer, right: MCPServer): boolean {
if (!Bun.deepEquals(left.auth, right.auth) || !Bun.deepEquals(left.oauth, right.oauth)) return false;
const leftTransport = left.transport ?? (left.command ? "stdio" : left.url ? "http" : "stdio");
const rightTransport = right.transport ?? (right.command ? "stdio" : right.url ? "http" : "stdio");
if (leftTransport !== rightTransport) return false;
if (leftTransport === "stdio") {
return (
left.command === right.command &&
Bun.deepEquals(left.args, right.args) &&
Bun.deepEquals(left.env, right.env) &&
left.cwd === right.cwd
);
}
return left.url === right.url && Bun.deepEquals(left.headers, right.headers);
}
export const mcpCapability = defineCapability<MCPServer>({
id: "mcps",
displayName: "MCP Servers",
description: "Model Context Protocol server configurations for external tool integrations",
key: server => server.name,
equivalent: isSameMCPConnection,
toExtensionId: server => `mcp:${server.name}`,
validate: server => {
if (!server.name) return "Missing server name";
if (!server.command && !server.url) return "Must have command or url";
// Validate transport-endpoint pairing
if (server.transport === "stdio" && !server.command) {
return "stdio transport requires command field";
}
if ((server.transport === "http" || server.transport === "sse") && !server.url) {
return "http/sse transport requires url field";
}
return undefined;
},
});