Skip to main content
Glama
capability-negotiation.ts5.9 kB
import { ServerCapabilities, ClientCapabilities, NegotiatedCapabilities, DEFAULT_SERVER_CAPABILITIES } from '../types/capabilities'; import { StructuredError } from '../types/errors'; /** * Check if protocol versions are compatible * @param serverVersion Server protocol version * @param clientVersion Client protocol version * @returns True if versions are compatible */ function areVersionsCompatible(serverVersion: string, clientVersion: string): boolean { // For now, versions must match exactly. In the future, we can implement semver comparison return serverVersion === clientVersion; } /** * Negotiate capabilities between server and client * @param clientCapabilities Optional client capabilities to negotiate with * @returns Negotiated capabilities * @throws {StructuredError} If protocol versions are incompatible */ export function negotiateCapabilities(clientCapabilities?: ClientCapabilities): NegotiatedCapabilities { // Start with protocol version const negotiatedCapabilities: NegotiatedCapabilities = { protocolVersion: DEFAULT_SERVER_CAPABILITIES.protocolVersion }; // If no client capabilities provided, copy server defaults if (!clientCapabilities) { if (DEFAULT_SERVER_CAPABILITIES.tools) { negotiatedCapabilities.tools = { ...DEFAULT_SERVER_CAPABILITIES.tools }; } if (DEFAULT_SERVER_CAPABILITIES.resources) { negotiatedCapabilities.resources = { ...DEFAULT_SERVER_CAPABILITIES.resources }; } if (DEFAULT_SERVER_CAPABILITIES.prompts) { negotiatedCapabilities.prompts = { ...DEFAULT_SERVER_CAPABILITIES.prompts }; } if (DEFAULT_SERVER_CAPABILITIES.logging) { negotiatedCapabilities.logging = { ...DEFAULT_SERVER_CAPABILITIES.logging }; } return negotiatedCapabilities; } // Check protocol version compatibility if (!areVersionsCompatible(DEFAULT_SERVER_CAPABILITIES.protocolVersion, clientCapabilities.protocolVersion)) { throw new StructuredError({ code: 'INCOMPATIBLE_PROTOCOL_VERSION', message: `Protocol version mismatch. Server: ${DEFAULT_SERVER_CAPABILITIES.protocolVersion}, Client: ${clientCapabilities.protocolVersion}`, data: { serverVersion: DEFAULT_SERVER_CAPABILITIES.protocolVersion, clientVersion: clientCapabilities.protocolVersion } }); } // Negotiate tools capabilities if (DEFAULT_SERVER_CAPABILITIES.tools) { negotiatedCapabilities.tools = { call: clientCapabilities.tools?.call ?? DEFAULT_SERVER_CAPABILITIES.tools.call ?? false, list: clientCapabilities.tools?.list ?? DEFAULT_SERVER_CAPABILITIES.tools.list ?? false, listChanged: clientCapabilities.tools?.listChanged ?? DEFAULT_SERVER_CAPABILITIES.tools.listChanged ?? false, subscribe: clientCapabilities.tools?.subscribe ?? DEFAULT_SERVER_CAPABILITIES.tools.subscribe ?? false }; } // Negotiate resources capabilities if (DEFAULT_SERVER_CAPABILITIES.resources) { negotiatedCapabilities.resources = { get: clientCapabilities.resources?.get ?? DEFAULT_SERVER_CAPABILITIES.resources.get ?? false, put: clientCapabilities.resources?.put ?? DEFAULT_SERVER_CAPABILITIES.resources.put ?? false, delete: clientCapabilities.resources?.delete ?? DEFAULT_SERVER_CAPABILITIES.resources.delete ?? false, listChanged: clientCapabilities.resources?.listChanged ?? DEFAULT_SERVER_CAPABILITIES.resources.listChanged ?? false, subscribe: clientCapabilities.resources?.subscribe ?? DEFAULT_SERVER_CAPABILITIES.resources.subscribe ?? false }; } // Negotiate prompts capabilities if (DEFAULT_SERVER_CAPABILITIES.prompts) { negotiatedCapabilities.prompts = { get: clientCapabilities.prompts?.get ?? DEFAULT_SERVER_CAPABILITIES.prompts.get ?? false, list: clientCapabilities.prompts?.list ?? DEFAULT_SERVER_CAPABILITIES.prompts.list ?? false, listChanged: clientCapabilities.prompts?.listChanged ?? DEFAULT_SERVER_CAPABILITIES.prompts.listChanged ?? false, subscribe: clientCapabilities.prompts?.subscribe ?? DEFAULT_SERVER_CAPABILITIES.prompts.subscribe ?? false }; } // Negotiate logging capabilities if (DEFAULT_SERVER_CAPABILITIES.logging) { negotiatedCapabilities.logging = { level: clientCapabilities.logging?.level ?? DEFAULT_SERVER_CAPABILITIES.logging.level ?? 'info', subscribe: clientCapabilities.logging?.subscribe ?? DEFAULT_SERVER_CAPABILITIES.logging.subscribe ?? false }; } return negotiatedCapabilities; } /** * Validate client capabilities against server capabilities * @param capabilities Client capabilities to validate * @returns True if capabilities are valid, false otherwise */ export function validateClientCapabilities(capabilities: unknown): capabilities is ClientCapabilities { if (!capabilities || typeof capabilities !== 'object') { return false; } const capObj = capabilities as Record<string, unknown>; // Check for unknown capability categories const validCategories = ['tools', 'resources', 'prompts', 'logging']; const hasInvalidCategory = Object.keys(capObj).some(key => !validCategories.includes(key)); if (hasInvalidCategory) { return false; } // Validate tools capabilities if (capObj.tools && typeof capObj.tools !== 'object') { return false; } // Validate resources capabilities if (capObj.resources && typeof capObj.resources !== 'object') { return false; } // Validate prompts capabilities if (capObj.prompts && typeof capObj.prompts !== 'object') { return false; } // Validate logging capabilities if (capObj.logging) { if (typeof capObj.logging !== 'object') { return false; } const logging = capObj.logging as Record<string, unknown>; if (logging.level && !['debug', 'info', 'warn', 'error'].includes(logging.level as string)) { return false; } } return true; }

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/learnwithcc/tally-mcp'

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