{"version":3,"file":"mcp-mock.cjs","names":["readBody","flattenHeaders","http","createMCPRequestHandler"],"sources":["../src/mcp-mock.ts"],"sourcesContent":["import * as http from \"node:http\";\nimport type { Mountable } from \"./types.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\nimport type {\n  MCPMockOptions,\n  MCPToolDefinition,\n  MCPResourceDefinition,\n  MCPResourceContent,\n  MCPPromptDefinition,\n  MCPPromptResult,\n  MCPContent,\n  MCPSession,\n} from \"./mcp-types.js\";\nimport { createMCPRequestHandler, type MCPState } from \"./mcp-handler.js\";\nimport { flattenHeaders, readBody } from \"./helpers.js\";\n\nexport class MCPMock implements Mountable {\n  private tools: Map<\n    string,\n    { def: MCPToolDefinition; handler?: (...args: unknown[]) => unknown }\n  > = new Map();\n  private resources: Map<string, { def: MCPResourceDefinition; content?: MCPResourceContent }> =\n    new Map();\n  private prompts: Map<\n    string,\n    {\n      def: MCPPromptDefinition;\n      handler?: (...args: unknown[]) => MCPPromptResult | Promise<MCPPromptResult>;\n    }\n  > = new Map();\n  private sessions: Map<string, MCPSession> = new Map();\n  private server: http.Server | null = null;\n  private journal: Journal | null = null;\n  private registry: MetricsRegistry | null = null;\n  private options: MCPMockOptions;\n  private requestHandler: ReturnType<typeof createMCPRequestHandler>;\n\n  constructor(options?: MCPMockOptions) {\n    this.options = options ?? {};\n    this.requestHandler = this.buildHandler();\n  }\n\n  // ---- Configuration: Tools ----\n\n  addTool(def: MCPToolDefinition): this {\n    this.tools.set(def.name, { def });\n    return this;\n  }\n\n  onToolCall(\n    name: string,\n    handler: (args: unknown) => MCPContent[] | string | Promise<MCPContent[] | string>,\n  ): this {\n    const entry = this.tools.get(name);\n    if (entry) {\n      entry.handler = handler;\n    } else {\n      this.tools.set(name, { def: { name }, handler });\n    }\n    return this;\n  }\n\n  // ---- Configuration: Resources ----\n\n  addResource(def: MCPResourceDefinition, content?: MCPResourceContent): this {\n    this.resources.set(def.uri, { def, content });\n    return this;\n  }\n\n  // ---- Configuration: Prompts ----\n\n  addPrompt(\n    def: MCPPromptDefinition,\n    handler?: (args: unknown) => MCPPromptResult | Promise<MCPPromptResult>,\n  ): this {\n    this.prompts.set(def.name, { def, handler });\n    return this;\n  }\n\n  // ---- Mountable interface ----\n\n  async handleRequest(\n    req: http.IncomingMessage,\n    res: http.ServerResponse,\n    pathname: string,\n  ): Promise<boolean> {\n    // Only handle POST and DELETE to the root of the mount\n    if (pathname !== \"/\" && pathname !== \"\") {\n      return false;\n    }\n    if (req.method !== \"POST\" && req.method !== \"DELETE\") {\n      return false;\n    }\n\n    const body = await readBody(req);\n\n    // Extract JSON-RPC method for metrics (skip for DELETE — no JSON-RPC body)\n    if (this.registry) {\n      if (req.method === \"DELETE\") {\n        this.registry.incrementCounter(\"aimock_mcp_requests_total\", { method: \"session/delete\" });\n      } else {\n        try {\n          const parsed = JSON.parse(body);\n          const method =\n            typeof parsed === \"object\" && parsed !== null && \"method\" in parsed\n              ? String(parsed.method)\n              : \"unknown\";\n          this.registry.incrementCounter(\"aimock_mcp_requests_total\", { method });\n        } catch {\n          this.registry.incrementCounter(\"aimock_mcp_requests_total\", { method: \"unknown\" });\n        }\n      }\n    }\n\n    await this.requestHandler(req, res, body);\n\n    // Journal the request after the handler completes\n    if (this.journal) {\n      this.journal.add({\n        method: req.method ?? \"POST\",\n        path: req.url ?? \"/\",\n        headers: flattenHeaders(req.headers),\n        body: null,\n        service: \"mcp\",\n        response: { status: res.statusCode, fixture: null },\n      });\n    }\n\n    return true;\n  }\n\n  health(): { status: string; [key: string]: unknown } {\n    return {\n      status: \"ok\",\n      tools: this.tools.size,\n      resources: this.resources.size,\n      prompts: this.prompts.size,\n      sessions: this.sessions.size,\n    };\n  }\n\n  setJournal(journal: Journal): void {\n    this.journal = journal;\n  }\n\n  setRegistry(registry: MetricsRegistry): void {\n    this.registry = registry;\n  }\n\n  // ---- Standalone mode ----\n\n  async start(): Promise<string> {\n    if (this.server) {\n      throw new Error(\"Server already started\");\n    }\n\n    const host = this.options.host ?? \"127.0.0.1\";\n    const port = this.options.port ?? 0;\n\n    return new Promise((resolve, reject) => {\n      const srv = http.createServer((req, res) => {\n        const chunks: Buffer[] = [];\n        req.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n        req.on(\"end\", () => {\n          const body = Buffer.concat(chunks).toString();\n\n          this.requestHandler(req, res, body)\n            .then(() => {\n              if (this.journal) {\n                this.journal.add({\n                  method: req.method ?? \"POST\",\n                  path: req.url ?? \"/\",\n                  headers: flattenHeaders(req.headers),\n                  body: null,\n                  service: \"mcp\",\n                  response: { status: res.statusCode, fixture: null },\n                });\n              }\n            })\n            .catch((err) => {\n              console.error(\"MCPMock request error:\", err);\n              if (!res.headersSent) {\n                res.writeHead(500);\n                res.end(\"Internal server error\");\n              } else if (!res.writableEnded) {\n                res.end();\n              }\n            });\n        });\n      });\n\n      srv.listen(port, host, () => {\n        this.server = srv;\n        const addr = srv.address();\n        if (typeof addr === \"object\" && addr !== null) {\n          resolve(`http://${host}:${addr.port}`);\n        } else {\n          resolve(`http://${host}:${port}`);\n        }\n      });\n\n      srv.on(\"error\", reject);\n    });\n  }\n\n  async stop(): Promise<void> {\n    if (!this.server) {\n      throw new Error(\"Server not started\");\n    }\n    const srv = this.server;\n    this.server = null;\n    await new Promise<void>((resolve, reject) => {\n      srv.close((err) => (err ? reject(err) : resolve()));\n    });\n  }\n\n  // ---- Inspection ----\n\n  getRequests(): unknown[] {\n    if (!this.journal) return [];\n    return this.journal.getAll().filter((e) => e.service === \"mcp\");\n  }\n\n  getSessions(): Map<string, MCPSession> {\n    return new Map(this.sessions);\n  }\n\n  reset(): this {\n    this.tools.clear();\n    this.resources.clear();\n    this.prompts.clear();\n    this.sessions.clear();\n    this.requestHandler = this.buildHandler();\n    return this;\n  }\n\n  // ---- Internal ----\n\n  private buildHandler() {\n    const state: MCPState = {\n      serverInfo: this.options.serverInfo ?? { name: \"mcp-mock\", version: \"1.0.0\" },\n      tools: this.tools,\n      resources: this.resources,\n      prompts: this.prompts,\n      sessions: this.sessions,\n    };\n    return createMCPRequestHandler(state);\n  }\n}\n"],"mappings":"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"}