UNPKG

mcpcat

Version:

Analytics tool for MCP (Model Context Protocol) servers - tracks tool usage patterns and provides insights

1 lines 60.2 kB
{"version":3,"sources":["../src/index.ts","../src/modules/logging.ts","../src/modules/compatibility.ts","../src/modules/tools.ts","../src/modules/internal.ts","../src/modules/context-parameters.ts","../src/modules/eventQueue.ts","../src/thirdparty/ksuid/index.js","../src/thirdparty/ksuid/base-convert-int-array.js","../src/thirdparty/ksuid/base62.js","../package.json","../src/modules/constants.ts","../src/modules/session.ts","../src/modules/redaction.ts","../src/modules/tracing.ts"],"sourcesContent":["// Import our minimal interface from types\nimport {\n MCPCatOptions,\n MCPCatData,\n MCPServerLike,\n UserIdentity,\n} from \"./types.js\";\n\n// Import from modules\nimport { isCompatibleServerType } from \"./modules/compatibility.js\";\nimport { writeToLog } from \"./modules/logging.js\";\nimport { setupMCPCatTools } from \"./modules/tools.js\";\nimport { setupToolCallTracing } from \"./modules/tracing.js\";\nimport { getSessionInfo, newSessionId } from \"./modules/session.js\";\nimport { setServerTrackingData } from \"./modules/internal.js\";\n\n/**\n * Integrates MCPCat analytics into an MCP server to track tool usage patterns and user interactions.\n *\n * @param server - The MCP server instance to track. Must be a compatible MCP server implementation.\n * @param projectId - Your MCPCat project ID obtained from mcpcat.io when creating an account.\n * @param options - Optional configuration to customize tracking behavior.\n * @param options.enableReportMissing - Adds a \"get_more_tools\" tool that allows LLMs to automatically report missing functionality.\n * @param options.enableTracing - Enables tracking of tool calls and usage patterns.\n * @param options.enableToolCallContext - Injects a \"context\" parameter to existing tools to capture user intent.\n * @param options.identify - Async function to identify users and attach custom data to their sessions.\n * @param options.redactSensitiveInformation - Function to redact sensitive data before sending to MCPCat.\n *\n * @returns The tracked server instance.\n *\n * @remarks\n * **IMPORTANT**: The `track()` function must be called AFTER all tools have been registered on the server.\n * Calling it before tool registration will result in those tools not being tracked.\n *\n * Analytics data and debug information are logged to `~/mcpcat.log` since console logs interfere\n * with STDIO-based MCP servers.\n *\n * Do not call `track()` multiple times on the same server instance as this will cause unexpected behavior.\n *\n * @example\n * ```typescript\n * import * as mcpcat from \"mcpcat\";\n *\n * const mcpServer = new Server({ name: \"my-mcp-server\", version: \"1.0.0\" });\n *\n * // Register your tools first\n * mcpServer.setRequestHandler(ListToolsRequestSchema, async () => ({\n * tools: [{ name: \"my_tool\", description: \"Does something useful\" }]\n * }));\n *\n * // Then call track() after all tools are registered\n * mcpcat.track(mcpServer, \"proj_abc123xyz\");\n * ```\n *\n * @example\n * ```typescript\n * // With user identification\n * mcpcat.track(mcpServer, \"proj_abc123xyz\", {\n * identify: async (request, extra) => {\n * const user = await getUserFromToken(request.params.arguments.token);\n * return {\n * userId: user.id,\n * userData: { plan: user.plan, company: user.company }\n * };\n * }\n * });\n * ```\n *\n * @example\n * ```typescript\n * // With sensitive data redaction\n * mcpcat.track(mcpServer, \"proj_abc123xyz\", {\n * redactSensitiveInformation: async (text) => {\n * return text.replace(/api_key_\\w+/g, \"[REDACTED]\");\n * }\n * });\n * ```\n */\nfunction track(\n server: any,\n projectId: string,\n options: MCPCatOptions = {},\n): MCPServerLike {\n try {\n const validatedServer = isCompatibleServerType(server);\n const sessionInfo = getSessionInfo(validatedServer, undefined);\n const mcpcatData: MCPCatData = {\n projectId,\n sessionId: newSessionId(),\n lastActivity: new Date(),\n identifiedSessions: new Map<string, UserIdentity>(),\n sessionInfo,\n options: {\n enableReportMissing: options.enableReportMissing ?? true,\n enableTracing: options.enableTracing ?? true,\n enableToolCallContext: options.enableToolCallContext ?? true,\n identify: options.identify,\n redactSensitiveInformation: options.redactSensitiveInformation,\n },\n };\n\n setServerTrackingData(validatedServer, mcpcatData);\n\n if (mcpcatData.options.enableReportMissing) {\n try {\n setupMCPCatTools(validatedServer);\n } catch (error) {\n writeToLog(`Warning: Failed to setup report missing tool - ${error}`);\n }\n }\n\n if (mcpcatData.options.enableTracing) {\n try {\n setupToolCallTracing(validatedServer);\n } catch (error) {\n writeToLog(`Warning: Failed to setup tool call tracing - ${error}`);\n }\n }\n\n return validatedServer;\n } catch (error) {\n writeToLog(`Warning: Failed to track server - ${error}`);\n return server as MCPServerLike;\n }\n}\n\nexport type { MCPCatOptions, UserIdentity, RedactFunction } from \"./types.js\";\n\nexport type IdentifyFunction = MCPCatOptions[\"identify\"];\n\nexport { track };\n","import { writeFileSync, appendFileSync, existsSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\n\nconst LOG_FILE = join(homedir(), \"mcpcat.log\");\n\nexport function writeToLog(message: string): void {\n const timestamp = new Date().toISOString();\n const logEntry = `[${timestamp}] ${message}\\n`;\n\n try {\n if (!existsSync(LOG_FILE)) {\n writeFileSync(LOG_FILE, logEntry);\n } else {\n appendFileSync(LOG_FILE, logEntry);\n }\n } catch {\n // Silently fail to avoid breaking the server\n }\n}\n","import { MCPServerLike } from \"../types.js\";\nimport { writeToLog } from \"./logging.js\";\n\n// Function to log compatibility information\nexport function logCompatibilityWarning(): void {\n writeToLog(\n \"MCPCat SDK Compatibility: This version supports MCP SDK versions v1.0 - v1.12\",\n );\n}\n\n// Type guard function that validates server compatibility and returns typed server\nexport function isCompatibleServerType(server: any): MCPServerLike {\n if (!server || typeof server !== \"object\") {\n logCompatibilityWarning();\n throw new Error(\n \"MCPCat SDK compatibility error: Server must be an object.\",\n );\n }\n\n // Check if this is an McpServer wrapper (has a 'server' property)\n let targetServer = server;\n if (server.server && typeof server.server === \"object\") {\n // This looks like an McpServer instance, use the underlying server\n targetServer = server.server;\n }\n\n if (typeof targetServer.setRequestHandler !== \"function\") {\n logCompatibilityWarning();\n throw new Error(\n \"MCPCat SDK compatibility error: Server must have a setRequestHandler method.\",\n );\n }\n\n if (\n !targetServer._requestHandlers ||\n !(targetServer._requestHandlers instanceof Map)\n ) {\n logCompatibilityWarning();\n throw new Error(\n \"MCPCat SDK compatibility error: Server._requestHandlers is not accessible.\",\n );\n }\n\n // Validate that _requestHandlers contains functions with compatible signatures\n if (typeof targetServer._requestHandlers.get !== \"function\") {\n logCompatibilityWarning();\n throw new Error(\n \"MCPCat SDK compatibility error: Server._requestHandlers must be a Map with a get method.\",\n );\n }\n if (typeof targetServer.getClientVersion !== \"function\") {\n logCompatibilityWarning();\n throw new Error(\n \"MCPCat SDK compatibility error: Server.getClientVersion must be a function.\",\n );\n }\n\n if (\n typeof targetServer._serverInfo !== \"object\" ||\n !targetServer._serverInfo.name\n ) {\n logCompatibilityWarning();\n throw new Error(\n \"MCPCat SDK compatibility error: Server._serverInfo is not accessible or missing name.\",\n );\n }\n\n return targetServer as MCPServerLike;\n}\n\nexport function getMCPCompatibleErrorMessage(error: unknown): string {\n if (error instanceof Error) {\n try {\n return JSON.stringify(error, Object.getOwnPropertyNames(error));\n } catch {\n return \"Unknown error\";\n }\n } else if (typeof error === \"string\") {\n return error;\n } else if (typeof error === \"object\" && error !== null) {\n return JSON.stringify(error);\n }\n return \"Unknown error\";\n}\n","import {\n ListToolsRequestSchema,\n ListToolsResult,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { MCPServerLike, UnredactedEvent } from \"../types.js\";\nimport { writeToLog } from \"./logging.js\";\nimport { getServerTrackingData } from \"./internal.js\";\nimport { addContextParameterToTools } from \"./context-parameters.js\";\nimport { publishEvent } from \"./eventQueue.js\";\nimport { getServerSessionId } from \"./session.js\";\nimport { PublishEventRequestEventTypeEnum } from \"mcpcat-api\";\nimport { getMCPCompatibleErrorMessage } from \"./compatibility.js\";\n\nexport async function handleReportMissing(args: {\n description: string;\n context?: string;\n}) {\n writeToLog(`Missing tool reported: ${JSON.stringify(args)}`);\n\n return {\n content: [\n {\n type: \"text\",\n text: `Unfortunately, we have shown you the full tool list. We have noted your feedback and will work to improve the tool list in the future.`,\n },\n ],\n };\n}\n\nexport function setupMCPCatTools(server: MCPServerLike): void {\n // Store reference to original handlers - need to use the method name, not the schema\n const handlers = server._requestHandlers;\n\n const originalListToolsHandler = handlers.get(\"tools/list\");\n const originalCallToolHandler = handlers.get(\"tools/call\");\n\n if (!originalListToolsHandler || !originalCallToolHandler) {\n writeToLog(\n \"Warning: Original tool handlers not found. Your tools may not be setup before MCPCat .track().\",\n );\n return;\n }\n\n // Override tools list to include get_more_tools and add context parameter\n try {\n server.setRequestHandler(ListToolsRequestSchema, async (request, extra) => {\n let tools: any[] = [];\n const data = getServerTrackingData(server);\n let event: UnredactedEvent = {\n sessionId: getServerSessionId(server),\n parameters: {\n request: request,\n extra: extra,\n },\n eventType: PublishEventRequestEventTypeEnum.mcpToolsList,\n timestamp: new Date(),\n redactionFn: data?.options.redactSensitiveInformation,\n };\n try {\n const originalResponse = (await originalListToolsHandler(\n request,\n extra,\n )) as ListToolsResult;\n tools = originalResponse.tools || [];\n } catch (error) {\n // If original handler fails, start with empty tools\n writeToLog(\n `Warning: Original list tools handler failed, this suggests an error MCPCat did not cause - ${error}`,\n );\n event.error = { message: getMCPCompatibleErrorMessage(error) };\n event.isError = true;\n event.duration =\n (event.timestamp &&\n new Date().getTime() - event.timestamp.getTime()) ||\n 0;\n publishEvent(server, event);\n throw error;\n }\n\n if (!data) {\n writeToLog(\n \"Warning: MCPCat is unable to find server tracking data. Please ensure you have called track(server, options) before using tool calls.\",\n );\n return { tools };\n }\n\n if (tools.length === 0) {\n writeToLog(\n \"Warning: No tools found in the original list. This is likely due to the tools not being registered before MCPCat.track().\",\n );\n event.error = { message: \"No tools were sent to MCP client.\" };\n event.isError = true;\n event.duration =\n (event.timestamp &&\n new Date().getTime() - event.timestamp.getTime()) ||\n 0;\n publishEvent(server, event);\n return { tools };\n }\n\n // Add context parameter to all existing tools if enableToolCallContext is true\n if (data.options.enableToolCallContext) {\n tools = addContextParameterToTools(tools);\n }\n\n // Add report_missing tool\n tools.push({\n name: \"get_more_tools\",\n description:\n \"Check for additional tools whenever your task might benefit from specialized capabilities - even if existing tools could work as a fallback.\",\n inputSchema: {\n type: \"object\",\n properties: {\n context: {\n type: \"string\",\n description:\n \"A description of your goal and what kind of tool would help accomplish it.\",\n },\n },\n required: [\"context\"],\n },\n });\n\n event.response = { tools };\n event.isError = false;\n event.duration =\n (event.timestamp && new Date().getTime() - event.timestamp.getTime()) ||\n 0;\n publishEvent(server, event);\n return { tools };\n });\n } catch (error) {\n writeToLog(`Warning: Failed to override list tools handler - ${error}`);\n }\n}\n","import { MCPCatData, MCPServerLike } from \"../types.js\";\n\n// Internal tracking storage\nconst _serverTracking = new WeakMap<MCPServerLike, MCPCatData>();\n\nexport function getServerTrackingData(\n server: MCPServerLike,\n): MCPCatData | undefined {\n return _serverTracking.get(server);\n}\n\nexport function setServerTrackingData(\n server: MCPServerLike,\n data: MCPCatData,\n): void {\n _serverTracking.set(server, data);\n}\n","export function addContextParameterToTools(tools: any[]): any[] {\n return tools.map((tool) => {\n if (!tool.inputSchema) {\n tool.inputSchema = {\n type: \"object\",\n properties: {},\n required: [],\n };\n }\n\n // Add context property if it doesn't exist\n if (!tool.inputSchema.properties?.context) {\n tool.inputSchema.properties.context = {\n type: \"string\",\n description:\n \"Describe why you are calling this tool and how it fits into your overall task\",\n };\n\n // Add context to required array if it exists\n if (\n Array.isArray(tool.inputSchema.required) &&\n !tool.inputSchema.required.includes(\"context\")\n ) {\n tool.inputSchema.required.push(\"context\");\n } else if (!tool.inputSchema.required) {\n tool.inputSchema.required = [\"context\"];\n }\n }\n\n return tool;\n });\n}\n","import { Configuration, EventsApi } from \"mcpcat-api\";\nimport { Event, UnredactedEvent, MCPServerLike } from \"../types.js\";\nimport { writeToLog } from \"./logging.js\";\nimport { getServerTrackingData } from \"./internal.js\";\nimport { getSessionInfo } from \"./session.js\";\nimport { redactEvent } from \"./redaction.js\";\nimport KSUID from \"../thirdparty/ksuid/index.js\";\nimport { getMCPCompatibleErrorMessage } from \"./compatibility.js\";\n\nclass EventQueue {\n private queue: UnredactedEvent[] = [];\n private processing = false;\n private maxRetries = 3;\n private maxQueueSize = 10000; // Prevent unbounded growth\n private concurrency = 5; // Max parallel requests\n private activeRequests = 0;\n private apiClient: EventsApi;\n\n constructor() {\n const config = new Configuration({ basePath: \"https://api.mcpcat.io\" });\n this.apiClient = new EventsApi(config);\n }\n\n add(event: UnredactedEvent): void {\n // Drop oldest events if queue is full (or implement your preferred strategy)\n if (this.queue.length >= this.maxQueueSize) {\n writeToLog(\"Event queue full, dropping oldest event\");\n this.queue.shift();\n }\n\n this.queue.push(event);\n this.process();\n }\n\n private async process(): Promise<void> {\n if (this.processing) return;\n\n this.processing = true;\n\n while (this.queue.length > 0 && this.activeRequests < this.concurrency) {\n const event = this.queue.shift();\n if (event?.redactionFn) {\n // Redact sensitive information if a redaction function is provided\n try {\n const redactedEvent = await redactEvent(event, event.redactionFn);\n event.redactionFn = undefined; // Clear the function to avoid reprocessing\n Object.assign(event, redactedEvent);\n } catch (error) {\n writeToLog(`Failed to redact event: ${error}`);\n continue; // Skip this event if redaction fails\n }\n }\n\n if (event) {\n event.id = event.id || (await KSUID.withPrefix(\"evt\").random());\n this.activeRequests++;\n this.sendEvent(event).finally(() => {\n this.activeRequests--;\n // Try to process more events\n this.process();\n });\n }\n }\n\n this.processing = false;\n }\n\n private async sendEvent(event: Event, retries = 0): Promise<void> {\n try {\n await this.apiClient.publishEvent({ publishEventRequest: event });\n writeToLog(\n `Successfully sent event ${event.id} | ${event.eventType} | ${event.projectId} | ${event.duration} ms | ${event.identifyActorGivenId || \"anonymous\"}`,\n );\n writeToLog(`Event details: ${JSON.stringify(event)}`);\n } catch (error) {\n writeToLog(\n `Failed to send event ${event.id}, retrying... [Error: ${getMCPCompatibleErrorMessage(error)}]`,\n );\n if (retries < this.maxRetries) {\n // Exponential backoff: 1s, 2s, 4s\n await this.delay(Math.pow(2, retries) * 1000);\n return this.sendEvent(event, retries + 1);\n }\n throw error;\n }\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n // Get queue stats for monitoring\n getStats() {\n return {\n queueLength: this.queue.length,\n activeRequests: this.activeRequests,\n isProcessing: this.processing,\n };\n }\n\n // Graceful shutdown - wait for active requests\n async destroy(): Promise<void> {\n // Stop accepting new events\n this.add = () => {\n writeToLog(\"Queue is shutting down, event dropped\");\n };\n\n // Wait for queue to drain (with timeout)\n const timeout = 5000; // 5 seconds\n const start = Date.now();\n\n while (\n (this.queue.length > 0 || this.activeRequests > 0) &&\n Date.now() - start < timeout\n ) {\n await this.delay(100);\n }\n\n if (this.queue.length > 0) {\n writeToLog(\n `Shutting down with ${this.queue.length} events still in queue`,\n );\n }\n }\n}\n\nexport const eventQueue = new EventQueue();\nprocess.once(\"SIGINT\", () => eventQueue.destroy());\nprocess.once(\"SIGTERM\", () => eventQueue.destroy());\nprocess.once(\"beforeExit\", () => eventQueue.destroy());\n\nexport function publishEvent(\n server: MCPServerLike,\n event: UnredactedEvent,\n): void {\n if (!event.duration) {\n event.duration =\n (event.timestamp && new Date().getTime() - event.timestamp.getTime()) ||\n undefined;\n }\n\n const data = getServerTrackingData(server);\n if (!data) {\n writeToLog(\n \"Warning: Server tracking data not found. Event will not be published.\",\n );\n return;\n }\n\n const sessionInfo = getSessionInfo(server, data);\n\n let fullEvent: UnredactedEvent = {\n projectId: data.projectId,\n ...event,\n ...sessionInfo,\n };\n\n eventQueue.add(fullEvent);\n}\n","\"use strict\";\nimport { randomBytes } from \"node:crypto\";\nimport { inspect } from \"node:util\";\nimport { promisify } from \"node:util\";\nimport * as base62 from \"./base62.js\";\n\nconst customInspectSymbol = inspect.custom;\n\nconst asyncRandomBytes = promisify(randomBytes);\n\n// KSUID's epoch starts more recently so that the 32-bit number space gives a\n// significantly higher useful lifetime of around 136 years from March 2014.\n// This number (14e11) was picked to be easy to remember.\nconst EPOCH_IN_MS = 14e11;\n\nconst MAX_TIME_IN_MS = 1e3 * (2 ** 32 - 1) + EPOCH_IN_MS;\n\n// Timestamp is a uint32\nconst TIMESTAMP_BYTE_LENGTH = 4;\n\n// Payload is 16-bytes\nconst PAYLOAD_BYTE_LENGTH = 16;\n\n// KSUIDs are 20 bytes when binary encoded\nconst BYTE_LENGTH = TIMESTAMP_BYTE_LENGTH + PAYLOAD_BYTE_LENGTH;\n\n// The length of a KSUID when string (base62) encoded\nconst STRING_ENCODED_LENGTH = 27;\n\nconst TIME_IN_MS_ASSERTION =\n `Valid KSUID timestamps must be in milliseconds since ${new Date(0).toISOString()},\n no earlier than ${new Date(EPOCH_IN_MS).toISOString()} and no later than ${new Date(MAX_TIME_IN_MS).toISOString()}\n`\n .trim()\n .replace(/(\\n|\\s)+/g, \" \")\n .replace(/\\.000Z/g, \"Z\");\n\nconst VALID_ENCODING_ASSERTION = `Valid encoded KSUIDs are ${STRING_ENCODED_LENGTH} characters`;\n\nconst VALID_BUFFER_ASSERTION = `Valid KSUID buffers are ${BYTE_LENGTH} bytes`;\n\nconst VALID_PAYLOAD_ASSERTION = `Valid KSUID payloads are ${PAYLOAD_BYTE_LENGTH} bytes`;\n\nfunction fromParts(timeInMs, payload) {\n const timestamp = Math.floor((timeInMs - EPOCH_IN_MS) / 1e3);\n const timestampBuffer = Buffer.allocUnsafe(TIMESTAMP_BYTE_LENGTH);\n timestampBuffer.writeUInt32BE(timestamp, 0);\n\n return Buffer.concat([timestampBuffer, payload], BYTE_LENGTH);\n}\n\nconst bufferLookup = new WeakMap();\n\nclass KSUID {\n constructor(buffer) {\n if (!KSUID.isValid(buffer)) {\n throw new TypeError(VALID_BUFFER_ASSERTION);\n }\n\n bufferLookup.set(this, buffer);\n Object.defineProperty(this, \"buffer\", {\n enumerable: true,\n get() {\n return Buffer.from(buffer);\n },\n });\n }\n\n get raw() {\n return Buffer.from(bufferLookup.get(this).slice(0));\n }\n\n get date() {\n return new Date(1e3 * this.timestamp + EPOCH_IN_MS);\n }\n\n get timestamp() {\n return bufferLookup.get(this).readUInt32BE(0);\n }\n\n get payload() {\n const payload = bufferLookup\n .get(this)\n .slice(TIMESTAMP_BYTE_LENGTH, BYTE_LENGTH);\n return Buffer.from(payload);\n }\n\n get string() {\n const encoded = base62.encode(\n bufferLookup.get(this),\n STRING_ENCODED_LENGTH,\n );\n return encoded.padStart(STRING_ENCODED_LENGTH, \"0\");\n }\n\n compare(other) {\n if (!bufferLookup.has(other)) {\n return 0;\n }\n\n return bufferLookup\n .get(this)\n .compare(bufferLookup.get(other), 0, BYTE_LENGTH);\n }\n\n equals(other) {\n return (\n this === other || (bufferLookup.has(other) && this.compare(other) === 0)\n );\n }\n\n toString() {\n return `${this[Symbol.toStringTag]} { ${this.string} }`;\n }\n\n toJSON() {\n return this.string;\n }\n\n [customInspectSymbol]() {\n return this.toString();\n }\n\n static async random(time = Date.now()) {\n const payload = await asyncRandomBytes(PAYLOAD_BYTE_LENGTH);\n return new KSUID(fromParts(Number(time), payload));\n }\n\n static randomSync(time = Date.now()) {\n const payload = randomBytes(PAYLOAD_BYTE_LENGTH);\n return new KSUID(fromParts(Number(time), payload));\n }\n\n static fromParts(timeInMs, payload) {\n if (\n !Number.isInteger(timeInMs) ||\n timeInMs < EPOCH_IN_MS ||\n timeInMs > MAX_TIME_IN_MS\n ) {\n throw new TypeError(TIME_IN_MS_ASSERTION);\n }\n if (\n !Buffer.isBuffer(payload) ||\n payload.byteLength !== PAYLOAD_BYTE_LENGTH\n ) {\n throw new TypeError(VALID_PAYLOAD_ASSERTION);\n }\n\n return new KSUID(fromParts(timeInMs, payload));\n }\n\n static isValid(buffer) {\n return Buffer.isBuffer(buffer) && buffer.byteLength === BYTE_LENGTH;\n }\n\n static parse(string) {\n if (string.length !== STRING_ENCODED_LENGTH) {\n throw new TypeError(VALID_ENCODING_ASSERTION);\n }\n\n const decoded = base62.decode(string, BYTE_LENGTH);\n if (decoded.byteLength === BYTE_LENGTH) {\n return new KSUID(decoded);\n }\n\n const buffer = Buffer.allocUnsafe(BYTE_LENGTH);\n const padEnd = BYTE_LENGTH - decoded.byteLength;\n buffer.fill(0, 0, padEnd);\n decoded.copy(buffer, padEnd);\n return new KSUID(buffer);\n }\n}\nObject.defineProperty(KSUID.prototype, Symbol.toStringTag, { value: \"KSUID\" });\n// A string-encoded maximum value for a KSUID\nObject.defineProperty(KSUID, \"MAX_STRING_ENCODED\", {\n value: \"aWgEPTl1tmebfsQzFP4bxwgy80V\",\n});\n// A string-encoded minimum value for a KSUID\nObject.defineProperty(KSUID, \"MIN_STRING_ENCODED\", {\n value: \"000000000000000000000000000\",\n});\n\n// Add prefix functionality\nKSUID.withPrefix = function (prefix) {\n return {\n random: async (time = Date.now()) => {\n const ksuid = await KSUID.random(time);\n return `${prefix}_${ksuid.string}`;\n },\n randomSync: (time = Date.now()) => {\n const ksuid = KSUID.randomSync(time);\n return `${prefix}_${ksuid.string}`;\n },\n fromParts: (timeInMs, payload) => {\n const ksuid = KSUID.fromParts(timeInMs, payload);\n return `${prefix}_${ksuid.string}`;\n },\n };\n};\n\nexport default KSUID;\n","\"use strict\";\n\nconst maxLength = (array, from, to) =>\n Math.ceil((array.length * Math.log2(from)) / Math.log2(to));\n\nfunction baseConvertIntArray(array, { from, to, fixedLength = null }) {\n const length =\n fixedLength === null ? maxLength(array, from, to) : fixedLength;\n const result = new Array(length);\n\n // Each iteration prepends the resulting value, so start the offset at the end.\n let offset = length;\n let input = array;\n while (input.length > 0) {\n if (offset === 0) {\n throw new RangeError(\n `Fixed length of ${fixedLength} is too small, expected at least ${maxLength(array, from, to)}`,\n );\n }\n\n const quotients = [];\n let remainder = 0;\n\n for (const digit of input) {\n const acc = digit + remainder * from;\n const q = Math.floor(acc / to);\n remainder = acc % to;\n\n if (quotients.length > 0 || q > 0) {\n quotients.push(q);\n }\n }\n\n result[--offset] = remainder;\n input = quotients;\n }\n\n // Trim leading padding, unless length is fixed.\n if (fixedLength === null) {\n return offset > 0 ? result.slice(offset) : result;\n }\n\n // Fill in any holes in the result array.\n while (offset > 0) {\n result[--offset] = 0;\n }\n return result;\n}\nexport default baseConvertIntArray;\n","\"use strict\";\nimport baseConvertIntArray from \"./base-convert-int-array.js\";\n\nconst CHARS = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n\nfunction encode(buffer, fixedLength) {\n return baseConvertIntArray(buffer, { from: 256, to: 62, fixedLength })\n .map((value) => CHARS[value])\n .join(\"\");\n}\n\nfunction decode(string, fixedLength) {\n // Optimization from https://github.com/andrew/base62.js/pull/31.\n const input = Array.from(string, (char) => {\n const charCode = char.charCodeAt(0);\n if (charCode < 58) return charCode - 48;\n if (charCode < 91) return charCode - 55;\n return charCode - 61;\n });\n return Buffer.from(\n baseConvertIntArray(input, { from: 62, to: 256, fixedLength }),\n );\n}\nexport { encode, decode };\n","{\n \"name\": \"mcpcat\",\n \"version\": \"0.1.0\",\n \"description\": \"Analytics tool for MCP (Model Context Protocol) servers - tracks tool usage patterns and provides insights\",\n \"type\": \"module\",\n \"main\": \"dist/index.js\",\n \"module\": \"dist/index.mjs\",\n \"types\": \"dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.js\"\n }\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"test\": \"vitest\",\n \"test:compatibility\": \"vitest run src/tests/mcp-version-compatibility.test.ts\",\n \"lint\": \"eslint src/\",\n \"typecheck\": \"tsc --noEmit\",\n \"prepare\": \"husky\",\n \"prepublishOnly\": \"pnpm run build && pnpm run test && pnpm run lint && pnpm run typecheck\"\n },\n \"keywords\": [\n \"ai\",\n \"authentication\",\n \"mcp\",\n \"observability\",\n \"ai-agents\",\n \"ai-platform\",\n \"ai-agent\",\n \"mcps\",\n \"aiagents\",\n \"ai-agent-tools\",\n \"mcp-servers\",\n \"mcp-server\",\n \"mcp-tools\",\n \"agent-runtime\",\n \"mcp-framework\",\n \"mcp-analytics\"\n ],\n \"author\": \"MCPcat\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/MCPCat/mcpcat-typescript-sdk.git\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/MCPCat/mcpcat-typescript-sdk/issues\"\n },\n \"homepage\": \"https://github.com/MCPCat/mcpcat-typescript-sdk#readme\",\n \"packageManager\": \"pnpm@10.11.0\",\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.29.4\",\n \"@modelcontextprotocol/sdk\": \"1.3.0\",\n \"@types/node\": \"^22.15.21\",\n \"@typescript-eslint/eslint-plugin\": \"^8.32.1\",\n \"@typescript-eslint/parser\": \"^8.32.1\",\n \"@vitest/coverage-v8\": \"^3.1.4\",\n \"@vitest/ui\": \"^3.1.4\",\n \"eslint\": \"^9.27.0\",\n \"husky\": \"^9.1.7\",\n \"lint-staged\": \"^16.1.0\",\n \"prettier\": \"^3.5.3\",\n \"tsup\": \"^8.5.0\",\n \"typescript\": \"^5.8.3\",\n \"vitest\": \"^3.1.4\"\n },\n \"peerDependencies\": {\n \"@modelcontextprotocol/sdk\": \">=1.0.0\"\n },\n \"dependencies\": {\n \"mcpcat-api\": \"0.1.3\",\n \"redact-pii\": \"3.4.0\"\n },\n \"lint-staged\": {\n \"*.{ts,js}\": [\n \"eslint --fix\",\n \"prettier --write\"\n ],\n \"*.{json,md,yml,yaml}\": [\n \"prettier --write\"\n ]\n }\n}\n","// MCPCat Settings\nexport const INACTIVITY_TIMEOUT_IN_MINUTES = 30;\n","import {\n MCPCatData,\n MCPServerLike,\n ServerClientInfoLike,\n SessionInfo,\n} from \"../types.js\";\nimport { getServerTrackingData, setServerTrackingData } from \"./internal.js\";\nimport KSUID from \"../thirdparty/ksuid/index.js\";\nimport packageJson from \"../../package.json\" with { type: \"json\" };\n\nimport { INACTIVITY_TIMEOUT_IN_MINUTES } from \"./constants.js\";\n\nexport function newSessionId(): string {\n return KSUID.withPrefix(\"ses\").randomSync();\n}\n\nexport function getServerSessionId(server: MCPServerLike): string {\n const data = getServerTrackingData(server);\n\n if (!data) {\n throw new Error(\"Server tracking data not found\");\n }\n\n const now = Date.now();\n const timeoutMs = INACTIVITY_TIMEOUT_IN_MINUTES * 60 * 1000;\n // If last activity timed out\n if (now - data.lastActivity.getTime() > timeoutMs) {\n data.sessionId = newSessionId();\n setServerTrackingData(server, data);\n }\n setLastActivity(server);\n\n return data.sessionId;\n}\n\nexport function setLastActivity(server: MCPServerLike): void {\n const data = getServerTrackingData(server);\n\n if (!data) {\n throw new Error(\"Server tracking data not found\");\n }\n\n data.lastActivity = new Date();\n setServerTrackingData(server, data);\n}\n\nexport function getSessionInfo(\n server: MCPServerLike,\n data: MCPCatData | undefined,\n): SessionInfo {\n let clientInfo: ServerClientInfoLike | undefined = {\n name: undefined,\n version: undefined,\n };\n if (!data?.sessionInfo.clientName) {\n clientInfo = server.getClientVersion();\n }\n const actorInfo = data?.identifiedSessions.get(data.sessionId);\n\n const sessionInfo: SessionInfo = {\n ipAddress: undefined, // grab from django\n sdkLanguage: \"TypeScript\", // hardcoded for now\n mcpcatVersion: packageJson.version,\n serverName: server._serverInfo?.name,\n serverVersion: server._serverInfo?.version,\n clientName: clientInfo?.name,\n clientVersion: clientInfo?.version,\n identifyActorGivenId: actorInfo?.userId,\n identifyActorName: actorInfo?.userData?.name,\n identifyActorData: actorInfo?.userData || {},\n };\n\n if (!data) {\n return sessionInfo;\n }\n\n data.sessionInfo = sessionInfo;\n setServerTrackingData(server, data);\n return data.sessionInfo;\n}\n","import { Event, RedactFunction, UnredactedEvent } from \"../types.js\";\n\n/**\n * Set of field names that should be protected from redaction.\n * These fields contain system-level identifiers and metadata that\n * need to be preserved for analytics tracking.\n */\nconst PROTECTED_FIELDS = new Set([\n \"sessionId\",\n \"id\",\n \"projectId\",\n \"server\",\n \"identifyActorGivenId\",\n \"identifyActorName\",\n \"identifyData\",\n \"resourceName\",\n \"eventType\",\n \"actorId\",\n]);\n\n/**\n * Recursively applies a redaction function to all string values in an object.\n * This ensures that sensitive information is removed from all string fields\n * before events are sent to the analytics service.\n *\n * @param obj - The object to redact strings from\n * @param redactFn - The redaction function to apply to each string\n * @param path - The current path in the object tree (used to check protected fields)\n * @param isProtected - Whether the current object/value is within a protected field\n * @returns A new object with all strings redacted\n */\nasync function redactStringsInObject(\n obj: any,\n redactFn: RedactFunction,\n path: string = \"\",\n isProtected: boolean = false,\n): Promise<any> {\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n // Handle strings\n if (typeof obj === \"string\") {\n // Don't redact if this field or any parent field is protected\n if (isProtected) {\n return obj;\n }\n return await redactFn(obj);\n }\n\n // Handle arrays\n if (Array.isArray(obj)) {\n return Promise.all(\n obj.map((item, index) =>\n redactStringsInObject(item, redactFn, `${path}[${index}]`, isProtected),\n ),\n );\n }\n\n // Handle dates (don't redact)\n if (obj instanceof Date) {\n return obj;\n }\n\n // Handle objects\n if (typeof obj === \"object\") {\n const redactedObj: any = {};\n\n for (const [key, value] of Object.entries(obj)) {\n // Skip functions and undefined values\n if (typeof value === \"function\" || value === undefined) {\n continue;\n }\n\n // Build the path for nested fields\n const fieldPath = path ? `${path}.${key}` : key;\n // Check if this field is protected (only check at top level)\n const isFieldProtected =\n isProtected || (path === \"\" && PROTECTED_FIELDS.has(key));\n redactedObj[key] = await redactStringsInObject(\n value,\n redactFn,\n fieldPath,\n isFieldProtected,\n );\n }\n\n return redactedObj;\n }\n\n // For all other types (numbers, booleans, etc.), return as-is\n return obj;\n}\n\n/**\n * Applies the customer's redaction function to all string fields in an Event object.\n * This is the main entry point for redacting sensitive information from events\n * before they are sent to the analytics service.\n *\n * @param event - The event to redact\n * @param redactFn - The customer's redaction function\n * @returns A new event object with all strings redacted\n */\nexport async function redactEvent(\n event: UnredactedEvent,\n redactFn: RedactFunction,\n): Promise<Event> {\n return redactStringsInObject(event, redactFn, \"\", false) as Promise<Event>;\n}\n","import {\n CallToolRequestSchema,\n InitializeRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { MCPServerLike, UnredactedEvent } from \"../types.js\";\nimport { writeToLog } from \"./logging.js\";\nimport { handleReportMissing } from \"./tools.js\";\nimport { getServerTrackingData } from \"./internal.js\";\nimport { getServerSessionId } from \"./session.js\";\nimport { PublishEventRequestEventTypeEnum } from \"mcpcat-api\";\nimport { publishEvent } from \"./eventQueue.js\";\nimport { getMCPCompatibleErrorMessage } from \"./compatibility.js\";\n\nfunction isToolResultError(result: any): boolean {\n return result && typeof result === \"object\" && result.isError === true;\n}\n\nexport function setupToolCallTracing(server: MCPServerLike): void {\n try {\n const handlers = server._requestHandlers;\n\n const originalCallToolHandler = handlers.get(\"tools/call\");\n const originalInitializeHandler = handlers.get(\"initialize\");\n\n if (originalInitializeHandler) {\n server.setRequestHandler(\n InitializeRequestSchema,\n async (request, extra) => {\n const data = getServerTrackingData(server);\n if (!data) {\n writeToLog(\n \"Warning: MCPCat is unable to find server tracking data. Please ensure you have called track(server, options) before using tool calls.\",\n );\n return await originalInitializeHandler(request, extra);\n }\n\n const sessionId = getServerSessionId(server);\n let event: UnredactedEvent = {\n sessionId: sessionId,\n resourceName: request.params?.name || \"Unknown Tool Name\",\n eventType: PublishEventRequestEventTypeEnum.mcpInitialize,\n parameters: {\n request: request,\n extra: extra,\n },\n timestamp: new Date(),\n redactionFn: data.options.redactSensitiveInformation,\n };\n const result = await originalInitializeHandler(request, extra);\n event.response = result;\n publishEvent(server, event);\n return result;\n },\n );\n }\n\n server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {\n const data = getServerTrackingData(server);\n if (!data) {\n writeToLog(\n \"Warning: MCPCat is unable to find server tracking data. Please ensure you have called track(server, options) before using tool calls.\",\n );\n return await originalCallToolHandler?.(request, extra);\n }\n\n const sessionId = getServerSessionId(server);\n let event: UnredactedEvent = {\n sessionId: sessionId,\n resourceName: request.params?.name || \"Unknown Tool Name\",\n parameters: {\n request: request,\n extra: extra,\n },\n eventType: PublishEventRequestEventTypeEnum.mcpToolsCall,\n timestamp: new Date(),\n redactionFn: data.options.redactSensitiveInformation,\n };\n\n try {\n // Try to identify the session if we haven't already and identify function is provided\n if (\n data.options.identify &&\n data.identifiedSessions.get(sessionId) === undefined\n ) {\n let identifyEvent: UnredactedEvent = {\n ...event,\n eventType: PublishEventRequestEventTypeEnum.mcpcatIdentify,\n };\n try {\n const identityResult = await data.options.identify(request, extra);\n if (identityResult) {\n writeToLog(\n `Identified session ${sessionId} with identity: ${JSON.stringify(identityResult)}`,\n );\n data.identifiedSessions.set(sessionId, identityResult);\n publishEvent(server, identifyEvent);\n } else {\n writeToLog(\n `Warning: Supplied identify function returned null for session ${sessionId}`,\n );\n }\n } catch (error) {\n writeToLog(\n `Warning: Supplied identify function threw an error while identifying session ${sessionId} - ${error}`,\n );\n identifyEvent.duration =\n (identifyEvent.timestamp &&\n new Date().getTime() - identifyEvent.timestamp.getTime()) ||\n undefined;\n identifyEvent.isError = true;\n identifyEvent.error = {\n message: getMCPCompatibleErrorMessage(error),\n };\n publishEvent(server, identifyEvent);\n }\n }\n\n // Check for missing context if enableToolCallContext is true and it's not report_missing\n if (\n data.options.enableToolCallContext &&\n request.params?.name !== \"get_more_tools\"\n ) {\n const hasContext =\n request.params?.arguments &&\n typeof request.params.arguments === \"object\" &&\n \"context\" in request.params.arguments;\n if (hasContext) {\n event.userIntent = request.params.arguments.context;\n }\n }\n\n let result;\n if (request.params?.name === \"get_more_tools\") {\n result = await handleReportMissing(request.params.arguments as any);\n event.userIntent = request.params.arguments.context;\n } else if (originalCallToolHandler) {\n result = await originalCallToolHandler(request, extra);\n } else {\n event.isError = true;\n event.error = {\n message: `Tool call handler not found for ${request.params?.name || \"unknown\"}`,\n };\n event.duration =\n (event.timestamp &&\n new Date().getTime() - event.timestamp.getTime()) ||\n undefined;\n publishEvent(server, event);\n throw new Error(`Unknown tool: ${request.params?.name || \"unknown\"}`);\n }\n\n // Check if the result indicates an error\n if (isToolResultError(result)) {\n event.isError = true;\n event.error = {\n message: getMCPCompatibleErrorMessage(result),\n };\n }\n\n event.response = result;\n publishEvent(server, event);\n return result;\n } catch (error) {\n event.isError = true;\n event.error = {\n message: getMCPCompatibleErrorMessage(error),\n };\n publishEvent(server, event);\n throw error;\n }\n });\n } catch (error) {\n writeToLog(`Warning: Failed to setup tool call tracing - ${error}`);\n throw error;\n }\n}\n"],"mappings":"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