@copass/mcp
Advanced tools
+26
-11
@@ -289,9 +289,12 @@ #!/usr/bin/env node | ||
| { | ||
| description: "Open a new Context Window \u2014 an ephemeral data source tracking this conversation. Subsequent retrieval calls become automatically window-aware, and every turn you record via `context_window_add_turn` is ingested into the graph so past turns become retrievable. Returns a `data_source_id`; persist it on your side if you may want to resume this conversation later via `context_window_attach`.", | ||
| description: 'Open a new Context Window \u2014 an ephemeral data source tracking this conversation. Subsequent retrieval calls become automatically window-aware, and every turn you record via `context_window_add_turn` is ingested into the graph so past turns become retrievable. Returns a `data_source_id`; persist it on your side if you may want to resume this conversation later via `context_window_attach`. Pass `participants` to set a default conversation roster \u2014 forwarded as the envelope `participants` field on every turn so downstream extraction can resolve second-person pronouns ("your X" \u2192 the OTHER listed participant).', | ||
| inputSchema: { | ||
| project_id: import_zod2.z.string().optional().describe("Override the server default project_id."), | ||
| name: import_zod2.z.string().optional().describe("Optional stable name for the underlying data source.") | ||
| name: import_zod2.z.string().optional().describe("Optional stable name for the underlying data source."), | ||
| participants: import_zod2.z.array(import_zod2.z.string()).optional().describe( | ||
| 'Default conversation participant roster (e.g. ["User", "Assistant"]). Forwarded on every turn unless add_turn passes its own override.' | ||
| ) | ||
| } | ||
| }, | ||
| async ({ project_id, name }) => { | ||
| async ({ project_id, name, participants }) => { | ||
| try { | ||
@@ -301,3 +304,4 @@ const window = await client.contextWindow.create({ | ||
| project_id: project_id ?? config.project_id, | ||
| name | ||
| name, | ||
| participants | ||
| }); | ||
@@ -317,10 +321,16 @@ windows.set(window); | ||
| { | ||
| description: "Append a turn to the active Context Window and push it into the graph. Call on every user message AND every assistant message so the thread itself becomes retrievable. If you omit `data_source_id`, the active window is used.", | ||
| description: 'Append a turn to the active Context Window and push it into the graph. Call on every user message AND every assistant message so the thread itself becomes retrievable. If you omit `data_source_id`, the active window is used. Pass `name` when the speaker has a richer identity than `role` alone (e.g. an actual user name like "Alice"); falls back to capitalized `role` when absent. Pass `participants` to override the window\'s default roster for this turn only.', | ||
| inputSchema: { | ||
| role: import_zod2.z.enum(["user", "assistant", "system"]).describe("Role of the speaker for this turn."), | ||
| content: import_zod2.z.string().describe("The turn content."), | ||
| name: import_zod2.z.string().optional().describe( | ||
| 'Optional named participant for this turn \u2014 forwarded as the envelope `speaker`. Caller-decided literal (e.g. "Alice", "support-bot"). When omitted, falls back to the capitalized role.' | ||
| ), | ||
| participants: import_zod2.z.array(import_zod2.z.string()).optional().describe( | ||
| "Per-turn participants override. Falls back to the window's constructor-time roster when omitted." | ||
| ), | ||
| data_source_id: import_zod2.z.string().optional().describe("Override the active window id (for multi-window use cases).") | ||
| } | ||
| }, | ||
| async ({ role, content, data_source_id }) => { | ||
| async ({ role, content, name, participants, data_source_id }) => { | ||
| try { | ||
@@ -333,3 +343,3 @@ const window = windows.resolve(data_source_id); | ||
| } | ||
| await window.addTurn({ role, content }); | ||
| await window.addTurn({ role, content, name }, { participants }); | ||
| return mcpResult2({ | ||
@@ -353,8 +363,12 @@ data_source_id: window.dataSourceId, | ||
| role: import_zod2.z.enum(["user", "assistant", "system"]), | ||
| content: import_zod2.z.string() | ||
| content: import_zod2.z.string(), | ||
| name: import_zod2.z.string().optional() | ||
| }) | ||
| ).optional().describe("Optional pre-existing turns to seed the local buffer.") | ||
| ).optional().describe("Optional pre-existing turns to seed the local buffer."), | ||
| participants: import_zod2.z.array(import_zod2.z.string()).optional().describe( | ||
| "Default conversation participant roster \u2014 forwarded on every turn pushed via add_turn unless that call overrides." | ||
| ) | ||
| } | ||
| }, | ||
| async ({ data_source_id, initial_turns }) => { | ||
| async ({ data_source_id, initial_turns, participants }) => { | ||
| try { | ||
@@ -364,3 +378,4 @@ const window = await client.contextWindow.attach({ | ||
| data_source_id, | ||
| initialTurns: initial_turns | ||
| initialTurns: initial_turns, | ||
| participants | ||
| }); | ||
@@ -367,0 +382,0 @@ windows.set(window); |
+1
-1
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/bin.ts","../src/config.ts","../src/server.ts","../src/windows.ts","../src/tools/retrieval.ts","../src/tools/context-window.ts","../src/tools/ingest.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { CopassClient } from '@copass/core';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { loadConfig } from './config.js';\nimport { buildServer } from './server.js';\nimport { WindowRegistry } from './windows.js';\n\nasync function main(): Promise<void> {\n const config = loadConfig();\n // loadConfig() enforces api_key at runtime; the static type marks it\n // optional so embedded callers (buildServer({ client })) don't have\n // to pass one. The bin path always has it.\n const apiKey = config.api_key!;\n\n const client = new CopassClient({\n apiUrl: config.api_url,\n auth: apiKey.startsWith('olk_')\n ? { type: 'api-key', key: apiKey }\n : { type: 'bearer', token: apiKey },\n });\n\n const windows = new WindowRegistry();\n\n // Pre-attach to a Context Window if the caller passed one via env var.\n // Lets a parent process (e.g. an HTTP server) own window lifecycle and share\n // it across multiple MCP subprocess spawns.\n if (config.context_window_id) {\n try {\n const window = await client.contextWindow.attach({\n sandbox_id: config.sandbox_id,\n data_source_id: config.context_window_id,\n initialTurns: config.context_window_initial_turns,\n });\n windows.set(window);\n const turnCount = config.context_window_initial_turns?.length ?? 0;\n process.stderr.write(\n `copass-mcp: attached to context window ${config.context_window_id}` +\n (turnCount > 0 ? ` (${turnCount} initial turn${turnCount === 1 ? '' : 's'})` : '') +\n '\\n',\n );\n } catch (err) {\n process.stderr.write(\n `copass-mcp: failed to attach to COPASS_CONTEXT_WINDOW_ID=${config.context_window_id}: ${\n err instanceof Error ? err.message : String(err)\n }\\n`,\n );\n throw err;\n }\n }\n\n const server = buildServer({ client, config, windows });\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n process.stderr.write(\n `copass-mcp: started (sandbox=${config.sandbox_id}, preset=${config.preset})\\n`,\n );\n}\n\nmain().catch((err) => {\n process.stderr.write(`copass-mcp: fatal: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n});\n","import type { ChatMessage, SearchPreset } from '@copass/core';\n\nexport interface ServerConfig {\n api_url: string;\n /**\n * Required when the standalone `copass-mcp` bin builds its own\n * `CopassClient` from this config (`bin.ts`). Optional for embedded\n * use — when a host process (e.g. another MCP server) constructs the\n * `CopassClient` itself and passes it into `buildServer({ client })`,\n * the tool handlers never read this field.\n */\n api_key?: string;\n sandbox_id: string;\n project_id?: string;\n preset: SearchPreset;\n /** Default data_source_id for `ingest` when the caller doesn't pass one. */\n ingest_data_source_id?: string;\n /**\n * If set, the server attaches to this Context Window on startup and makes\n * it the active window. Use when another process (e.g. a Hono server)\n * already created the window and you're launching the MCP server as a\n * subprocess that should share it.\n */\n context_window_id?: string;\n /**\n * Optional pre-existing turns to seed the Context Window's buffer on\n * startup. Makes retrieval immediately window-aware on the first tool\n * call — without these, the first `discover` / `interpret` / `search`\n * after a subprocess spawn sees an empty history.\n *\n * Required when `context_window_id` refers to a thread that has prior\n * turns and the server should dedupe retrieval against them.\n */\n context_window_initial_turns?: ChatMessage[];\n}\n\nconst VALID_PRESETS: readonly SearchPreset[] = [\n // Canonical names\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n // `:thinking` variants are /search-only. Setting one of these as the\n // MCP subprocess default makes `interpret` fail (decomposition isn't\n // valid on /interpret) — fine when the subprocess is only used for\n // `search`, otherwise override per-call via the `preset` tool arg.\n 'copass/copass_1.0:thinking',\n 'copass/copass_2.0:thinking',\n 'copass/1.0:thinking',\n 'copass/2.0:thinking',\n] as const;\n\n/**\n * Read config from `process.env`.\n *\n * - `COPASS_API_KEY` (required)\n * - `COPASS_SANDBOX_ID` (required)\n * - `COPASS_API_URL` (default: https://ai.copass.id)\n * - `COPASS_PROJECT_ID` (optional — default for retrieval/ingest)\n * - `COPASS_PRESET` (default: `copass/copass_1.0`). Accepts any\n * `SearchPreset` (including the `copass/X.0` short aliases), but\n * `:thinking` variants only work on `/search` — setting one as the\n * subprocess default breaks `interpret`. Leave at `copass/copass_1.0`\n * unless the subprocess is search-only, and override per-call via\n * the `preset` tool arg.\n *\n * Throws a descriptive error for missing/invalid values so the MCP client\n * sees an immediate startup failure with actionable text.\n */\nexport function loadConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig {\n const missing: string[] = [];\n const api_key = env.COPASS_API_KEY ?? '';\n const sandbox_id = env.COPASS_SANDBOX_ID ?? '';\n\n if (!api_key) missing.push('COPASS_API_KEY');\n if (!sandbox_id) missing.push('COPASS_SANDBOX_ID');\n\n if (missing.length > 0) {\n throw new Error(\n `@copass/mcp: missing required env var(s): ${missing.join(', ')}. ` +\n `Set them before launching the server.`,\n );\n }\n\n const api_url = env.COPASS_API_URL?.trim() || 'https://ai.copass.id';\n const project_id = env.COPASS_PROJECT_ID?.trim() || undefined;\n const rawPreset = env.COPASS_PRESET?.trim() || 'copass/copass_1.0';\n\n if (!VALID_PRESETS.includes(rawPreset as SearchPreset)) {\n throw new Error(\n `@copass/mcp: COPASS_PRESET must be one of ${VALID_PRESETS.join(', ')}; got \"${rawPreset}\"`,\n );\n }\n\n const ingest_data_source_id = env.COPASS_INGEST_DATA_SOURCE_ID?.trim() || undefined;\n const context_window_id = env.COPASS_CONTEXT_WINDOW_ID?.trim() || undefined;\n const context_window_initial_turns = parseInitialTurns(\n env.COPASS_CONTEXT_WINDOW_INITIAL_TURNS,\n );\n\n return {\n api_url,\n api_key,\n sandbox_id,\n project_id,\n preset: rawPreset as SearchPreset,\n ingest_data_source_id,\n context_window_id,\n context_window_initial_turns,\n };\n}\n\nfunction parseInitialTurns(raw: string | undefined): ChatMessage[] | undefined {\n if (!raw || !raw.trim()) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS is not valid JSON: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n if (!Array.isArray(parsed)) {\n throw new Error(\n '@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS must be a JSON array of {role, content} objects',\n );\n }\n const turns: ChatMessage[] = [];\n for (const [i, entry] of parsed.entries()) {\n if (\n !entry ||\n typeof entry !== 'object' ||\n typeof (entry as { role?: unknown }).role !== 'string' ||\n typeof (entry as { content?: unknown }).content !== 'string'\n ) {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS[${i}] must be {role: string, content: string}`,\n );\n }\n const { role, content } = entry as { role: string; content: string };\n if (role !== 'user' && role !== 'assistant' && role !== 'system') {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS[${i}].role must be \"user\" | \"assistant\" | \"system\"; got \"${role}\"`,\n );\n }\n turns.push({ role, content });\n }\n return turns;\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CopassClient } from '@copass/core';\nimport { registerToMcpServer } from '@copass/management/adapters/mcp';\nimport type { ServerConfig } from './config.js';\nimport { WindowRegistry } from './windows.js';\nimport { registerRetrievalTools } from './tools/retrieval.js';\nimport { registerContextWindowTools } from './tools/context-window.js';\nimport { registerIngestTool } from './tools/ingest.js';\n\nexport interface BuildServerOptions {\n client: CopassClient;\n config: ServerConfig;\n /**\n * Optional pre-populated window registry. Useful when a parent process has\n * already created or attached to a Context Window and wants the server to\n * start with it as the active window.\n */\n windows?: WindowRegistry;\n}\n\n/**\n * Assemble the Copass MCP server with every tool registered. Doesn't connect\n * a transport — see `startStdioServer` in `./bin.ts` for the default path,\n * or wire your own transport if embedding.\n */\nexport function buildServer({ client, config, windows }: BuildServerOptions): McpServer {\n const server = new McpServer(\n {\n name: 'copass',\n version: '0.3.0',\n },\n {\n capabilities: { tools: {} },\n },\n );\n\n const registry = windows ?? new WindowRegistry();\n const deps = { client, config, windows: registry };\n\n registerRetrievalTools(server, deps);\n registerContextWindowTools(server, deps);\n registerIngestTool(server, { client, config });\n\n // The full management tool corpus is exposed regardless of sandbox role.\n // Viewer-role users see write tools but get structured 403 from the\n // service layer when they call them. Avoids an extra HTTP round-trip\n // at MCP startup. See ADR 0007 Phase 4 brief §10 for trade-off.\n registerToMcpServer(server, client, { sandboxId: config.sandbox_id });\n\n return server;\n}\n","import type { ContextWindow } from '@copass/core';\n\n/**\n * In-memory registry of open Context Windows.\n *\n * Tracks a map of `data_source_id → ContextWindow` plus a single \"active\"\n * window id. Retrieval and add-turn tools use the active window implicitly\n * so the LLM doesn't have to thread an id on every call.\n *\n * For multi-window use cases, callers pass `data_source_id` explicitly and\n * the active id is ignored.\n */\nexport class WindowRegistry {\n private readonly windows = new Map<string, ContextWindow>();\n private activeId: string | null = null;\n\n /** Register a new window and make it active. */\n set(window: ContextWindow): void {\n this.windows.set(window.dataSourceId, window);\n this.activeId = window.dataSourceId;\n }\n\n /** Look up a window by id, or fall back to the active one. */\n resolve(dataSourceId?: string): ContextWindow | undefined {\n const id = dataSourceId ?? this.activeId;\n return id ? this.windows.get(id) : undefined;\n }\n\n /** Make an existing window the active one. Throws if unknown. */\n activate(dataSourceId: string): ContextWindow {\n const window = this.windows.get(dataSourceId);\n if (!window) {\n throw new Error(\n `No window registered for data_source_id=\"${dataSourceId}\" — call context_window_create or context_window_attach first.`,\n );\n }\n this.activeId = dataSourceId;\n return window;\n }\n\n /** Drop a window from the registry. Clears active if it matched. */\n drop(dataSourceId: string): void {\n this.windows.delete(dataSourceId);\n if (this.activeId === dataSourceId) this.activeId = null;\n }\n\n get activeDataSourceId(): string | null {\n return this.activeId;\n }\n}\n","import { z } from 'zod';\nimport {\n DISCOVER_QUERY_PARAM,\n INTERPRET_DESCRIPTION,\n INTERPRET_ITEMS_PARAM,\n INTERPRET_QUERY_PARAM,\n MCP_DISCOVER_DESCRIPTION,\n PRESET_PARAM,\n PROJECT_ID_PARAM,\n SEARCH_DESCRIPTION,\n SEARCH_QUERY_PARAM,\n} from '@copass/config';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\nimport type { WindowRegistry } from '../windows.js';\n\ninterface RetrievalDeps {\n client: CopassClient;\n config: ServerConfig;\n windows: WindowRegistry;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerRetrievalTools(server: McpServer, deps: RetrievalDeps): void {\n const { client, config, windows } = deps;\n\n server.registerTool(\n 'discover',\n {\n description: MCP_DISCOVER_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(DISCOVER_QUERY_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n // Per-call preset override. `:thinking` variants are\n // /search-only and rejected on /discover, so the enum here\n // intentionally omits them.\n preset: z\n .enum([\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n ])\n .optional()\n .describe(PRESET_PARAM),\n },\n },\n async ({ query, project_id, preset }) => {\n try {\n const response = await client.retrieval.discover(config.sandbox_id, {\n query,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n // Per-call override wins; otherwise inherit the subprocess\n // default. `:thinking` variants are stripped at the server\n // (/discover rejects them) so we don't second-guess here.\n preset: preset ?? config.preset,\n });\n return mcpResult({\n header: response.header,\n // Project the v2 fields (`subgraph` + `matched_query_nodes`)\n // alongside the v1 fields. Populated only under\n // `copass/copass_2.0` (or its `copass/2.0` alias); `null`\n // under v1.\n items: response.items.map((item) => ({\n score: item.score,\n summary: item.summary,\n canonical_ids: item.canonical_ids,\n subgraph: item.subgraph ?? null,\n matched_query_nodes: item.matched_query_nodes ?? null,\n })),\n next_steps: response.next_steps,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'interpret',\n {\n description: INTERPRET_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(INTERPRET_QUERY_PARAM),\n items: z.array(z.array(z.string()).min(1)).min(1).describe(INTERPRET_ITEMS_PARAM),\n preset: z.enum([\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n ]).optional().describe(PRESET_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n },\n },\n async ({ query, items, preset, project_id }) => {\n try {\n const response = await client.retrieval.interpret(config.sandbox_id, {\n query,\n items,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n preset: preset ?? config.preset,\n });\n return mcpResult({ brief: response.brief });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'search',\n {\n description: SEARCH_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(SEARCH_QUERY_PARAM),\n // `:thinking` variants split the question into sub-questions and\n // run the base preset on each before a combined synthesis.\n preset: z\n .enum([\n // Canonical names\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n 'copass/copass_1.0:thinking',\n 'copass/copass_2.0:thinking',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n 'copass/1.0:thinking',\n 'copass/2.0:thinking',\n ])\n .optional()\n .describe(PRESET_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n },\n },\n async ({ query, preset, project_id }) => {\n try {\n const response = await client.retrieval.search(config.sandbox_id, {\n query,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n preset: preset ?? config.preset,\n });\n return mcpResult({ answer: response.answer });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n","import { z } from 'zod';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\nimport type { WindowRegistry } from '../windows.js';\n\ninterface ContextWindowDeps {\n client: CopassClient;\n config: ServerConfig;\n windows: WindowRegistry;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerContextWindowTools(server: McpServer, deps: ContextWindowDeps): void {\n const { client, config, windows } = deps;\n\n server.registerTool(\n 'context_window_create',\n {\n description:\n 'Open a new Context Window — an ephemeral data source tracking this conversation. ' +\n 'Subsequent retrieval calls become automatically window-aware, and every turn you ' +\n 'record via `context_window_add_turn` is ingested into the graph so past turns ' +\n 'become retrievable. Returns a `data_source_id`; persist it on your side if you ' +\n 'may want to resume this conversation later via `context_window_attach`.',\n inputSchema: {\n project_id: z.string().optional().describe('Override the server default project_id.'),\n name: z.string().optional().describe('Optional stable name for the underlying data source.'),\n },\n },\n async ({ project_id, name }) => {\n try {\n const window = await client.contextWindow.create({\n sandbox_id: config.sandbox_id,\n project_id: project_id ?? config.project_id,\n name,\n });\n windows.set(window);\n return mcpResult({\n data_source_id: window.dataSourceId,\n active: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_add_turn',\n {\n description:\n 'Append a turn to the active Context Window and push it into the graph. Call on ' +\n 'every user message AND every assistant message so the thread itself becomes ' +\n 'retrievable. If you omit `data_source_id`, the active window is used.',\n inputSchema: {\n role: z\n .enum(['user', 'assistant', 'system'])\n .describe('Role of the speaker for this turn.'),\n content: z.string().describe('The turn content.'),\n data_source_id: z\n .string()\n .optional()\n .describe('Override the active window id (for multi-window use cases).'),\n },\n },\n async ({ role, content, data_source_id }) => {\n try {\n const window = windows.resolve(data_source_id);\n if (!window) {\n throw new Error(\n 'No active Context Window — call context_window_create or context_window_attach first.',\n );\n }\n await window.addTurn({ role, content });\n return mcpResult({\n data_source_id: window.dataSourceId,\n turn_count: window.getTurns().length,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_attach',\n {\n description:\n 'Resume an existing Context Window by `data_source_id` (one you previously persisted ' +\n 'on your side after `context_window_create`). Makes the resumed window the active ' +\n 'window. Pass `initial_turns` to seed the local buffer for immediate window-aware ' +\n 'retrieval, otherwise the buffer starts empty and fills as you call add_turn.',\n inputSchema: {\n data_source_id: z.string().describe('Id of the Context Window data source to resume.'),\n initial_turns: z\n .array(\n z.object({\n role: z.enum(['user', 'assistant', 'system']),\n content: z.string(),\n }),\n )\n .optional()\n .describe('Optional pre-existing turns to seed the local buffer.'),\n },\n },\n async ({ data_source_id, initial_turns }) => {\n try {\n const window = await client.contextWindow.attach({\n sandbox_id: config.sandbox_id,\n data_source_id,\n initialTurns: initial_turns,\n });\n windows.set(window);\n return mcpResult({\n data_source_id: window.dataSourceId,\n turn_count: window.getTurns().length,\n active: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_close',\n {\n description:\n 'Close a Context Window — flips the underlying data source to `disconnected` ' +\n 'immediately instead of waiting for TTL. Best-effort; idempotent. If you omit ' +\n '`data_source_id`, the active window is closed.',\n inputSchema: {\n data_source_id: z\n .string()\n .optional()\n .describe('Override the active window id.'),\n },\n },\n async ({ data_source_id }) => {\n try {\n const window = windows.resolve(data_source_id);\n if (!window) {\n throw new Error('No active Context Window to close.');\n }\n await window.close();\n windows.drop(window.dataSourceId);\n return mcpResult({\n data_source_id: window.dataSourceId,\n closed: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n","import { z } from 'zod';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\n\ninterface IngestDeps {\n client: CopassClient;\n config: ServerConfig;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerIngestTool(server: McpServer, deps: IngestDeps): void {\n const { client, config } = deps;\n\n server.registerTool(\n 'ingest',\n {\n description:\n 'Push content into the knowledge graph via a data source. Use for: architecture ' +\n 'decisions (source_type: \"decision\"), user-shared context (\"user_input\"), ' +\n 'corrections (\"correction\"), durable notes, any significant new concept. Do NOT ' +\n 'ingest trivial changes or ephemeral debug context — those belong in the Context ' +\n 'Window (via context_window_add_turn). Pass `data_source_id` explicitly or set ' +\n '`COPASS_INGEST_DATA_SOURCE_ID` in the server env for an implicit target.',\n inputSchema: {\n content: z.string().min(1).describe('The content to ingest.'),\n source_type: z\n .string()\n .optional()\n .describe(\n 'Type tag: code, markdown, json, text, conversation, decision, correction, ' +\n 'user_input. Defaults to \"text\" when omitted.',\n ),\n data_source_id: z\n .string()\n .optional()\n .describe(\n 'Target data source. Falls back to COPASS_INGEST_DATA_SOURCE_ID env var.',\n ),\n project_id: z.string().optional().describe('Override the server default project_id.'),\n storage_only: z\n .boolean()\n .optional()\n .describe('If true, chunk and store but skip ontology ingestion.'),\n occurred_at: z\n .string()\n .optional()\n .describe(\n 'Optional ISO 8601 timestamp anchoring the content to a real-world moment ' +\n '(e.g. when this decision was made, when the user said it). Used as the ' +\n 'default occurred_at for any composed event whose own timestamp is null, ' +\n 'so temporal queries can find it.',\n ),\n },\n },\n async ({ content, source_type, data_source_id, project_id, storage_only, occurred_at }) => {\n try {\n const sourceId = data_source_id ?? config.ingest_data_source_id;\n if (!sourceId) {\n throw new Error(\n 'ingest requires a `data_source_id` argument or COPASS_INGEST_DATA_SOURCE_ID env var. ' +\n 'Register a data source via the REST API or CLI, then pass its id here.',\n );\n }\n\n const response = await client.sources.ingest(config.sandbox_id, sourceId, {\n text: content,\n source_type: source_type ?? 'text',\n project_id: project_id ?? config.project_id,\n storage_only,\n occurred_at,\n });\n\n return mcpResult({\n job_id: response.job_id,\n status: response.status,\n data_source_id: sourceId,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n"],"mappings":"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","names":["import_mcp","import_zod","mcpResult","mcpError","import_zod","mcpResult","mcpError"]} | ||
| {"version":3,"sources":["../src/bin.ts","../src/config.ts","../src/server.ts","../src/windows.ts","../src/tools/retrieval.ts","../src/tools/context-window.ts","../src/tools/ingest.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { CopassClient } from '@copass/core';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { loadConfig } from './config.js';\nimport { buildServer } from './server.js';\nimport { WindowRegistry } from './windows.js';\n\nasync function main(): Promise<void> {\n const config = loadConfig();\n // loadConfig() enforces api_key at runtime; the static type marks it\n // optional so embedded callers (buildServer({ client })) don't have\n // to pass one. The bin path always has it.\n const apiKey = config.api_key!;\n\n const client = new CopassClient({\n apiUrl: config.api_url,\n auth: apiKey.startsWith('olk_')\n ? { type: 'api-key', key: apiKey }\n : { type: 'bearer', token: apiKey },\n });\n\n const windows = new WindowRegistry();\n\n // Pre-attach to a Context Window if the caller passed one via env var.\n // Lets a parent process (e.g. an HTTP server) own window lifecycle and share\n // it across multiple MCP subprocess spawns.\n if (config.context_window_id) {\n try {\n const window = await client.contextWindow.attach({\n sandbox_id: config.sandbox_id,\n data_source_id: config.context_window_id,\n initialTurns: config.context_window_initial_turns,\n });\n windows.set(window);\n const turnCount = config.context_window_initial_turns?.length ?? 0;\n process.stderr.write(\n `copass-mcp: attached to context window ${config.context_window_id}` +\n (turnCount > 0 ? ` (${turnCount} initial turn${turnCount === 1 ? '' : 's'})` : '') +\n '\\n',\n );\n } catch (err) {\n process.stderr.write(\n `copass-mcp: failed to attach to COPASS_CONTEXT_WINDOW_ID=${config.context_window_id}: ${\n err instanceof Error ? err.message : String(err)\n }\\n`,\n );\n throw err;\n }\n }\n\n const server = buildServer({ client, config, windows });\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n process.stderr.write(\n `copass-mcp: started (sandbox=${config.sandbox_id}, preset=${config.preset})\\n`,\n );\n}\n\nmain().catch((err) => {\n process.stderr.write(`copass-mcp: fatal: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n});\n","import type { ChatMessage, SearchPreset } from '@copass/core';\n\nexport interface ServerConfig {\n api_url: string;\n /**\n * Required when the standalone `copass-mcp` bin builds its own\n * `CopassClient` from this config (`bin.ts`). Optional for embedded\n * use — when a host process (e.g. another MCP server) constructs the\n * `CopassClient` itself and passes it into `buildServer({ client })`,\n * the tool handlers never read this field.\n */\n api_key?: string;\n sandbox_id: string;\n project_id?: string;\n preset: SearchPreset;\n /** Default data_source_id for `ingest` when the caller doesn't pass one. */\n ingest_data_source_id?: string;\n /**\n * If set, the server attaches to this Context Window on startup and makes\n * it the active window. Use when another process (e.g. a Hono server)\n * already created the window and you're launching the MCP server as a\n * subprocess that should share it.\n */\n context_window_id?: string;\n /**\n * Optional pre-existing turns to seed the Context Window's buffer on\n * startup. Makes retrieval immediately window-aware on the first tool\n * call — without these, the first `discover` / `interpret` / `search`\n * after a subprocess spawn sees an empty history.\n *\n * Required when `context_window_id` refers to a thread that has prior\n * turns and the server should dedupe retrieval against them.\n */\n context_window_initial_turns?: ChatMessage[];\n}\n\nconst VALID_PRESETS: readonly SearchPreset[] = [\n // Canonical names\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n // `:thinking` variants are /search-only. Setting one of these as the\n // MCP subprocess default makes `interpret` fail (decomposition isn't\n // valid on /interpret) — fine when the subprocess is only used for\n // `search`, otherwise override per-call via the `preset` tool arg.\n 'copass/copass_1.0:thinking',\n 'copass/copass_2.0:thinking',\n 'copass/1.0:thinking',\n 'copass/2.0:thinking',\n] as const;\n\n/**\n * Read config from `process.env`.\n *\n * - `COPASS_API_KEY` (required)\n * - `COPASS_SANDBOX_ID` (required)\n * - `COPASS_API_URL` (default: https://ai.copass.id)\n * - `COPASS_PROJECT_ID` (optional — default for retrieval/ingest)\n * - `COPASS_PRESET` (default: `copass/copass_1.0`). Accepts any\n * `SearchPreset` (including the `copass/X.0` short aliases), but\n * `:thinking` variants only work on `/search` — setting one as the\n * subprocess default breaks `interpret`. Leave at `copass/copass_1.0`\n * unless the subprocess is search-only, and override per-call via\n * the `preset` tool arg.\n *\n * Throws a descriptive error for missing/invalid values so the MCP client\n * sees an immediate startup failure with actionable text.\n */\nexport function loadConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig {\n const missing: string[] = [];\n const api_key = env.COPASS_API_KEY ?? '';\n const sandbox_id = env.COPASS_SANDBOX_ID ?? '';\n\n if (!api_key) missing.push('COPASS_API_KEY');\n if (!sandbox_id) missing.push('COPASS_SANDBOX_ID');\n\n if (missing.length > 0) {\n throw new Error(\n `@copass/mcp: missing required env var(s): ${missing.join(', ')}. ` +\n `Set them before launching the server.`,\n );\n }\n\n const api_url = env.COPASS_API_URL?.trim() || 'https://ai.copass.id';\n const project_id = env.COPASS_PROJECT_ID?.trim() || undefined;\n const rawPreset = env.COPASS_PRESET?.trim() || 'copass/copass_1.0';\n\n if (!VALID_PRESETS.includes(rawPreset as SearchPreset)) {\n throw new Error(\n `@copass/mcp: COPASS_PRESET must be one of ${VALID_PRESETS.join(', ')}; got \"${rawPreset}\"`,\n );\n }\n\n const ingest_data_source_id = env.COPASS_INGEST_DATA_SOURCE_ID?.trim() || undefined;\n const context_window_id = env.COPASS_CONTEXT_WINDOW_ID?.trim() || undefined;\n const context_window_initial_turns = parseInitialTurns(\n env.COPASS_CONTEXT_WINDOW_INITIAL_TURNS,\n );\n\n return {\n api_url,\n api_key,\n sandbox_id,\n project_id,\n preset: rawPreset as SearchPreset,\n ingest_data_source_id,\n context_window_id,\n context_window_initial_turns,\n };\n}\n\nfunction parseInitialTurns(raw: string | undefined): ChatMessage[] | undefined {\n if (!raw || !raw.trim()) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS is not valid JSON: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n if (!Array.isArray(parsed)) {\n throw new Error(\n '@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS must be a JSON array of {role, content} objects',\n );\n }\n const turns: ChatMessage[] = [];\n for (const [i, entry] of parsed.entries()) {\n if (\n !entry ||\n typeof entry !== 'object' ||\n typeof (entry as { role?: unknown }).role !== 'string' ||\n typeof (entry as { content?: unknown }).content !== 'string'\n ) {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS[${i}] must be {role: string, content: string}`,\n );\n }\n const { role, content } = entry as { role: string; content: string };\n if (role !== 'user' && role !== 'assistant' && role !== 'system') {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS[${i}].role must be \"user\" | \"assistant\" | \"system\"; got \"${role}\"`,\n );\n }\n turns.push({ role, content });\n }\n return turns;\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CopassClient } from '@copass/core';\nimport { registerToMcpServer } from '@copass/management/adapters/mcp';\nimport type { ServerConfig } from './config.js';\nimport { WindowRegistry } from './windows.js';\nimport { registerRetrievalTools } from './tools/retrieval.js';\nimport { registerContextWindowTools } from './tools/context-window.js';\nimport { registerIngestTool } from './tools/ingest.js';\n\nexport interface BuildServerOptions {\n client: CopassClient;\n config: ServerConfig;\n /**\n * Optional pre-populated window registry. Useful when a parent process has\n * already created or attached to a Context Window and wants the server to\n * start with it as the active window.\n */\n windows?: WindowRegistry;\n}\n\n/**\n * Assemble the Copass MCP server with every tool registered. Doesn't connect\n * a transport — see `startStdioServer` in `./bin.ts` for the default path,\n * or wire your own transport if embedding.\n */\nexport function buildServer({ client, config, windows }: BuildServerOptions): McpServer {\n const server = new McpServer(\n {\n name: 'copass',\n version: '0.3.0',\n },\n {\n capabilities: { tools: {} },\n },\n );\n\n const registry = windows ?? new WindowRegistry();\n const deps = { client, config, windows: registry };\n\n registerRetrievalTools(server, deps);\n registerContextWindowTools(server, deps);\n registerIngestTool(server, { client, config });\n\n // The full management tool corpus is exposed regardless of sandbox role.\n // Viewer-role users see write tools but get structured 403 from the\n // service layer when they call them. Avoids an extra HTTP round-trip\n // at MCP startup. See ADR 0007 Phase 4 brief §10 for trade-off.\n registerToMcpServer(server, client, { sandboxId: config.sandbox_id });\n\n return server;\n}\n","import type { ContextWindow } from '@copass/core';\n\n/**\n * In-memory registry of open Context Windows.\n *\n * Tracks a map of `data_source_id → ContextWindow` plus a single \"active\"\n * window id. Retrieval and add-turn tools use the active window implicitly\n * so the LLM doesn't have to thread an id on every call.\n *\n * For multi-window use cases, callers pass `data_source_id` explicitly and\n * the active id is ignored.\n */\nexport class WindowRegistry {\n private readonly windows = new Map<string, ContextWindow>();\n private activeId: string | null = null;\n\n /** Register a new window and make it active. */\n set(window: ContextWindow): void {\n this.windows.set(window.dataSourceId, window);\n this.activeId = window.dataSourceId;\n }\n\n /** Look up a window by id, or fall back to the active one. */\n resolve(dataSourceId?: string): ContextWindow | undefined {\n const id = dataSourceId ?? this.activeId;\n return id ? this.windows.get(id) : undefined;\n }\n\n /** Make an existing window the active one. Throws if unknown. */\n activate(dataSourceId: string): ContextWindow {\n const window = this.windows.get(dataSourceId);\n if (!window) {\n throw new Error(\n `No window registered for data_source_id=\"${dataSourceId}\" — call context_window_create or context_window_attach first.`,\n );\n }\n this.activeId = dataSourceId;\n return window;\n }\n\n /** Drop a window from the registry. Clears active if it matched. */\n drop(dataSourceId: string): void {\n this.windows.delete(dataSourceId);\n if (this.activeId === dataSourceId) this.activeId = null;\n }\n\n get activeDataSourceId(): string | null {\n return this.activeId;\n }\n}\n","import { z } from 'zod';\nimport {\n DISCOVER_QUERY_PARAM,\n INTERPRET_DESCRIPTION,\n INTERPRET_ITEMS_PARAM,\n INTERPRET_QUERY_PARAM,\n MCP_DISCOVER_DESCRIPTION,\n PRESET_PARAM,\n PROJECT_ID_PARAM,\n SEARCH_DESCRIPTION,\n SEARCH_QUERY_PARAM,\n} from '@copass/config';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\nimport type { WindowRegistry } from '../windows.js';\n\ninterface RetrievalDeps {\n client: CopassClient;\n config: ServerConfig;\n windows: WindowRegistry;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerRetrievalTools(server: McpServer, deps: RetrievalDeps): void {\n const { client, config, windows } = deps;\n\n server.registerTool(\n 'discover',\n {\n description: MCP_DISCOVER_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(DISCOVER_QUERY_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n // Per-call preset override. `:thinking` variants are\n // /search-only and rejected on /discover, so the enum here\n // intentionally omits them.\n preset: z\n .enum([\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n ])\n .optional()\n .describe(PRESET_PARAM),\n },\n },\n async ({ query, project_id, preset }) => {\n try {\n const response = await client.retrieval.discover(config.sandbox_id, {\n query,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n // Per-call override wins; otherwise inherit the subprocess\n // default. `:thinking` variants are stripped at the server\n // (/discover rejects them) so we don't second-guess here.\n preset: preset ?? config.preset,\n });\n return mcpResult({\n header: response.header,\n // Project the v2 fields (`subgraph` + `matched_query_nodes`)\n // alongside the v1 fields. Populated only under\n // `copass/copass_2.0` (or its `copass/2.0` alias); `null`\n // under v1.\n items: response.items.map((item) => ({\n score: item.score,\n summary: item.summary,\n canonical_ids: item.canonical_ids,\n subgraph: item.subgraph ?? null,\n matched_query_nodes: item.matched_query_nodes ?? null,\n })),\n next_steps: response.next_steps,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'interpret',\n {\n description: INTERPRET_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(INTERPRET_QUERY_PARAM),\n items: z.array(z.array(z.string()).min(1)).min(1).describe(INTERPRET_ITEMS_PARAM),\n preset: z.enum([\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n ]).optional().describe(PRESET_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n },\n },\n async ({ query, items, preset, project_id }) => {\n try {\n const response = await client.retrieval.interpret(config.sandbox_id, {\n query,\n items,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n preset: preset ?? config.preset,\n });\n return mcpResult({ brief: response.brief });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'search',\n {\n description: SEARCH_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(SEARCH_QUERY_PARAM),\n // `:thinking` variants split the question into sub-questions and\n // run the base preset on each before a combined synthesis.\n preset: z\n .enum([\n // Canonical names\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n 'copass/copass_1.0:thinking',\n 'copass/copass_2.0:thinking',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n 'copass/1.0:thinking',\n 'copass/2.0:thinking',\n ])\n .optional()\n .describe(PRESET_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n },\n },\n async ({ query, preset, project_id }) => {\n try {\n const response = await client.retrieval.search(config.sandbox_id, {\n query,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n preset: preset ?? config.preset,\n });\n return mcpResult({ answer: response.answer });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n","import { z } from 'zod';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\nimport type { WindowRegistry } from '../windows.js';\n\ninterface ContextWindowDeps {\n client: CopassClient;\n config: ServerConfig;\n windows: WindowRegistry;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerContextWindowTools(server: McpServer, deps: ContextWindowDeps): void {\n const { client, config, windows } = deps;\n\n server.registerTool(\n 'context_window_create',\n {\n description:\n 'Open a new Context Window — an ephemeral data source tracking this conversation. ' +\n 'Subsequent retrieval calls become automatically window-aware, and every turn you ' +\n 'record via `context_window_add_turn` is ingested into the graph so past turns ' +\n 'become retrievable. Returns a `data_source_id`; persist it on your side if you ' +\n 'may want to resume this conversation later via `context_window_attach`. Pass ' +\n '`participants` to set a default conversation roster — forwarded as the envelope ' +\n '`participants` field on every turn so downstream extraction can resolve ' +\n 'second-person pronouns (\"your X\" → the OTHER listed participant).',\n inputSchema: {\n project_id: z.string().optional().describe('Override the server default project_id.'),\n name: z.string().optional().describe('Optional stable name for the underlying data source.'),\n participants: z\n .array(z.string())\n .optional()\n .describe(\n 'Default conversation participant roster (e.g. [\"User\", \"Assistant\"]). ' +\n 'Forwarded on every turn unless add_turn passes its own override.',\n ),\n },\n },\n async ({ project_id, name, participants }) => {\n try {\n const window = await client.contextWindow.create({\n sandbox_id: config.sandbox_id,\n project_id: project_id ?? config.project_id,\n name,\n participants,\n });\n windows.set(window);\n return mcpResult({\n data_source_id: window.dataSourceId,\n active: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_add_turn',\n {\n description:\n 'Append a turn to the active Context Window and push it into the graph. Call on ' +\n 'every user message AND every assistant message so the thread itself becomes ' +\n 'retrievable. If you omit `data_source_id`, the active window is used. Pass `name` ' +\n 'when the speaker has a richer identity than `role` alone (e.g. an actual user name ' +\n 'like \"Alice\"); falls back to capitalized `role` when absent. Pass `participants` ' +\n 'to override the window\\'s default roster for this turn only.',\n inputSchema: {\n role: z\n .enum(['user', 'assistant', 'system'])\n .describe('Role of the speaker for this turn.'),\n content: z.string().describe('The turn content.'),\n name: z\n .string()\n .optional()\n .describe(\n 'Optional named participant for this turn — forwarded as the envelope ' +\n '`speaker`. Caller-decided literal (e.g. \"Alice\", \"support-bot\"). When ' +\n 'omitted, falls back to the capitalized role.',\n ),\n participants: z\n .array(z.string())\n .optional()\n .describe(\n 'Per-turn participants override. Falls back to the window\\'s ' +\n 'constructor-time roster when omitted.',\n ),\n data_source_id: z\n .string()\n .optional()\n .describe('Override the active window id (for multi-window use cases).'),\n },\n },\n async ({ role, content, name, participants, data_source_id }) => {\n try {\n const window = windows.resolve(data_source_id);\n if (!window) {\n throw new Error(\n 'No active Context Window — call context_window_create or context_window_attach first.',\n );\n }\n await window.addTurn({ role, content, name }, { participants });\n return mcpResult({\n data_source_id: window.dataSourceId,\n turn_count: window.getTurns().length,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_attach',\n {\n description:\n 'Resume an existing Context Window by `data_source_id` (one you previously persisted ' +\n 'on your side after `context_window_create`). Makes the resumed window the active ' +\n 'window. Pass `initial_turns` to seed the local buffer for immediate window-aware ' +\n 'retrieval, otherwise the buffer starts empty and fills as you call add_turn.',\n inputSchema: {\n data_source_id: z.string().describe('Id of the Context Window data source to resume.'),\n initial_turns: z\n .array(\n z.object({\n role: z.enum(['user', 'assistant', 'system']),\n content: z.string(),\n name: z.string().optional(),\n }),\n )\n .optional()\n .describe('Optional pre-existing turns to seed the local buffer.'),\n participants: z\n .array(z.string())\n .optional()\n .describe(\n 'Default conversation participant roster — forwarded on every turn ' +\n 'pushed via add_turn unless that call overrides.',\n ),\n },\n },\n async ({ data_source_id, initial_turns, participants }) => {\n try {\n const window = await client.contextWindow.attach({\n sandbox_id: config.sandbox_id,\n data_source_id,\n initialTurns: initial_turns,\n participants,\n });\n windows.set(window);\n return mcpResult({\n data_source_id: window.dataSourceId,\n turn_count: window.getTurns().length,\n active: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_close',\n {\n description:\n 'Close a Context Window — flips the underlying data source to `disconnected` ' +\n 'immediately instead of waiting for TTL. Best-effort; idempotent. If you omit ' +\n '`data_source_id`, the active window is closed.',\n inputSchema: {\n data_source_id: z\n .string()\n .optional()\n .describe('Override the active window id.'),\n },\n },\n async ({ data_source_id }) => {\n try {\n const window = windows.resolve(data_source_id);\n if (!window) {\n throw new Error('No active Context Window to close.');\n }\n await window.close();\n windows.drop(window.dataSourceId);\n return mcpResult({\n data_source_id: window.dataSourceId,\n closed: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n","import { z } from 'zod';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\n\ninterface IngestDeps {\n client: CopassClient;\n config: ServerConfig;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerIngestTool(server: McpServer, deps: IngestDeps): void {\n const { client, config } = deps;\n\n server.registerTool(\n 'ingest',\n {\n description:\n 'Push content into the knowledge graph via a data source. Use for: architecture ' +\n 'decisions (source_type: \"decision\"), user-shared context (\"user_input\"), ' +\n 'corrections (\"correction\"), durable notes, any significant new concept. Do NOT ' +\n 'ingest trivial changes or ephemeral debug context — those belong in the Context ' +\n 'Window (via context_window_add_turn). Pass `data_source_id` explicitly or set ' +\n '`COPASS_INGEST_DATA_SOURCE_ID` in the server env for an implicit target.',\n inputSchema: {\n content: z.string().min(1).describe('The content to ingest.'),\n source_type: z\n .string()\n .optional()\n .describe(\n 'Type tag: code, markdown, json, text, conversation, decision, correction, ' +\n 'user_input. Defaults to \"text\" when omitted.',\n ),\n data_source_id: z\n .string()\n .optional()\n .describe(\n 'Target data source. Falls back to COPASS_INGEST_DATA_SOURCE_ID env var.',\n ),\n project_id: z.string().optional().describe('Override the server default project_id.'),\n storage_only: z\n .boolean()\n .optional()\n .describe('If true, chunk and store but skip ontology ingestion.'),\n occurred_at: z\n .string()\n .optional()\n .describe(\n 'Optional ISO 8601 timestamp anchoring the content to a real-world moment ' +\n '(e.g. when this decision was made, when the user said it). Used as the ' +\n 'default occurred_at for any composed event whose own timestamp is null, ' +\n 'so temporal queries can find it.',\n ),\n },\n },\n async ({ content, source_type, data_source_id, project_id, storage_only, occurred_at }) => {\n try {\n const sourceId = data_source_id ?? config.ingest_data_source_id;\n if (!sourceId) {\n throw new Error(\n 'ingest requires a `data_source_id` argument or COPASS_INGEST_DATA_SOURCE_ID env var. ' +\n 'Register a data source via the REST API or CLI, then pass its id here.',\n );\n }\n\n const response = await client.sources.ingest(config.sandbox_id, sourceId, {\n text: content,\n source_type: source_type ?? 'text',\n project_id: project_id ?? config.project_id,\n storage_only,\n occurred_at,\n });\n\n return mcpResult({\n job_id: response.job_id,\n status: response.status,\n data_source_id: sourceId,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n"],"mappings":"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","names":["import_mcp","import_zod","mcpResult","mcpError","import_zod","mcpResult","mcpError"]} |
+26
-11
@@ -298,9 +298,12 @@ #!/usr/bin/env node | ||
| { | ||
| description: "Open a new Context Window \u2014 an ephemeral data source tracking this conversation. Subsequent retrieval calls become automatically window-aware, and every turn you record via `context_window_add_turn` is ingested into the graph so past turns become retrievable. Returns a `data_source_id`; persist it on your side if you may want to resume this conversation later via `context_window_attach`.", | ||
| description: 'Open a new Context Window \u2014 an ephemeral data source tracking this conversation. Subsequent retrieval calls become automatically window-aware, and every turn you record via `context_window_add_turn` is ingested into the graph so past turns become retrievable. Returns a `data_source_id`; persist it on your side if you may want to resume this conversation later via `context_window_attach`. Pass `participants` to set a default conversation roster \u2014 forwarded as the envelope `participants` field on every turn so downstream extraction can resolve second-person pronouns ("your X" \u2192 the OTHER listed participant).', | ||
| inputSchema: { | ||
| project_id: z2.string().optional().describe("Override the server default project_id."), | ||
| name: z2.string().optional().describe("Optional stable name for the underlying data source.") | ||
| name: z2.string().optional().describe("Optional stable name for the underlying data source."), | ||
| participants: z2.array(z2.string()).optional().describe( | ||
| 'Default conversation participant roster (e.g. ["User", "Assistant"]). Forwarded on every turn unless add_turn passes its own override.' | ||
| ) | ||
| } | ||
| }, | ||
| async ({ project_id, name }) => { | ||
| async ({ project_id, name, participants }) => { | ||
| try { | ||
@@ -310,3 +313,4 @@ const window = await client.contextWindow.create({ | ||
| project_id: project_id ?? config.project_id, | ||
| name | ||
| name, | ||
| participants | ||
| }); | ||
@@ -326,10 +330,16 @@ windows.set(window); | ||
| { | ||
| description: "Append a turn to the active Context Window and push it into the graph. Call on every user message AND every assistant message so the thread itself becomes retrievable. If you omit `data_source_id`, the active window is used.", | ||
| description: 'Append a turn to the active Context Window and push it into the graph. Call on every user message AND every assistant message so the thread itself becomes retrievable. If you omit `data_source_id`, the active window is used. Pass `name` when the speaker has a richer identity than `role` alone (e.g. an actual user name like "Alice"); falls back to capitalized `role` when absent. Pass `participants` to override the window\'s default roster for this turn only.', | ||
| inputSchema: { | ||
| role: z2.enum(["user", "assistant", "system"]).describe("Role of the speaker for this turn."), | ||
| content: z2.string().describe("The turn content."), | ||
| name: z2.string().optional().describe( | ||
| 'Optional named participant for this turn \u2014 forwarded as the envelope `speaker`. Caller-decided literal (e.g. "Alice", "support-bot"). When omitted, falls back to the capitalized role.' | ||
| ), | ||
| participants: z2.array(z2.string()).optional().describe( | ||
| "Per-turn participants override. Falls back to the window's constructor-time roster when omitted." | ||
| ), | ||
| data_source_id: z2.string().optional().describe("Override the active window id (for multi-window use cases).") | ||
| } | ||
| }, | ||
| async ({ role, content, data_source_id }) => { | ||
| async ({ role, content, name, participants, data_source_id }) => { | ||
| try { | ||
@@ -342,3 +352,3 @@ const window = windows.resolve(data_source_id); | ||
| } | ||
| await window.addTurn({ role, content }); | ||
| await window.addTurn({ role, content, name }, { participants }); | ||
| return mcpResult2({ | ||
@@ -362,8 +372,12 @@ data_source_id: window.dataSourceId, | ||
| role: z2.enum(["user", "assistant", "system"]), | ||
| content: z2.string() | ||
| content: z2.string(), | ||
| name: z2.string().optional() | ||
| }) | ||
| ).optional().describe("Optional pre-existing turns to seed the local buffer.") | ||
| ).optional().describe("Optional pre-existing turns to seed the local buffer."), | ||
| participants: z2.array(z2.string()).optional().describe( | ||
| "Default conversation participant roster \u2014 forwarded on every turn pushed via add_turn unless that call overrides." | ||
| ) | ||
| } | ||
| }, | ||
| async ({ data_source_id, initial_turns }) => { | ||
| async ({ data_source_id, initial_turns, participants }) => { | ||
| try { | ||
@@ -373,3 +387,4 @@ const window = await client.contextWindow.attach({ | ||
| data_source_id, | ||
| initialTurns: initial_turns | ||
| initialTurns: initial_turns, | ||
| participants | ||
| }); | ||
@@ -376,0 +391,0 @@ windows.set(window); |
+1
-1
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/bin.ts","../src/config.ts","../src/server.ts","../src/windows.ts","../src/tools/retrieval.ts","../src/tools/context-window.ts","../src/tools/ingest.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { CopassClient } from '@copass/core';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { loadConfig } from './config.js';\nimport { buildServer } from './server.js';\nimport { WindowRegistry } from './windows.js';\n\nasync function main(): Promise<void> {\n const config = loadConfig();\n // loadConfig() enforces api_key at runtime; the static type marks it\n // optional so embedded callers (buildServer({ client })) don't have\n // to pass one. The bin path always has it.\n const apiKey = config.api_key!;\n\n const client = new CopassClient({\n apiUrl: config.api_url,\n auth: apiKey.startsWith('olk_')\n ? { type: 'api-key', key: apiKey }\n : { type: 'bearer', token: apiKey },\n });\n\n const windows = new WindowRegistry();\n\n // Pre-attach to a Context Window if the caller passed one via env var.\n // Lets a parent process (e.g. an HTTP server) own window lifecycle and share\n // it across multiple MCP subprocess spawns.\n if (config.context_window_id) {\n try {\n const window = await client.contextWindow.attach({\n sandbox_id: config.sandbox_id,\n data_source_id: config.context_window_id,\n initialTurns: config.context_window_initial_turns,\n });\n windows.set(window);\n const turnCount = config.context_window_initial_turns?.length ?? 0;\n process.stderr.write(\n `copass-mcp: attached to context window ${config.context_window_id}` +\n (turnCount > 0 ? ` (${turnCount} initial turn${turnCount === 1 ? '' : 's'})` : '') +\n '\\n',\n );\n } catch (err) {\n process.stderr.write(\n `copass-mcp: failed to attach to COPASS_CONTEXT_WINDOW_ID=${config.context_window_id}: ${\n err instanceof Error ? err.message : String(err)\n }\\n`,\n );\n throw err;\n }\n }\n\n const server = buildServer({ client, config, windows });\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n process.stderr.write(\n `copass-mcp: started (sandbox=${config.sandbox_id}, preset=${config.preset})\\n`,\n );\n}\n\nmain().catch((err) => {\n process.stderr.write(`copass-mcp: fatal: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n});\n","import type { ChatMessage, SearchPreset } from '@copass/core';\n\nexport interface ServerConfig {\n api_url: string;\n /**\n * Required when the standalone `copass-mcp` bin builds its own\n * `CopassClient` from this config (`bin.ts`). Optional for embedded\n * use — when a host process (e.g. another MCP server) constructs the\n * `CopassClient` itself and passes it into `buildServer({ client })`,\n * the tool handlers never read this field.\n */\n api_key?: string;\n sandbox_id: string;\n project_id?: string;\n preset: SearchPreset;\n /** Default data_source_id for `ingest` when the caller doesn't pass one. */\n ingest_data_source_id?: string;\n /**\n * If set, the server attaches to this Context Window on startup and makes\n * it the active window. Use when another process (e.g. a Hono server)\n * already created the window and you're launching the MCP server as a\n * subprocess that should share it.\n */\n context_window_id?: string;\n /**\n * Optional pre-existing turns to seed the Context Window's buffer on\n * startup. Makes retrieval immediately window-aware on the first tool\n * call — without these, the first `discover` / `interpret` / `search`\n * after a subprocess spawn sees an empty history.\n *\n * Required when `context_window_id` refers to a thread that has prior\n * turns and the server should dedupe retrieval against them.\n */\n context_window_initial_turns?: ChatMessage[];\n}\n\nconst VALID_PRESETS: readonly SearchPreset[] = [\n // Canonical names\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n // `:thinking` variants are /search-only. Setting one of these as the\n // MCP subprocess default makes `interpret` fail (decomposition isn't\n // valid on /interpret) — fine when the subprocess is only used for\n // `search`, otherwise override per-call via the `preset` tool arg.\n 'copass/copass_1.0:thinking',\n 'copass/copass_2.0:thinking',\n 'copass/1.0:thinking',\n 'copass/2.0:thinking',\n] as const;\n\n/**\n * Read config from `process.env`.\n *\n * - `COPASS_API_KEY` (required)\n * - `COPASS_SANDBOX_ID` (required)\n * - `COPASS_API_URL` (default: https://ai.copass.id)\n * - `COPASS_PROJECT_ID` (optional — default for retrieval/ingest)\n * - `COPASS_PRESET` (default: `copass/copass_1.0`). Accepts any\n * `SearchPreset` (including the `copass/X.0` short aliases), but\n * `:thinking` variants only work on `/search` — setting one as the\n * subprocess default breaks `interpret`. Leave at `copass/copass_1.0`\n * unless the subprocess is search-only, and override per-call via\n * the `preset` tool arg.\n *\n * Throws a descriptive error for missing/invalid values so the MCP client\n * sees an immediate startup failure with actionable text.\n */\nexport function loadConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig {\n const missing: string[] = [];\n const api_key = env.COPASS_API_KEY ?? '';\n const sandbox_id = env.COPASS_SANDBOX_ID ?? '';\n\n if (!api_key) missing.push('COPASS_API_KEY');\n if (!sandbox_id) missing.push('COPASS_SANDBOX_ID');\n\n if (missing.length > 0) {\n throw new Error(\n `@copass/mcp: missing required env var(s): ${missing.join(', ')}. ` +\n `Set them before launching the server.`,\n );\n }\n\n const api_url = env.COPASS_API_URL?.trim() || 'https://ai.copass.id';\n const project_id = env.COPASS_PROJECT_ID?.trim() || undefined;\n const rawPreset = env.COPASS_PRESET?.trim() || 'copass/copass_1.0';\n\n if (!VALID_PRESETS.includes(rawPreset as SearchPreset)) {\n throw new Error(\n `@copass/mcp: COPASS_PRESET must be one of ${VALID_PRESETS.join(', ')}; got \"${rawPreset}\"`,\n );\n }\n\n const ingest_data_source_id = env.COPASS_INGEST_DATA_SOURCE_ID?.trim() || undefined;\n const context_window_id = env.COPASS_CONTEXT_WINDOW_ID?.trim() || undefined;\n const context_window_initial_turns = parseInitialTurns(\n env.COPASS_CONTEXT_WINDOW_INITIAL_TURNS,\n );\n\n return {\n api_url,\n api_key,\n sandbox_id,\n project_id,\n preset: rawPreset as SearchPreset,\n ingest_data_source_id,\n context_window_id,\n context_window_initial_turns,\n };\n}\n\nfunction parseInitialTurns(raw: string | undefined): ChatMessage[] | undefined {\n if (!raw || !raw.trim()) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS is not valid JSON: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n if (!Array.isArray(parsed)) {\n throw new Error(\n '@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS must be a JSON array of {role, content} objects',\n );\n }\n const turns: ChatMessage[] = [];\n for (const [i, entry] of parsed.entries()) {\n if (\n !entry ||\n typeof entry !== 'object' ||\n typeof (entry as { role?: unknown }).role !== 'string' ||\n typeof (entry as { content?: unknown }).content !== 'string'\n ) {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS[${i}] must be {role: string, content: string}`,\n );\n }\n const { role, content } = entry as { role: string; content: string };\n if (role !== 'user' && role !== 'assistant' && role !== 'system') {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS[${i}].role must be \"user\" | \"assistant\" | \"system\"; got \"${role}\"`,\n );\n }\n turns.push({ role, content });\n }\n return turns;\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CopassClient } from '@copass/core';\nimport { registerToMcpServer } from '@copass/management/adapters/mcp';\nimport type { ServerConfig } from './config.js';\nimport { WindowRegistry } from './windows.js';\nimport { registerRetrievalTools } from './tools/retrieval.js';\nimport { registerContextWindowTools } from './tools/context-window.js';\nimport { registerIngestTool } from './tools/ingest.js';\n\nexport interface BuildServerOptions {\n client: CopassClient;\n config: ServerConfig;\n /**\n * Optional pre-populated window registry. Useful when a parent process has\n * already created or attached to a Context Window and wants the server to\n * start with it as the active window.\n */\n windows?: WindowRegistry;\n}\n\n/**\n * Assemble the Copass MCP server with every tool registered. Doesn't connect\n * a transport — see `startStdioServer` in `./bin.ts` for the default path,\n * or wire your own transport if embedding.\n */\nexport function buildServer({ client, config, windows }: BuildServerOptions): McpServer {\n const server = new McpServer(\n {\n name: 'copass',\n version: '0.3.0',\n },\n {\n capabilities: { tools: {} },\n },\n );\n\n const registry = windows ?? new WindowRegistry();\n const deps = { client, config, windows: registry };\n\n registerRetrievalTools(server, deps);\n registerContextWindowTools(server, deps);\n registerIngestTool(server, { client, config });\n\n // The full management tool corpus is exposed regardless of sandbox role.\n // Viewer-role users see write tools but get structured 403 from the\n // service layer when they call them. Avoids an extra HTTP round-trip\n // at MCP startup. See ADR 0007 Phase 4 brief §10 for trade-off.\n registerToMcpServer(server, client, { sandboxId: config.sandbox_id });\n\n return server;\n}\n","import type { ContextWindow } from '@copass/core';\n\n/**\n * In-memory registry of open Context Windows.\n *\n * Tracks a map of `data_source_id → ContextWindow` plus a single \"active\"\n * window id. Retrieval and add-turn tools use the active window implicitly\n * so the LLM doesn't have to thread an id on every call.\n *\n * For multi-window use cases, callers pass `data_source_id` explicitly and\n * the active id is ignored.\n */\nexport class WindowRegistry {\n private readonly windows = new Map<string, ContextWindow>();\n private activeId: string | null = null;\n\n /** Register a new window and make it active. */\n set(window: ContextWindow): void {\n this.windows.set(window.dataSourceId, window);\n this.activeId = window.dataSourceId;\n }\n\n /** Look up a window by id, or fall back to the active one. */\n resolve(dataSourceId?: string): ContextWindow | undefined {\n const id = dataSourceId ?? this.activeId;\n return id ? this.windows.get(id) : undefined;\n }\n\n /** Make an existing window the active one. Throws if unknown. */\n activate(dataSourceId: string): ContextWindow {\n const window = this.windows.get(dataSourceId);\n if (!window) {\n throw new Error(\n `No window registered for data_source_id=\"${dataSourceId}\" — call context_window_create or context_window_attach first.`,\n );\n }\n this.activeId = dataSourceId;\n return window;\n }\n\n /** Drop a window from the registry. Clears active if it matched. */\n drop(dataSourceId: string): void {\n this.windows.delete(dataSourceId);\n if (this.activeId === dataSourceId) this.activeId = null;\n }\n\n get activeDataSourceId(): string | null {\n return this.activeId;\n }\n}\n","import { z } from 'zod';\nimport {\n DISCOVER_QUERY_PARAM,\n INTERPRET_DESCRIPTION,\n INTERPRET_ITEMS_PARAM,\n INTERPRET_QUERY_PARAM,\n MCP_DISCOVER_DESCRIPTION,\n PRESET_PARAM,\n PROJECT_ID_PARAM,\n SEARCH_DESCRIPTION,\n SEARCH_QUERY_PARAM,\n} from '@copass/config';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\nimport type { WindowRegistry } from '../windows.js';\n\ninterface RetrievalDeps {\n client: CopassClient;\n config: ServerConfig;\n windows: WindowRegistry;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerRetrievalTools(server: McpServer, deps: RetrievalDeps): void {\n const { client, config, windows } = deps;\n\n server.registerTool(\n 'discover',\n {\n description: MCP_DISCOVER_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(DISCOVER_QUERY_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n // Per-call preset override. `:thinking` variants are\n // /search-only and rejected on /discover, so the enum here\n // intentionally omits them.\n preset: z\n .enum([\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n ])\n .optional()\n .describe(PRESET_PARAM),\n },\n },\n async ({ query, project_id, preset }) => {\n try {\n const response = await client.retrieval.discover(config.sandbox_id, {\n query,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n // Per-call override wins; otherwise inherit the subprocess\n // default. `:thinking` variants are stripped at the server\n // (/discover rejects them) so we don't second-guess here.\n preset: preset ?? config.preset,\n });\n return mcpResult({\n header: response.header,\n // Project the v2 fields (`subgraph` + `matched_query_nodes`)\n // alongside the v1 fields. Populated only under\n // `copass/copass_2.0` (or its `copass/2.0` alias); `null`\n // under v1.\n items: response.items.map((item) => ({\n score: item.score,\n summary: item.summary,\n canonical_ids: item.canonical_ids,\n subgraph: item.subgraph ?? null,\n matched_query_nodes: item.matched_query_nodes ?? null,\n })),\n next_steps: response.next_steps,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'interpret',\n {\n description: INTERPRET_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(INTERPRET_QUERY_PARAM),\n items: z.array(z.array(z.string()).min(1)).min(1).describe(INTERPRET_ITEMS_PARAM),\n preset: z.enum([\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n ]).optional().describe(PRESET_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n },\n },\n async ({ query, items, preset, project_id }) => {\n try {\n const response = await client.retrieval.interpret(config.sandbox_id, {\n query,\n items,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n preset: preset ?? config.preset,\n });\n return mcpResult({ brief: response.brief });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'search',\n {\n description: SEARCH_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(SEARCH_QUERY_PARAM),\n // `:thinking` variants split the question into sub-questions and\n // run the base preset on each before a combined synthesis.\n preset: z\n .enum([\n // Canonical names\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n 'copass/copass_1.0:thinking',\n 'copass/copass_2.0:thinking',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n 'copass/1.0:thinking',\n 'copass/2.0:thinking',\n ])\n .optional()\n .describe(PRESET_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n },\n },\n async ({ query, preset, project_id }) => {\n try {\n const response = await client.retrieval.search(config.sandbox_id, {\n query,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n preset: preset ?? config.preset,\n });\n return mcpResult({ answer: response.answer });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n","import { z } from 'zod';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\nimport type { WindowRegistry } from '../windows.js';\n\ninterface ContextWindowDeps {\n client: CopassClient;\n config: ServerConfig;\n windows: WindowRegistry;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerContextWindowTools(server: McpServer, deps: ContextWindowDeps): void {\n const { client, config, windows } = deps;\n\n server.registerTool(\n 'context_window_create',\n {\n description:\n 'Open a new Context Window — an ephemeral data source tracking this conversation. ' +\n 'Subsequent retrieval calls become automatically window-aware, and every turn you ' +\n 'record via `context_window_add_turn` is ingested into the graph so past turns ' +\n 'become retrievable. Returns a `data_source_id`; persist it on your side if you ' +\n 'may want to resume this conversation later via `context_window_attach`.',\n inputSchema: {\n project_id: z.string().optional().describe('Override the server default project_id.'),\n name: z.string().optional().describe('Optional stable name for the underlying data source.'),\n },\n },\n async ({ project_id, name }) => {\n try {\n const window = await client.contextWindow.create({\n sandbox_id: config.sandbox_id,\n project_id: project_id ?? config.project_id,\n name,\n });\n windows.set(window);\n return mcpResult({\n data_source_id: window.dataSourceId,\n active: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_add_turn',\n {\n description:\n 'Append a turn to the active Context Window and push it into the graph. Call on ' +\n 'every user message AND every assistant message so the thread itself becomes ' +\n 'retrievable. If you omit `data_source_id`, the active window is used.',\n inputSchema: {\n role: z\n .enum(['user', 'assistant', 'system'])\n .describe('Role of the speaker for this turn.'),\n content: z.string().describe('The turn content.'),\n data_source_id: z\n .string()\n .optional()\n .describe('Override the active window id (for multi-window use cases).'),\n },\n },\n async ({ role, content, data_source_id }) => {\n try {\n const window = windows.resolve(data_source_id);\n if (!window) {\n throw new Error(\n 'No active Context Window — call context_window_create or context_window_attach first.',\n );\n }\n await window.addTurn({ role, content });\n return mcpResult({\n data_source_id: window.dataSourceId,\n turn_count: window.getTurns().length,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_attach',\n {\n description:\n 'Resume an existing Context Window by `data_source_id` (one you previously persisted ' +\n 'on your side after `context_window_create`). Makes the resumed window the active ' +\n 'window. Pass `initial_turns` to seed the local buffer for immediate window-aware ' +\n 'retrieval, otherwise the buffer starts empty and fills as you call add_turn.',\n inputSchema: {\n data_source_id: z.string().describe('Id of the Context Window data source to resume.'),\n initial_turns: z\n .array(\n z.object({\n role: z.enum(['user', 'assistant', 'system']),\n content: z.string(),\n }),\n )\n .optional()\n .describe('Optional pre-existing turns to seed the local buffer.'),\n },\n },\n async ({ data_source_id, initial_turns }) => {\n try {\n const window = await client.contextWindow.attach({\n sandbox_id: config.sandbox_id,\n data_source_id,\n initialTurns: initial_turns,\n });\n windows.set(window);\n return mcpResult({\n data_source_id: window.dataSourceId,\n turn_count: window.getTurns().length,\n active: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_close',\n {\n description:\n 'Close a Context Window — flips the underlying data source to `disconnected` ' +\n 'immediately instead of waiting for TTL. Best-effort; idempotent. If you omit ' +\n '`data_source_id`, the active window is closed.',\n inputSchema: {\n data_source_id: z\n .string()\n .optional()\n .describe('Override the active window id.'),\n },\n },\n async ({ data_source_id }) => {\n try {\n const window = windows.resolve(data_source_id);\n if (!window) {\n throw new Error('No active Context Window to close.');\n }\n await window.close();\n windows.drop(window.dataSourceId);\n return mcpResult({\n data_source_id: window.dataSourceId,\n closed: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n","import { z } from 'zod';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\n\ninterface IngestDeps {\n client: CopassClient;\n config: ServerConfig;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerIngestTool(server: McpServer, deps: IngestDeps): void {\n const { client, config } = deps;\n\n server.registerTool(\n 'ingest',\n {\n description:\n 'Push content into the knowledge graph via a data source. Use for: architecture ' +\n 'decisions (source_type: \"decision\"), user-shared context (\"user_input\"), ' +\n 'corrections (\"correction\"), durable notes, any significant new concept. Do NOT ' +\n 'ingest trivial changes or ephemeral debug context — those belong in the Context ' +\n 'Window (via context_window_add_turn). Pass `data_source_id` explicitly or set ' +\n '`COPASS_INGEST_DATA_SOURCE_ID` in the server env for an implicit target.',\n inputSchema: {\n content: z.string().min(1).describe('The content to ingest.'),\n source_type: z\n .string()\n .optional()\n .describe(\n 'Type tag: code, markdown, json, text, conversation, decision, correction, ' +\n 'user_input. Defaults to \"text\" when omitted.',\n ),\n data_source_id: z\n .string()\n .optional()\n .describe(\n 'Target data source. Falls back to COPASS_INGEST_DATA_SOURCE_ID env var.',\n ),\n project_id: z.string().optional().describe('Override the server default project_id.'),\n storage_only: z\n .boolean()\n .optional()\n .describe('If true, chunk and store but skip ontology ingestion.'),\n occurred_at: z\n .string()\n .optional()\n .describe(\n 'Optional ISO 8601 timestamp anchoring the content to a real-world moment ' +\n '(e.g. when this decision was made, when the user said it). Used as the ' +\n 'default occurred_at for any composed event whose own timestamp is null, ' +\n 'so temporal queries can find it.',\n ),\n },\n },\n async ({ content, source_type, data_source_id, project_id, storage_only, occurred_at }) => {\n try {\n const sourceId = data_source_id ?? config.ingest_data_source_id;\n if (!sourceId) {\n throw new Error(\n 'ingest requires a `data_source_id` argument or COPASS_INGEST_DATA_SOURCE_ID env var. ' +\n 'Register a data source via the REST API or CLI, then pass its id here.',\n );\n }\n\n const response = await client.sources.ingest(config.sandbox_id, sourceId, {\n text: content,\n source_type: source_type ?? 'text',\n project_id: project_id ?? config.project_id,\n storage_only,\n occurred_at,\n });\n\n return mcpResult({\n job_id: response.job_id,\n status: response.status,\n data_source_id: sourceId,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n"],"mappings":"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","names":["z","mcpResult","mcpError","z","mcpResult","mcpError"]} | ||
| {"version":3,"sources":["../src/bin.ts","../src/config.ts","../src/server.ts","../src/windows.ts","../src/tools/retrieval.ts","../src/tools/context-window.ts","../src/tools/ingest.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { CopassClient } from '@copass/core';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { loadConfig } from './config.js';\nimport { buildServer } from './server.js';\nimport { WindowRegistry } from './windows.js';\n\nasync function main(): Promise<void> {\n const config = loadConfig();\n // loadConfig() enforces api_key at runtime; the static type marks it\n // optional so embedded callers (buildServer({ client })) don't have\n // to pass one. The bin path always has it.\n const apiKey = config.api_key!;\n\n const client = new CopassClient({\n apiUrl: config.api_url,\n auth: apiKey.startsWith('olk_')\n ? { type: 'api-key', key: apiKey }\n : { type: 'bearer', token: apiKey },\n });\n\n const windows = new WindowRegistry();\n\n // Pre-attach to a Context Window if the caller passed one via env var.\n // Lets a parent process (e.g. an HTTP server) own window lifecycle and share\n // it across multiple MCP subprocess spawns.\n if (config.context_window_id) {\n try {\n const window = await client.contextWindow.attach({\n sandbox_id: config.sandbox_id,\n data_source_id: config.context_window_id,\n initialTurns: config.context_window_initial_turns,\n });\n windows.set(window);\n const turnCount = config.context_window_initial_turns?.length ?? 0;\n process.stderr.write(\n `copass-mcp: attached to context window ${config.context_window_id}` +\n (turnCount > 0 ? ` (${turnCount} initial turn${turnCount === 1 ? '' : 's'})` : '') +\n '\\n',\n );\n } catch (err) {\n process.stderr.write(\n `copass-mcp: failed to attach to COPASS_CONTEXT_WINDOW_ID=${config.context_window_id}: ${\n err instanceof Error ? err.message : String(err)\n }\\n`,\n );\n throw err;\n }\n }\n\n const server = buildServer({ client, config, windows });\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n process.stderr.write(\n `copass-mcp: started (sandbox=${config.sandbox_id}, preset=${config.preset})\\n`,\n );\n}\n\nmain().catch((err) => {\n process.stderr.write(`copass-mcp: fatal: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n});\n","import type { ChatMessage, SearchPreset } from '@copass/core';\n\nexport interface ServerConfig {\n api_url: string;\n /**\n * Required when the standalone `copass-mcp` bin builds its own\n * `CopassClient` from this config (`bin.ts`). Optional for embedded\n * use — when a host process (e.g. another MCP server) constructs the\n * `CopassClient` itself and passes it into `buildServer({ client })`,\n * the tool handlers never read this field.\n */\n api_key?: string;\n sandbox_id: string;\n project_id?: string;\n preset: SearchPreset;\n /** Default data_source_id for `ingest` when the caller doesn't pass one. */\n ingest_data_source_id?: string;\n /**\n * If set, the server attaches to this Context Window on startup and makes\n * it the active window. Use when another process (e.g. a Hono server)\n * already created the window and you're launching the MCP server as a\n * subprocess that should share it.\n */\n context_window_id?: string;\n /**\n * Optional pre-existing turns to seed the Context Window's buffer on\n * startup. Makes retrieval immediately window-aware on the first tool\n * call — without these, the first `discover` / `interpret` / `search`\n * after a subprocess spawn sees an empty history.\n *\n * Required when `context_window_id` refers to a thread that has prior\n * turns and the server should dedupe retrieval against them.\n */\n context_window_initial_turns?: ChatMessage[];\n}\n\nconst VALID_PRESETS: readonly SearchPreset[] = [\n // Canonical names\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n // `:thinking` variants are /search-only. Setting one of these as the\n // MCP subprocess default makes `interpret` fail (decomposition isn't\n // valid on /interpret) — fine when the subprocess is only used for\n // `search`, otherwise override per-call via the `preset` tool arg.\n 'copass/copass_1.0:thinking',\n 'copass/copass_2.0:thinking',\n 'copass/1.0:thinking',\n 'copass/2.0:thinking',\n] as const;\n\n/**\n * Read config from `process.env`.\n *\n * - `COPASS_API_KEY` (required)\n * - `COPASS_SANDBOX_ID` (required)\n * - `COPASS_API_URL` (default: https://ai.copass.id)\n * - `COPASS_PROJECT_ID` (optional — default for retrieval/ingest)\n * - `COPASS_PRESET` (default: `copass/copass_1.0`). Accepts any\n * `SearchPreset` (including the `copass/X.0` short aliases), but\n * `:thinking` variants only work on `/search` — setting one as the\n * subprocess default breaks `interpret`. Leave at `copass/copass_1.0`\n * unless the subprocess is search-only, and override per-call via\n * the `preset` tool arg.\n *\n * Throws a descriptive error for missing/invalid values so the MCP client\n * sees an immediate startup failure with actionable text.\n */\nexport function loadConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig {\n const missing: string[] = [];\n const api_key = env.COPASS_API_KEY ?? '';\n const sandbox_id = env.COPASS_SANDBOX_ID ?? '';\n\n if (!api_key) missing.push('COPASS_API_KEY');\n if (!sandbox_id) missing.push('COPASS_SANDBOX_ID');\n\n if (missing.length > 0) {\n throw new Error(\n `@copass/mcp: missing required env var(s): ${missing.join(', ')}. ` +\n `Set them before launching the server.`,\n );\n }\n\n const api_url = env.COPASS_API_URL?.trim() || 'https://ai.copass.id';\n const project_id = env.COPASS_PROJECT_ID?.trim() || undefined;\n const rawPreset = env.COPASS_PRESET?.trim() || 'copass/copass_1.0';\n\n if (!VALID_PRESETS.includes(rawPreset as SearchPreset)) {\n throw new Error(\n `@copass/mcp: COPASS_PRESET must be one of ${VALID_PRESETS.join(', ')}; got \"${rawPreset}\"`,\n );\n }\n\n const ingest_data_source_id = env.COPASS_INGEST_DATA_SOURCE_ID?.trim() || undefined;\n const context_window_id = env.COPASS_CONTEXT_WINDOW_ID?.trim() || undefined;\n const context_window_initial_turns = parseInitialTurns(\n env.COPASS_CONTEXT_WINDOW_INITIAL_TURNS,\n );\n\n return {\n api_url,\n api_key,\n sandbox_id,\n project_id,\n preset: rawPreset as SearchPreset,\n ingest_data_source_id,\n context_window_id,\n context_window_initial_turns,\n };\n}\n\nfunction parseInitialTurns(raw: string | undefined): ChatMessage[] | undefined {\n if (!raw || !raw.trim()) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS is not valid JSON: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n if (!Array.isArray(parsed)) {\n throw new Error(\n '@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS must be a JSON array of {role, content} objects',\n );\n }\n const turns: ChatMessage[] = [];\n for (const [i, entry] of parsed.entries()) {\n if (\n !entry ||\n typeof entry !== 'object' ||\n typeof (entry as { role?: unknown }).role !== 'string' ||\n typeof (entry as { content?: unknown }).content !== 'string'\n ) {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS[${i}] must be {role: string, content: string}`,\n );\n }\n const { role, content } = entry as { role: string; content: string };\n if (role !== 'user' && role !== 'assistant' && role !== 'system') {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS[${i}].role must be \"user\" | \"assistant\" | \"system\"; got \"${role}\"`,\n );\n }\n turns.push({ role, content });\n }\n return turns;\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CopassClient } from '@copass/core';\nimport { registerToMcpServer } from '@copass/management/adapters/mcp';\nimport type { ServerConfig } from './config.js';\nimport { WindowRegistry } from './windows.js';\nimport { registerRetrievalTools } from './tools/retrieval.js';\nimport { registerContextWindowTools } from './tools/context-window.js';\nimport { registerIngestTool } from './tools/ingest.js';\n\nexport interface BuildServerOptions {\n client: CopassClient;\n config: ServerConfig;\n /**\n * Optional pre-populated window registry. Useful when a parent process has\n * already created or attached to a Context Window and wants the server to\n * start with it as the active window.\n */\n windows?: WindowRegistry;\n}\n\n/**\n * Assemble the Copass MCP server with every tool registered. Doesn't connect\n * a transport — see `startStdioServer` in `./bin.ts` for the default path,\n * or wire your own transport if embedding.\n */\nexport function buildServer({ client, config, windows }: BuildServerOptions): McpServer {\n const server = new McpServer(\n {\n name: 'copass',\n version: '0.3.0',\n },\n {\n capabilities: { tools: {} },\n },\n );\n\n const registry = windows ?? new WindowRegistry();\n const deps = { client, config, windows: registry };\n\n registerRetrievalTools(server, deps);\n registerContextWindowTools(server, deps);\n registerIngestTool(server, { client, config });\n\n // The full management tool corpus is exposed regardless of sandbox role.\n // Viewer-role users see write tools but get structured 403 from the\n // service layer when they call them. Avoids an extra HTTP round-trip\n // at MCP startup. See ADR 0007 Phase 4 brief §10 for trade-off.\n registerToMcpServer(server, client, { sandboxId: config.sandbox_id });\n\n return server;\n}\n","import type { ContextWindow } from '@copass/core';\n\n/**\n * In-memory registry of open Context Windows.\n *\n * Tracks a map of `data_source_id → ContextWindow` plus a single \"active\"\n * window id. Retrieval and add-turn tools use the active window implicitly\n * so the LLM doesn't have to thread an id on every call.\n *\n * For multi-window use cases, callers pass `data_source_id` explicitly and\n * the active id is ignored.\n */\nexport class WindowRegistry {\n private readonly windows = new Map<string, ContextWindow>();\n private activeId: string | null = null;\n\n /** Register a new window and make it active. */\n set(window: ContextWindow): void {\n this.windows.set(window.dataSourceId, window);\n this.activeId = window.dataSourceId;\n }\n\n /** Look up a window by id, or fall back to the active one. */\n resolve(dataSourceId?: string): ContextWindow | undefined {\n const id = dataSourceId ?? this.activeId;\n return id ? this.windows.get(id) : undefined;\n }\n\n /** Make an existing window the active one. Throws if unknown. */\n activate(dataSourceId: string): ContextWindow {\n const window = this.windows.get(dataSourceId);\n if (!window) {\n throw new Error(\n `No window registered for data_source_id=\"${dataSourceId}\" — call context_window_create or context_window_attach first.`,\n );\n }\n this.activeId = dataSourceId;\n return window;\n }\n\n /** Drop a window from the registry. Clears active if it matched. */\n drop(dataSourceId: string): void {\n this.windows.delete(dataSourceId);\n if (this.activeId === dataSourceId) this.activeId = null;\n }\n\n get activeDataSourceId(): string | null {\n return this.activeId;\n }\n}\n","import { z } from 'zod';\nimport {\n DISCOVER_QUERY_PARAM,\n INTERPRET_DESCRIPTION,\n INTERPRET_ITEMS_PARAM,\n INTERPRET_QUERY_PARAM,\n MCP_DISCOVER_DESCRIPTION,\n PRESET_PARAM,\n PROJECT_ID_PARAM,\n SEARCH_DESCRIPTION,\n SEARCH_QUERY_PARAM,\n} from '@copass/config';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\nimport type { WindowRegistry } from '../windows.js';\n\ninterface RetrievalDeps {\n client: CopassClient;\n config: ServerConfig;\n windows: WindowRegistry;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerRetrievalTools(server: McpServer, deps: RetrievalDeps): void {\n const { client, config, windows } = deps;\n\n server.registerTool(\n 'discover',\n {\n description: MCP_DISCOVER_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(DISCOVER_QUERY_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n // Per-call preset override. `:thinking` variants are\n // /search-only and rejected on /discover, so the enum here\n // intentionally omits them.\n preset: z\n .enum([\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n ])\n .optional()\n .describe(PRESET_PARAM),\n },\n },\n async ({ query, project_id, preset }) => {\n try {\n const response = await client.retrieval.discover(config.sandbox_id, {\n query,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n // Per-call override wins; otherwise inherit the subprocess\n // default. `:thinking` variants are stripped at the server\n // (/discover rejects them) so we don't second-guess here.\n preset: preset ?? config.preset,\n });\n return mcpResult({\n header: response.header,\n // Project the v2 fields (`subgraph` + `matched_query_nodes`)\n // alongside the v1 fields. Populated only under\n // `copass/copass_2.0` (or its `copass/2.0` alias); `null`\n // under v1.\n items: response.items.map((item) => ({\n score: item.score,\n summary: item.summary,\n canonical_ids: item.canonical_ids,\n subgraph: item.subgraph ?? null,\n matched_query_nodes: item.matched_query_nodes ?? null,\n })),\n next_steps: response.next_steps,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'interpret',\n {\n description: INTERPRET_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(INTERPRET_QUERY_PARAM),\n items: z.array(z.array(z.string()).min(1)).min(1).describe(INTERPRET_ITEMS_PARAM),\n preset: z.enum([\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n ]).optional().describe(PRESET_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n },\n },\n async ({ query, items, preset, project_id }) => {\n try {\n const response = await client.retrieval.interpret(config.sandbox_id, {\n query,\n items,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n preset: preset ?? config.preset,\n });\n return mcpResult({ brief: response.brief });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'search',\n {\n description: SEARCH_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(SEARCH_QUERY_PARAM),\n // `:thinking` variants split the question into sub-questions and\n // run the base preset on each before a combined synthesis.\n preset: z\n .enum([\n // Canonical names\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n 'copass/copass_1.0:thinking',\n 'copass/copass_2.0:thinking',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n 'copass/1.0:thinking',\n 'copass/2.0:thinking',\n ])\n .optional()\n .describe(PRESET_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n },\n },\n async ({ query, preset, project_id }) => {\n try {\n const response = await client.retrieval.search(config.sandbox_id, {\n query,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n preset: preset ?? config.preset,\n });\n return mcpResult({ answer: response.answer });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n","import { z } from 'zod';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\nimport type { WindowRegistry } from '../windows.js';\n\ninterface ContextWindowDeps {\n client: CopassClient;\n config: ServerConfig;\n windows: WindowRegistry;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerContextWindowTools(server: McpServer, deps: ContextWindowDeps): void {\n const { client, config, windows } = deps;\n\n server.registerTool(\n 'context_window_create',\n {\n description:\n 'Open a new Context Window — an ephemeral data source tracking this conversation. ' +\n 'Subsequent retrieval calls become automatically window-aware, and every turn you ' +\n 'record via `context_window_add_turn` is ingested into the graph so past turns ' +\n 'become retrievable. Returns a `data_source_id`; persist it on your side if you ' +\n 'may want to resume this conversation later via `context_window_attach`. Pass ' +\n '`participants` to set a default conversation roster — forwarded as the envelope ' +\n '`participants` field on every turn so downstream extraction can resolve ' +\n 'second-person pronouns (\"your X\" → the OTHER listed participant).',\n inputSchema: {\n project_id: z.string().optional().describe('Override the server default project_id.'),\n name: z.string().optional().describe('Optional stable name for the underlying data source.'),\n participants: z\n .array(z.string())\n .optional()\n .describe(\n 'Default conversation participant roster (e.g. [\"User\", \"Assistant\"]). ' +\n 'Forwarded on every turn unless add_turn passes its own override.',\n ),\n },\n },\n async ({ project_id, name, participants }) => {\n try {\n const window = await client.contextWindow.create({\n sandbox_id: config.sandbox_id,\n project_id: project_id ?? config.project_id,\n name,\n participants,\n });\n windows.set(window);\n return mcpResult({\n data_source_id: window.dataSourceId,\n active: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_add_turn',\n {\n description:\n 'Append a turn to the active Context Window and push it into the graph. Call on ' +\n 'every user message AND every assistant message so the thread itself becomes ' +\n 'retrievable. If you omit `data_source_id`, the active window is used. Pass `name` ' +\n 'when the speaker has a richer identity than `role` alone (e.g. an actual user name ' +\n 'like \"Alice\"); falls back to capitalized `role` when absent. Pass `participants` ' +\n 'to override the window\\'s default roster for this turn only.',\n inputSchema: {\n role: z\n .enum(['user', 'assistant', 'system'])\n .describe('Role of the speaker for this turn.'),\n content: z.string().describe('The turn content.'),\n name: z\n .string()\n .optional()\n .describe(\n 'Optional named participant for this turn — forwarded as the envelope ' +\n '`speaker`. Caller-decided literal (e.g. \"Alice\", \"support-bot\"). When ' +\n 'omitted, falls back to the capitalized role.',\n ),\n participants: z\n .array(z.string())\n .optional()\n .describe(\n 'Per-turn participants override. Falls back to the window\\'s ' +\n 'constructor-time roster when omitted.',\n ),\n data_source_id: z\n .string()\n .optional()\n .describe('Override the active window id (for multi-window use cases).'),\n },\n },\n async ({ role, content, name, participants, data_source_id }) => {\n try {\n const window = windows.resolve(data_source_id);\n if (!window) {\n throw new Error(\n 'No active Context Window — call context_window_create or context_window_attach first.',\n );\n }\n await window.addTurn({ role, content, name }, { participants });\n return mcpResult({\n data_source_id: window.dataSourceId,\n turn_count: window.getTurns().length,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_attach',\n {\n description:\n 'Resume an existing Context Window by `data_source_id` (one you previously persisted ' +\n 'on your side after `context_window_create`). Makes the resumed window the active ' +\n 'window. Pass `initial_turns` to seed the local buffer for immediate window-aware ' +\n 'retrieval, otherwise the buffer starts empty and fills as you call add_turn.',\n inputSchema: {\n data_source_id: z.string().describe('Id of the Context Window data source to resume.'),\n initial_turns: z\n .array(\n z.object({\n role: z.enum(['user', 'assistant', 'system']),\n content: z.string(),\n name: z.string().optional(),\n }),\n )\n .optional()\n .describe('Optional pre-existing turns to seed the local buffer.'),\n participants: z\n .array(z.string())\n .optional()\n .describe(\n 'Default conversation participant roster — forwarded on every turn ' +\n 'pushed via add_turn unless that call overrides.',\n ),\n },\n },\n async ({ data_source_id, initial_turns, participants }) => {\n try {\n const window = await client.contextWindow.attach({\n sandbox_id: config.sandbox_id,\n data_source_id,\n initialTurns: initial_turns,\n participants,\n });\n windows.set(window);\n return mcpResult({\n data_source_id: window.dataSourceId,\n turn_count: window.getTurns().length,\n active: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_close',\n {\n description:\n 'Close a Context Window — flips the underlying data source to `disconnected` ' +\n 'immediately instead of waiting for TTL. Best-effort; idempotent. If you omit ' +\n '`data_source_id`, the active window is closed.',\n inputSchema: {\n data_source_id: z\n .string()\n .optional()\n .describe('Override the active window id.'),\n },\n },\n async ({ data_source_id }) => {\n try {\n const window = windows.resolve(data_source_id);\n if (!window) {\n throw new Error('No active Context Window to close.');\n }\n await window.close();\n windows.drop(window.dataSourceId);\n return mcpResult({\n data_source_id: window.dataSourceId,\n closed: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n","import { z } from 'zod';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\n\ninterface IngestDeps {\n client: CopassClient;\n config: ServerConfig;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerIngestTool(server: McpServer, deps: IngestDeps): void {\n const { client, config } = deps;\n\n server.registerTool(\n 'ingest',\n {\n description:\n 'Push content into the knowledge graph via a data source. Use for: architecture ' +\n 'decisions (source_type: \"decision\"), user-shared context (\"user_input\"), ' +\n 'corrections (\"correction\"), durable notes, any significant new concept. Do NOT ' +\n 'ingest trivial changes or ephemeral debug context — those belong in the Context ' +\n 'Window (via context_window_add_turn). Pass `data_source_id` explicitly or set ' +\n '`COPASS_INGEST_DATA_SOURCE_ID` in the server env for an implicit target.',\n inputSchema: {\n content: z.string().min(1).describe('The content to ingest.'),\n source_type: z\n .string()\n .optional()\n .describe(\n 'Type tag: code, markdown, json, text, conversation, decision, correction, ' +\n 'user_input. Defaults to \"text\" when omitted.',\n ),\n data_source_id: z\n .string()\n .optional()\n .describe(\n 'Target data source. Falls back to COPASS_INGEST_DATA_SOURCE_ID env var.',\n ),\n project_id: z.string().optional().describe('Override the server default project_id.'),\n storage_only: z\n .boolean()\n .optional()\n .describe('If true, chunk and store but skip ontology ingestion.'),\n occurred_at: z\n .string()\n .optional()\n .describe(\n 'Optional ISO 8601 timestamp anchoring the content to a real-world moment ' +\n '(e.g. when this decision was made, when the user said it). Used as the ' +\n 'default occurred_at for any composed event whose own timestamp is null, ' +\n 'so temporal queries can find it.',\n ),\n },\n },\n async ({ content, source_type, data_source_id, project_id, storage_only, occurred_at }) => {\n try {\n const sourceId = data_source_id ?? config.ingest_data_source_id;\n if (!sourceId) {\n throw new Error(\n 'ingest requires a `data_source_id` argument or COPASS_INGEST_DATA_SOURCE_ID env var. ' +\n 'Register a data source via the REST API or CLI, then pass its id here.',\n );\n }\n\n const response = await client.sources.ingest(config.sandbox_id, sourceId, {\n text: content,\n source_type: source_type ?? 'text',\n project_id: project_id ?? config.project_id,\n storage_only,\n occurred_at,\n });\n\n return mcpResult({\n job_id: response.job_id,\n status: response.status,\n data_source_id: sourceId,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n"],"mappings":"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","names":["z","mcpResult","mcpError","z","mcpResult","mcpError"]} |
+26
-11
@@ -228,9 +228,12 @@ "use strict"; | ||
| { | ||
| description: "Open a new Context Window \u2014 an ephemeral data source tracking this conversation. Subsequent retrieval calls become automatically window-aware, and every turn you record via `context_window_add_turn` is ingested into the graph so past turns become retrievable. Returns a `data_source_id`; persist it on your side if you may want to resume this conversation later via `context_window_attach`.", | ||
| description: 'Open a new Context Window \u2014 an ephemeral data source tracking this conversation. Subsequent retrieval calls become automatically window-aware, and every turn you record via `context_window_add_turn` is ingested into the graph so past turns become retrievable. Returns a `data_source_id`; persist it on your side if you may want to resume this conversation later via `context_window_attach`. Pass `participants` to set a default conversation roster \u2014 forwarded as the envelope `participants` field on every turn so downstream extraction can resolve second-person pronouns ("your X" \u2192 the OTHER listed participant).', | ||
| inputSchema: { | ||
| project_id: import_zod2.z.string().optional().describe("Override the server default project_id."), | ||
| name: import_zod2.z.string().optional().describe("Optional stable name for the underlying data source.") | ||
| name: import_zod2.z.string().optional().describe("Optional stable name for the underlying data source."), | ||
| participants: import_zod2.z.array(import_zod2.z.string()).optional().describe( | ||
| 'Default conversation participant roster (e.g. ["User", "Assistant"]). Forwarded on every turn unless add_turn passes its own override.' | ||
| ) | ||
| } | ||
| }, | ||
| async ({ project_id, name }) => { | ||
| async ({ project_id, name, participants }) => { | ||
| try { | ||
@@ -240,3 +243,4 @@ const window = await client.contextWindow.create({ | ||
| project_id: project_id ?? config.project_id, | ||
| name | ||
| name, | ||
| participants | ||
| }); | ||
@@ -256,10 +260,16 @@ windows.set(window); | ||
| { | ||
| description: "Append a turn to the active Context Window and push it into the graph. Call on every user message AND every assistant message so the thread itself becomes retrievable. If you omit `data_source_id`, the active window is used.", | ||
| description: 'Append a turn to the active Context Window and push it into the graph. Call on every user message AND every assistant message so the thread itself becomes retrievable. If you omit `data_source_id`, the active window is used. Pass `name` when the speaker has a richer identity than `role` alone (e.g. an actual user name like "Alice"); falls back to capitalized `role` when absent. Pass `participants` to override the window\'s default roster for this turn only.', | ||
| inputSchema: { | ||
| role: import_zod2.z.enum(["user", "assistant", "system"]).describe("Role of the speaker for this turn."), | ||
| content: import_zod2.z.string().describe("The turn content."), | ||
| name: import_zod2.z.string().optional().describe( | ||
| 'Optional named participant for this turn \u2014 forwarded as the envelope `speaker`. Caller-decided literal (e.g. "Alice", "support-bot"). When omitted, falls back to the capitalized role.' | ||
| ), | ||
| participants: import_zod2.z.array(import_zod2.z.string()).optional().describe( | ||
| "Per-turn participants override. Falls back to the window's constructor-time roster when omitted." | ||
| ), | ||
| data_source_id: import_zod2.z.string().optional().describe("Override the active window id (for multi-window use cases).") | ||
| } | ||
| }, | ||
| async ({ role, content, data_source_id }) => { | ||
| async ({ role, content, name, participants, data_source_id }) => { | ||
| try { | ||
@@ -272,3 +282,3 @@ const window = windows.resolve(data_source_id); | ||
| } | ||
| await window.addTurn({ role, content }); | ||
| await window.addTurn({ role, content, name }, { participants }); | ||
| return mcpResult2({ | ||
@@ -292,8 +302,12 @@ data_source_id: window.dataSourceId, | ||
| role: import_zod2.z.enum(["user", "assistant", "system"]), | ||
| content: import_zod2.z.string() | ||
| content: import_zod2.z.string(), | ||
| name: import_zod2.z.string().optional() | ||
| }) | ||
| ).optional().describe("Optional pre-existing turns to seed the local buffer.") | ||
| ).optional().describe("Optional pre-existing turns to seed the local buffer."), | ||
| participants: import_zod2.z.array(import_zod2.z.string()).optional().describe( | ||
| "Default conversation participant roster \u2014 forwarded on every turn pushed via add_turn unless that call overrides." | ||
| ) | ||
| } | ||
| }, | ||
| async ({ data_source_id, initial_turns }) => { | ||
| async ({ data_source_id, initial_turns, participants }) => { | ||
| try { | ||
@@ -303,3 +317,4 @@ const window = await client.contextWindow.attach({ | ||
| data_source_id, | ||
| initialTurns: initial_turns | ||
| initialTurns: initial_turns, | ||
| participants | ||
| }); | ||
@@ -306,0 +321,0 @@ windows.set(window); |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/index.ts","../src/server.ts","../src/windows.ts","../src/tools/retrieval.ts","../src/tools/context-window.ts","../src/tools/ingest.ts","../src/config.ts"],"sourcesContent":["export { buildServer } from './server.js';\nexport type { BuildServerOptions } from './server.js';\nexport { loadConfig } from './config.js';\nexport type { ServerConfig } from './config.js';\nexport { WindowRegistry } from './windows.js';\n\n// Sub-registrars — for callers that want to compose tools onto an\n// existing `McpServer` rather than spinning up a whole `buildServer()`.\n// Pick the tool groups you want and skip the rest (e.g. embed\n// `discover` / `interpret` / `search` without the SDK ingest when the\n// host process owns its own ingest tool).\nexport { registerRetrievalTools } from './tools/retrieval.js';\nexport { registerContextWindowTools } from './tools/context-window.js';\nexport { registerIngestTool } from './tools/ingest.js';\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CopassClient } from '@copass/core';\nimport { registerToMcpServer } from '@copass/management/adapters/mcp';\nimport type { ServerConfig } from './config.js';\nimport { WindowRegistry } from './windows.js';\nimport { registerRetrievalTools } from './tools/retrieval.js';\nimport { registerContextWindowTools } from './tools/context-window.js';\nimport { registerIngestTool } from './tools/ingest.js';\n\nexport interface BuildServerOptions {\n client: CopassClient;\n config: ServerConfig;\n /**\n * Optional pre-populated window registry. Useful when a parent process has\n * already created or attached to a Context Window and wants the server to\n * start with it as the active window.\n */\n windows?: WindowRegistry;\n}\n\n/**\n * Assemble the Copass MCP server with every tool registered. Doesn't connect\n * a transport — see `startStdioServer` in `./bin.ts` for the default path,\n * or wire your own transport if embedding.\n */\nexport function buildServer({ client, config, windows }: BuildServerOptions): McpServer {\n const server = new McpServer(\n {\n name: 'copass',\n version: '0.3.0',\n },\n {\n capabilities: { tools: {} },\n },\n );\n\n const registry = windows ?? new WindowRegistry();\n const deps = { client, config, windows: registry };\n\n registerRetrievalTools(server, deps);\n registerContextWindowTools(server, deps);\n registerIngestTool(server, { client, config });\n\n // The full management tool corpus is exposed regardless of sandbox role.\n // Viewer-role users see write tools but get structured 403 from the\n // service layer when they call them. Avoids an extra HTTP round-trip\n // at MCP startup. See ADR 0007 Phase 4 brief §10 for trade-off.\n registerToMcpServer(server, client, { sandboxId: config.sandbox_id });\n\n return server;\n}\n","import type { ContextWindow } from '@copass/core';\n\n/**\n * In-memory registry of open Context Windows.\n *\n * Tracks a map of `data_source_id → ContextWindow` plus a single \"active\"\n * window id. Retrieval and add-turn tools use the active window implicitly\n * so the LLM doesn't have to thread an id on every call.\n *\n * For multi-window use cases, callers pass `data_source_id` explicitly and\n * the active id is ignored.\n */\nexport class WindowRegistry {\n private readonly windows = new Map<string, ContextWindow>();\n private activeId: string | null = null;\n\n /** Register a new window and make it active. */\n set(window: ContextWindow): void {\n this.windows.set(window.dataSourceId, window);\n this.activeId = window.dataSourceId;\n }\n\n /** Look up a window by id, or fall back to the active one. */\n resolve(dataSourceId?: string): ContextWindow | undefined {\n const id = dataSourceId ?? this.activeId;\n return id ? this.windows.get(id) : undefined;\n }\n\n /** Make an existing window the active one. Throws if unknown. */\n activate(dataSourceId: string): ContextWindow {\n const window = this.windows.get(dataSourceId);\n if (!window) {\n throw new Error(\n `No window registered for data_source_id=\"${dataSourceId}\" — call context_window_create or context_window_attach first.`,\n );\n }\n this.activeId = dataSourceId;\n return window;\n }\n\n /** Drop a window from the registry. Clears active if it matched. */\n drop(dataSourceId: string): void {\n this.windows.delete(dataSourceId);\n if (this.activeId === dataSourceId) this.activeId = null;\n }\n\n get activeDataSourceId(): string | null {\n return this.activeId;\n }\n}\n","import { z } from 'zod';\nimport {\n DISCOVER_QUERY_PARAM,\n INTERPRET_DESCRIPTION,\n INTERPRET_ITEMS_PARAM,\n INTERPRET_QUERY_PARAM,\n MCP_DISCOVER_DESCRIPTION,\n PRESET_PARAM,\n PROJECT_ID_PARAM,\n SEARCH_DESCRIPTION,\n SEARCH_QUERY_PARAM,\n} from '@copass/config';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\nimport type { WindowRegistry } from '../windows.js';\n\ninterface RetrievalDeps {\n client: CopassClient;\n config: ServerConfig;\n windows: WindowRegistry;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerRetrievalTools(server: McpServer, deps: RetrievalDeps): void {\n const { client, config, windows } = deps;\n\n server.registerTool(\n 'discover',\n {\n description: MCP_DISCOVER_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(DISCOVER_QUERY_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n // Per-call preset override. `:thinking` variants are\n // /search-only and rejected on /discover, so the enum here\n // intentionally omits them.\n preset: z\n .enum([\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n ])\n .optional()\n .describe(PRESET_PARAM),\n },\n },\n async ({ query, project_id, preset }) => {\n try {\n const response = await client.retrieval.discover(config.sandbox_id, {\n query,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n // Per-call override wins; otherwise inherit the subprocess\n // default. `:thinking` variants are stripped at the server\n // (/discover rejects them) so we don't second-guess here.\n preset: preset ?? config.preset,\n });\n return mcpResult({\n header: response.header,\n // Project the v2 fields (`subgraph` + `matched_query_nodes`)\n // alongside the v1 fields. Populated only under\n // `copass/copass_2.0` (or its `copass/2.0` alias); `null`\n // under v1.\n items: response.items.map((item) => ({\n score: item.score,\n summary: item.summary,\n canonical_ids: item.canonical_ids,\n subgraph: item.subgraph ?? null,\n matched_query_nodes: item.matched_query_nodes ?? null,\n })),\n next_steps: response.next_steps,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'interpret',\n {\n description: INTERPRET_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(INTERPRET_QUERY_PARAM),\n items: z.array(z.array(z.string()).min(1)).min(1).describe(INTERPRET_ITEMS_PARAM),\n preset: z.enum([\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n ]).optional().describe(PRESET_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n },\n },\n async ({ query, items, preset, project_id }) => {\n try {\n const response = await client.retrieval.interpret(config.sandbox_id, {\n query,\n items,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n preset: preset ?? config.preset,\n });\n return mcpResult({ brief: response.brief });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'search',\n {\n description: SEARCH_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(SEARCH_QUERY_PARAM),\n // `:thinking` variants split the question into sub-questions and\n // run the base preset on each before a combined synthesis.\n preset: z\n .enum([\n // Canonical names\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n 'copass/copass_1.0:thinking',\n 'copass/copass_2.0:thinking',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n 'copass/1.0:thinking',\n 'copass/2.0:thinking',\n ])\n .optional()\n .describe(PRESET_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n },\n },\n async ({ query, preset, project_id }) => {\n try {\n const response = await client.retrieval.search(config.sandbox_id, {\n query,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n preset: preset ?? config.preset,\n });\n return mcpResult({ answer: response.answer });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n","import { z } from 'zod';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\nimport type { WindowRegistry } from '../windows.js';\n\ninterface ContextWindowDeps {\n client: CopassClient;\n config: ServerConfig;\n windows: WindowRegistry;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerContextWindowTools(server: McpServer, deps: ContextWindowDeps): void {\n const { client, config, windows } = deps;\n\n server.registerTool(\n 'context_window_create',\n {\n description:\n 'Open a new Context Window — an ephemeral data source tracking this conversation. ' +\n 'Subsequent retrieval calls become automatically window-aware, and every turn you ' +\n 'record via `context_window_add_turn` is ingested into the graph so past turns ' +\n 'become retrievable. Returns a `data_source_id`; persist it on your side if you ' +\n 'may want to resume this conversation later via `context_window_attach`.',\n inputSchema: {\n project_id: z.string().optional().describe('Override the server default project_id.'),\n name: z.string().optional().describe('Optional stable name for the underlying data source.'),\n },\n },\n async ({ project_id, name }) => {\n try {\n const window = await client.contextWindow.create({\n sandbox_id: config.sandbox_id,\n project_id: project_id ?? config.project_id,\n name,\n });\n windows.set(window);\n return mcpResult({\n data_source_id: window.dataSourceId,\n active: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_add_turn',\n {\n description:\n 'Append a turn to the active Context Window and push it into the graph. Call on ' +\n 'every user message AND every assistant message so the thread itself becomes ' +\n 'retrievable. If you omit `data_source_id`, the active window is used.',\n inputSchema: {\n role: z\n .enum(['user', 'assistant', 'system'])\n .describe('Role of the speaker for this turn.'),\n content: z.string().describe('The turn content.'),\n data_source_id: z\n .string()\n .optional()\n .describe('Override the active window id (for multi-window use cases).'),\n },\n },\n async ({ role, content, data_source_id }) => {\n try {\n const window = windows.resolve(data_source_id);\n if (!window) {\n throw new Error(\n 'No active Context Window — call context_window_create or context_window_attach first.',\n );\n }\n await window.addTurn({ role, content });\n return mcpResult({\n data_source_id: window.dataSourceId,\n turn_count: window.getTurns().length,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_attach',\n {\n description:\n 'Resume an existing Context Window by `data_source_id` (one you previously persisted ' +\n 'on your side after `context_window_create`). Makes the resumed window the active ' +\n 'window. Pass `initial_turns` to seed the local buffer for immediate window-aware ' +\n 'retrieval, otherwise the buffer starts empty and fills as you call add_turn.',\n inputSchema: {\n data_source_id: z.string().describe('Id of the Context Window data source to resume.'),\n initial_turns: z\n .array(\n z.object({\n role: z.enum(['user', 'assistant', 'system']),\n content: z.string(),\n }),\n )\n .optional()\n .describe('Optional pre-existing turns to seed the local buffer.'),\n },\n },\n async ({ data_source_id, initial_turns }) => {\n try {\n const window = await client.contextWindow.attach({\n sandbox_id: config.sandbox_id,\n data_source_id,\n initialTurns: initial_turns,\n });\n windows.set(window);\n return mcpResult({\n data_source_id: window.dataSourceId,\n turn_count: window.getTurns().length,\n active: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_close',\n {\n description:\n 'Close a Context Window — flips the underlying data source to `disconnected` ' +\n 'immediately instead of waiting for TTL. Best-effort; idempotent. If you omit ' +\n '`data_source_id`, the active window is closed.',\n inputSchema: {\n data_source_id: z\n .string()\n .optional()\n .describe('Override the active window id.'),\n },\n },\n async ({ data_source_id }) => {\n try {\n const window = windows.resolve(data_source_id);\n if (!window) {\n throw new Error('No active Context Window to close.');\n }\n await window.close();\n windows.drop(window.dataSourceId);\n return mcpResult({\n data_source_id: window.dataSourceId,\n closed: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n","import { z } from 'zod';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\n\ninterface IngestDeps {\n client: CopassClient;\n config: ServerConfig;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerIngestTool(server: McpServer, deps: IngestDeps): void {\n const { client, config } = deps;\n\n server.registerTool(\n 'ingest',\n {\n description:\n 'Push content into the knowledge graph via a data source. Use for: architecture ' +\n 'decisions (source_type: \"decision\"), user-shared context (\"user_input\"), ' +\n 'corrections (\"correction\"), durable notes, any significant new concept. Do NOT ' +\n 'ingest trivial changes or ephemeral debug context — those belong in the Context ' +\n 'Window (via context_window_add_turn). Pass `data_source_id` explicitly or set ' +\n '`COPASS_INGEST_DATA_SOURCE_ID` in the server env for an implicit target.',\n inputSchema: {\n content: z.string().min(1).describe('The content to ingest.'),\n source_type: z\n .string()\n .optional()\n .describe(\n 'Type tag: code, markdown, json, text, conversation, decision, correction, ' +\n 'user_input. Defaults to \"text\" when omitted.',\n ),\n data_source_id: z\n .string()\n .optional()\n .describe(\n 'Target data source. Falls back to COPASS_INGEST_DATA_SOURCE_ID env var.',\n ),\n project_id: z.string().optional().describe('Override the server default project_id.'),\n storage_only: z\n .boolean()\n .optional()\n .describe('If true, chunk and store but skip ontology ingestion.'),\n occurred_at: z\n .string()\n .optional()\n .describe(\n 'Optional ISO 8601 timestamp anchoring the content to a real-world moment ' +\n '(e.g. when this decision was made, when the user said it). Used as the ' +\n 'default occurred_at for any composed event whose own timestamp is null, ' +\n 'so temporal queries can find it.',\n ),\n },\n },\n async ({ content, source_type, data_source_id, project_id, storage_only, occurred_at }) => {\n try {\n const sourceId = data_source_id ?? config.ingest_data_source_id;\n if (!sourceId) {\n throw new Error(\n 'ingest requires a `data_source_id` argument or COPASS_INGEST_DATA_SOURCE_ID env var. ' +\n 'Register a data source via the REST API or CLI, then pass its id here.',\n );\n }\n\n const response = await client.sources.ingest(config.sandbox_id, sourceId, {\n text: content,\n source_type: source_type ?? 'text',\n project_id: project_id ?? config.project_id,\n storage_only,\n occurred_at,\n });\n\n return mcpResult({\n job_id: response.job_id,\n status: response.status,\n data_source_id: sourceId,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n","import type { ChatMessage, SearchPreset } from '@copass/core';\n\nexport interface ServerConfig {\n api_url: string;\n /**\n * Required when the standalone `copass-mcp` bin builds its own\n * `CopassClient` from this config (`bin.ts`). Optional for embedded\n * use — when a host process (e.g. another MCP server) constructs the\n * `CopassClient` itself and passes it into `buildServer({ client })`,\n * the tool handlers never read this field.\n */\n api_key?: string;\n sandbox_id: string;\n project_id?: string;\n preset: SearchPreset;\n /** Default data_source_id for `ingest` when the caller doesn't pass one. */\n ingest_data_source_id?: string;\n /**\n * If set, the server attaches to this Context Window on startup and makes\n * it the active window. Use when another process (e.g. a Hono server)\n * already created the window and you're launching the MCP server as a\n * subprocess that should share it.\n */\n context_window_id?: string;\n /**\n * Optional pre-existing turns to seed the Context Window's buffer on\n * startup. Makes retrieval immediately window-aware on the first tool\n * call — without these, the first `discover` / `interpret` / `search`\n * after a subprocess spawn sees an empty history.\n *\n * Required when `context_window_id` refers to a thread that has prior\n * turns and the server should dedupe retrieval against them.\n */\n context_window_initial_turns?: ChatMessage[];\n}\n\nconst VALID_PRESETS: readonly SearchPreset[] = [\n // Canonical names\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n // `:thinking` variants are /search-only. Setting one of these as the\n // MCP subprocess default makes `interpret` fail (decomposition isn't\n // valid on /interpret) — fine when the subprocess is only used for\n // `search`, otherwise override per-call via the `preset` tool arg.\n 'copass/copass_1.0:thinking',\n 'copass/copass_2.0:thinking',\n 'copass/1.0:thinking',\n 'copass/2.0:thinking',\n] as const;\n\n/**\n * Read config from `process.env`.\n *\n * - `COPASS_API_KEY` (required)\n * - `COPASS_SANDBOX_ID` (required)\n * - `COPASS_API_URL` (default: https://ai.copass.id)\n * - `COPASS_PROJECT_ID` (optional — default for retrieval/ingest)\n * - `COPASS_PRESET` (default: `copass/copass_1.0`). Accepts any\n * `SearchPreset` (including the `copass/X.0` short aliases), but\n * `:thinking` variants only work on `/search` — setting one as the\n * subprocess default breaks `interpret`. Leave at `copass/copass_1.0`\n * unless the subprocess is search-only, and override per-call via\n * the `preset` tool arg.\n *\n * Throws a descriptive error for missing/invalid values so the MCP client\n * sees an immediate startup failure with actionable text.\n */\nexport function loadConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig {\n const missing: string[] = [];\n const api_key = env.COPASS_API_KEY ?? '';\n const sandbox_id = env.COPASS_SANDBOX_ID ?? '';\n\n if (!api_key) missing.push('COPASS_API_KEY');\n if (!sandbox_id) missing.push('COPASS_SANDBOX_ID');\n\n if (missing.length > 0) {\n throw new Error(\n `@copass/mcp: missing required env var(s): ${missing.join(', ')}. ` +\n `Set them before launching the server.`,\n );\n }\n\n const api_url = env.COPASS_API_URL?.trim() || 'https://ai.copass.id';\n const project_id = env.COPASS_PROJECT_ID?.trim() || undefined;\n const rawPreset = env.COPASS_PRESET?.trim() || 'copass/copass_1.0';\n\n if (!VALID_PRESETS.includes(rawPreset as SearchPreset)) {\n throw new Error(\n `@copass/mcp: COPASS_PRESET must be one of ${VALID_PRESETS.join(', ')}; got \"${rawPreset}\"`,\n );\n }\n\n const ingest_data_source_id = env.COPASS_INGEST_DATA_SOURCE_ID?.trim() || undefined;\n const context_window_id = env.COPASS_CONTEXT_WINDOW_ID?.trim() || undefined;\n const context_window_initial_turns = parseInitialTurns(\n env.COPASS_CONTEXT_WINDOW_INITIAL_TURNS,\n );\n\n return {\n api_url,\n api_key,\n sandbox_id,\n project_id,\n preset: rawPreset as SearchPreset,\n ingest_data_source_id,\n context_window_id,\n context_window_initial_turns,\n };\n}\n\nfunction parseInitialTurns(raw: string | undefined): ChatMessage[] | undefined {\n if (!raw || !raw.trim()) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS is not valid JSON: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n if (!Array.isArray(parsed)) {\n throw new Error(\n '@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS must be a JSON array of {role, content} objects',\n );\n }\n const turns: ChatMessage[] = [];\n for (const [i, entry] of parsed.entries()) {\n if (\n !entry ||\n typeof entry !== 'object' ||\n typeof (entry as { role?: unknown }).role !== 'string' ||\n typeof (entry as { content?: unknown }).content !== 'string'\n ) {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS[${i}] must be {role: string, content: string}`,\n );\n }\n const { role, content } = entry as { role: string; content: string };\n if (role !== 'user' && role !== 'assistant' && role !== 'system') {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS[${i}].role must be \"user\" | \"assistant\" | \"system\"; got \"${role}\"`,\n );\n }\n turns.push({ role, content });\n }\n return turns;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,iBAA0B;AAE1B,IAAAA,cAAoC;;;ACU7B,IAAM,iBAAN,MAAqB;AAAA,EACT,UAAU,oBAAI,IAA2B;AAAA,EAClD,WAA0B;AAAA;AAAA,EAGlC,IAAI,QAA6B;AAC/B,SAAK,QAAQ,IAAI,OAAO,cAAc,MAAM;AAC5C,SAAK,WAAW,OAAO;AAAA,EACzB;AAAA;AAAA,EAGA,QAAQ,cAAkD;AACxD,UAAM,KAAK,gBAAgB,KAAK;AAChC,WAAO,KAAK,KAAK,QAAQ,IAAI,EAAE,IAAI;AAAA,EACrC;AAAA;AAAA,EAGA,SAAS,cAAqC;AAC5C,UAAM,SAAS,KAAK,QAAQ,IAAI,YAAY;AAC5C,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI;AAAA,QACR,4CAA4C,YAAY;AAAA,MAC1D;AAAA,IACF;AACA,SAAK,WAAW;AAChB,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,KAAK,cAA4B;AAC/B,SAAK,QAAQ,OAAO,YAAY;AAChC,QAAI,KAAK,aAAa,aAAc,MAAK,WAAW;AAAA,EACtD;AAAA,EAEA,IAAI,qBAAoC;AACtC,WAAO,KAAK;AAAA,EACd;AACF;;;ACjDA,iBAAkB;AAClB,oBAUO;AAYP,SAAS,UAAU,SAAkB;AACnC,SAAO;AAAA,IACL,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,KAAK,UAAU,SAAS,MAAM,CAAC,EAAE,CAAC;AAAA,EAC7E;AACF;AAEA,SAAS,SAAS,OAAgB;AAChC,QAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,SAAO;AAAA,IACL,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,UAAU,OAAO,GAAG,CAAC;AAAA,IAC9D,SAAS;AAAA,EACX;AACF;AAEO,SAAS,uBAAuB,QAAmB,MAA2B;AACnF,QAAM,EAAE,QAAQ,QAAQ,QAAQ,IAAI;AAEpC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,aAAa;AAAA,MACb,aAAa;AAAA,QACX,OAAO,aAAE,OAAO,EAAE,SAAS,kCAAoB;AAAA,QAC/C,YAAY,aAAE,OAAO,EAAE,SAAS,EAAE,SAAS,8BAAgB;AAAA;AAAA;AAAA;AAAA,QAI3D,QAAQ,aACL,KAAK;AAAA,UACJ;AAAA,UACA;AAAA;AAAA,UAEA;AAAA,UACA;AAAA,QACF,CAAC,EACA,SAAS,EACT,SAAS,0BAAY;AAAA,MAC1B;AAAA,IACF;AAAA,IACA,OAAO,EAAE,OAAO,YAAY,OAAO,MAAM;AACvC,UAAI;AACF,cAAM,WAAW,MAAM,OAAO,UAAU,SAAS,OAAO,YAAY;AAAA,UAClE;AAAA,UACA,YAAY,cAAc,OAAO;AAAA,UACjC,QAAQ,QAAQ,QAAQ;AAAA;AAAA;AAAA;AAAA,UAIxB,QAAQ,UAAU,OAAO;AAAA,QAC3B,CAAC;AACD,eAAO,UAAU;AAAA,UACf,QAAQ,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,UAKjB,OAAO,SAAS,MAAM,IAAI,CAAC,UAAU;AAAA,YACnC,OAAO,KAAK;AAAA,YACZ,SAAS,KAAK;AAAA,YACd,eAAe,KAAK;AAAA,YACpB,UAAU,KAAK,YAAY;AAAA,YAC3B,qBAAqB,KAAK,uBAAuB;AAAA,UACnD,EAAE;AAAA,UACF,YAAY,SAAS;AAAA,QACvB,CAAC;AAAA,MACH,SAAS,GAAG;AACV,eAAO,SAAS,CAAC;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,aAAa;AAAA,MACb,aAAa;AAAA,QACX,OAAO,aAAE,OAAO,EAAE,SAAS,mCAAqB;AAAA,QAChD,OAAO,aAAE,MAAM,aAAE,MAAM,aAAE,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,SAAS,mCAAqB;AAAA,QAChF,QAAQ,aAAE,KAAK;AAAA,UACb;AAAA,UACA;AAAA;AAAA,UAEA;AAAA,UACA;AAAA,QACF,CAAC,EAAE,SAAS,EAAE,SAAS,0BAAY;AAAA,QACnC,YAAY,aAAE,OAAO,EAAE,SAAS,EAAE,SAAS,8BAAgB;AAAA,MAC7D;AAAA,IACF;AAAA,IACA,OAAO,EAAE,OAAO,OAAO,QAAQ,WAAW,MAAM;AAC9C,UAAI;AACF,cAAM,WAAW,MAAM,OAAO,UAAU,UAAU,OAAO,YAAY;AAAA,UACnE;AAAA,UACA;AAAA,UACA,YAAY,cAAc,OAAO;AAAA,UACjC,QAAQ,QAAQ,QAAQ;AAAA,UACxB,QAAQ,UAAU,OAAO;AAAA,QAC3B,CAAC;AACD,eAAO,UAAU,EAAE,OAAO,SAAS,MAAM,CAAC;AAAA,MAC5C,SAAS,GAAG;AACV,eAAO,SAAS,CAAC;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,aAAa;AAAA,MACb,aAAa;AAAA,QACX,OAAO,aAAE,OAAO,EAAE,SAAS,gCAAkB;AAAA;AAAA;AAAA,QAG7C,QAAQ,aACL,KAAK;AAAA;AAAA,UAEJ;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA;AAAA,UAEA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC,EACA,SAAS,EACT,SAAS,0BAAY;AAAA,QACxB,YAAY,aAAE,OAAO,EAAE,SAAS,EAAE,SAAS,8BAAgB;AAAA,MAC7D;AAAA,IACF;AAAA,IACA,OAAO,EAAE,OAAO,QAAQ,WAAW,MAAM;AACvC,UAAI;AACF,cAAM,WAAW,MAAM,OAAO,UAAU,OAAO,OAAO,YAAY;AAAA,UAChE;AAAA,UACA,YAAY,cAAc,OAAO;AAAA,UACjC,QAAQ,QAAQ,QAAQ;AAAA,UACxB,QAAQ,UAAU,OAAO;AAAA,QAC3B,CAAC;AACD,eAAO,UAAU,EAAE,QAAQ,SAAS,OAAO,CAAC;AAAA,MAC9C,SAAS,GAAG;AACV,eAAO,SAAS,CAAC;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACF;;;ACvKA,IAAAC,cAAkB;AAYlB,SAASC,WAAU,SAAkB;AACnC,SAAO;AAAA,IACL,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,KAAK,UAAU,SAAS,MAAM,CAAC,EAAE,CAAC;AAAA,EAC7E;AACF;AAEA,SAASC,UAAS,OAAgB;AAChC,QAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,SAAO;AAAA,IACL,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,UAAU,OAAO,GAAG,CAAC;AAAA,IAC9D,SAAS;AAAA,EACX;AACF;AAEO,SAAS,2BAA2B,QAAmB,MAA+B;AAC3F,QAAM,EAAE,QAAQ,QAAQ,QAAQ,IAAI;AAEpC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,aACE;AAAA,MAKF,aAAa;AAAA,QACX,YAAY,cAAE,OAAO,EAAE,SAAS,EAAE,SAAS,yCAAyC;AAAA,QACpF,MAAM,cAAE,OAAO,EAAE,SAAS,EAAE,SAAS,sDAAsD;AAAA,MAC7F;AAAA,IACF;AAAA,IACA,OAAO,EAAE,YAAY,KAAK,MAAM;AAC9B,UAAI;AACF,cAAM,SAAS,MAAM,OAAO,cAAc,OAAO;AAAA,UAC/C,YAAY,OAAO;AAAA,UACnB,YAAY,cAAc,OAAO;AAAA,UACjC;AAAA,QACF,CAAC;AACD,gBAAQ,IAAI,MAAM;AAClB,eAAOD,WAAU;AAAA,UACf,gBAAgB,OAAO;AAAA,UACvB,QAAQ;AAAA,QACV,CAAC;AAAA,MACH,SAAS,GAAG;AACV,eAAOC,UAAS,CAAC;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,aACE;AAAA,MAGF,aAAa;AAAA,QACX,MAAM,cACH,KAAK,CAAC,QAAQ,aAAa,QAAQ,CAAC,EACpC,SAAS,oCAAoC;AAAA,QAChD,SAAS,cAAE,OAAO,EAAE,SAAS,mBAAmB;AAAA,QAChD,gBAAgB,cACb,OAAO,EACP,SAAS,EACT,SAAS,6DAA6D;AAAA,MAC3E;AAAA,IACF;AAAA,IACA,OAAO,EAAE,MAAM,SAAS,eAAe,MAAM;AAC3C,UAAI;AACF,cAAM,SAAS,QAAQ,QAAQ,cAAc;AAC7C,YAAI,CAAC,QAAQ;AACX,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QACF;AACA,cAAM,OAAO,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACtC,eAAOD,WAAU;AAAA,UACf,gBAAgB,OAAO;AAAA,UACvB,YAAY,OAAO,SAAS,EAAE;AAAA,QAChC,CAAC;AAAA,MACH,SAAS,GAAG;AACV,eAAOC,UAAS,CAAC;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,aACE;AAAA,MAIF,aAAa;AAAA,QACX,gBAAgB,cAAE,OAAO,EAAE,SAAS,iDAAiD;AAAA,QACrF,eAAe,cACZ;AAAA,UACC,cAAE,OAAO;AAAA,YACP,MAAM,cAAE,KAAK,CAAC,QAAQ,aAAa,QAAQ,CAAC;AAAA,YAC5C,SAAS,cAAE,OAAO;AAAA,UACpB,CAAC;AAAA,QACH,EACC,SAAS,EACT,SAAS,uDAAuD;AAAA,MACrE;AAAA,IACF;AAAA,IACA,OAAO,EAAE,gBAAgB,cAAc,MAAM;AAC3C,UAAI;AACF,cAAM,SAAS,MAAM,OAAO,cAAc,OAAO;AAAA,UAC/C,YAAY,OAAO;AAAA,UACnB;AAAA,UACA,cAAc;AAAA,QAChB,CAAC;AACD,gBAAQ,IAAI,MAAM;AAClB,eAAOD,WAAU;AAAA,UACf,gBAAgB,OAAO;AAAA,UACvB,YAAY,OAAO,SAAS,EAAE;AAAA,UAC9B,QAAQ;AAAA,QACV,CAAC;AAAA,MACH,SAAS,GAAG;AACV,eAAOC,UAAS,CAAC;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,aACE;AAAA,MAGF,aAAa;AAAA,QACX,gBAAgB,cACb,OAAO,EACP,SAAS,EACT,SAAS,gCAAgC;AAAA,MAC9C;AAAA,IACF;AAAA,IACA,OAAO,EAAE,eAAe,MAAM;AAC5B,UAAI;AACF,cAAM,SAAS,QAAQ,QAAQ,cAAc;AAC7C,YAAI,CAAC,QAAQ;AACX,gBAAM,IAAI,MAAM,oCAAoC;AAAA,QACtD;AACA,cAAM,OAAO,MAAM;AACnB,gBAAQ,KAAK,OAAO,YAAY;AAChC,eAAOD,WAAU;AAAA,UACf,gBAAgB,OAAO;AAAA,UACvB,QAAQ;AAAA,QACV,CAAC;AAAA,MACH,SAAS,GAAG;AACV,eAAOC,UAAS,CAAC;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACF;;;ACzKA,IAAAC,cAAkB;AAUlB,SAASC,WAAU,SAAkB;AACnC,SAAO;AAAA,IACL,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,KAAK,UAAU,SAAS,MAAM,CAAC,EAAE,CAAC;AAAA,EAC7E;AACF;AAEA,SAASC,UAAS,OAAgB;AAChC,QAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,SAAO;AAAA,IACL,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,UAAU,OAAO,GAAG,CAAC;AAAA,IAC9D,SAAS;AAAA,EACX;AACF;AAEO,SAAS,mBAAmB,QAAmB,MAAwB;AAC5E,QAAM,EAAE,QAAQ,OAAO,IAAI;AAE3B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,aACE;AAAA,MAMF,aAAa;AAAA,QACX,SAAS,cAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS,wBAAwB;AAAA,QAC5D,aAAa,cACV,OAAO,EACP,SAAS,EACT;AAAA,UACC;AAAA,QAEF;AAAA,QACF,gBAAgB,cACb,OAAO,EACP,SAAS,EACT;AAAA,UACC;AAAA,QACF;AAAA,QACF,YAAY,cAAE,OAAO,EAAE,SAAS,EAAE,SAAS,yCAAyC;AAAA,QACpF,cAAc,cACX,QAAQ,EACR,SAAS,EACT,SAAS,uDAAuD;AAAA,QACnE,aAAa,cACV,OAAO,EACP,SAAS,EACT;AAAA,UACC;AAAA,QAIF;AAAA,MACJ;AAAA,IACF;AAAA,IACA,OAAO,EAAE,SAAS,aAAa,gBAAgB,YAAY,cAAc,YAAY,MAAM;AACzF,UAAI;AACF,cAAM,WAAW,kBAAkB,OAAO;AAC1C,YAAI,CAAC,UAAU;AACb,gBAAM,IAAI;AAAA,YACR;AAAA,UAEF;AAAA,QACF;AAEA,cAAM,WAAW,MAAM,OAAO,QAAQ,OAAO,OAAO,YAAY,UAAU;AAAA,UACxE,MAAM;AAAA,UACN,aAAa,eAAe;AAAA,UAC5B,YAAY,cAAc,OAAO;AAAA,UACjC;AAAA,UACA;AAAA,QACF,CAAC;AAED,eAAOD,WAAU;AAAA,UACf,QAAQ,SAAS;AAAA,UACjB,QAAQ,SAAS;AAAA,UACjB,gBAAgB;AAAA,QAClB,CAAC;AAAA,MACH,SAAS,GAAG;AACV,eAAOC,UAAS,CAAC;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACF;;;AJvEO,SAAS,YAAY,EAAE,QAAQ,QAAQ,QAAQ,GAAkC;AACtF,QAAM,SAAS,IAAI;AAAA,IACjB;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,cAAc,EAAE,OAAO,CAAC,EAAE;AAAA,IAC5B;AAAA,EACF;AAEA,QAAM,WAAW,WAAW,IAAI,eAAe;AAC/C,QAAM,OAAO,EAAE,QAAQ,QAAQ,SAAS,SAAS;AAEjD,yBAAuB,QAAQ,IAAI;AACnC,6BAA2B,QAAQ,IAAI;AACvC,qBAAmB,QAAQ,EAAE,QAAQ,OAAO,CAAC;AAM7C,uCAAoB,QAAQ,QAAQ,EAAE,WAAW,OAAO,WAAW,CAAC;AAEpE,SAAO;AACT;;;AKdA,IAAM,gBAAyC;AAAA;AAAA,EAE7C;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAmBO,SAAS,WAAW,MAAyB,QAAQ,KAAmB;AAC7E,QAAM,UAAoB,CAAC;AAC3B,QAAM,UAAU,IAAI,kBAAkB;AACtC,QAAM,aAAa,IAAI,qBAAqB;AAE5C,MAAI,CAAC,QAAS,SAAQ,KAAK,gBAAgB;AAC3C,MAAI,CAAC,WAAY,SAAQ,KAAK,mBAAmB;AAEjD,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,IAAI;AAAA,MACR,6CAA6C,QAAQ,KAAK,IAAI,CAAC;AAAA,IAEjE;AAAA,EACF;AAEA,QAAM,UAAU,IAAI,gBAAgB,KAAK,KAAK;AAC9C,QAAM,aAAa,IAAI,mBAAmB,KAAK,KAAK;AACpD,QAAM,YAAY,IAAI,eAAe,KAAK,KAAK;AAE/C,MAAI,CAAC,cAAc,SAAS,SAAyB,GAAG;AACtD,UAAM,IAAI;AAAA,MACR,6CAA6C,cAAc,KAAK,IAAI,CAAC,UAAU,SAAS;AAAA,IAC1F;AAAA,EACF;AAEA,QAAM,wBAAwB,IAAI,8BAA8B,KAAK,KAAK;AAC1E,QAAM,oBAAoB,IAAI,0BAA0B,KAAK,KAAK;AAClE,QAAM,+BAA+B;AAAA,IACnC,IAAI;AAAA,EACN;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,kBAAkB,KAAoD;AAC7E,MAAI,CAAC,OAAO,CAAC,IAAI,KAAK,EAAG,QAAO;AAChC,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR,uEACE,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CACjD;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,QAAuB,CAAC;AAC9B,aAAW,CAAC,GAAG,KAAK,KAAK,OAAO,QAAQ,GAAG;AACzC,QACE,CAAC,SACD,OAAO,UAAU,YACjB,OAAQ,MAA6B,SAAS,YAC9C,OAAQ,MAAgC,YAAY,UACpD;AACA,YAAM,IAAI;AAAA,QACR,oDAAoD,CAAC;AAAA,MACvD;AAAA,IACF;AACA,UAAM,EAAE,MAAM,QAAQ,IAAI;AAC1B,QAAI,SAAS,UAAU,SAAS,eAAe,SAAS,UAAU;AAChE,YAAM,IAAI;AAAA,QACR,oDAAoD,CAAC,wDAAwD,IAAI;AAAA,MACnH;AAAA,IACF;AACA,UAAM,KAAK,EAAE,MAAM,QAAQ,CAAC;AAAA,EAC9B;AACA,SAAO;AACT;","names":["import_mcp","import_zod","mcpResult","mcpError","import_zod","mcpResult","mcpError"]} | ||
| {"version":3,"sources":["../src/index.ts","../src/server.ts","../src/windows.ts","../src/tools/retrieval.ts","../src/tools/context-window.ts","../src/tools/ingest.ts","../src/config.ts"],"sourcesContent":["export { buildServer } from './server.js';\nexport type { BuildServerOptions } from './server.js';\nexport { loadConfig } from './config.js';\nexport type { ServerConfig } from './config.js';\nexport { WindowRegistry } from './windows.js';\n\n// Sub-registrars — for callers that want to compose tools onto an\n// existing `McpServer` rather than spinning up a whole `buildServer()`.\n// Pick the tool groups you want and skip the rest (e.g. embed\n// `discover` / `interpret` / `search` without the SDK ingest when the\n// host process owns its own ingest tool).\nexport { registerRetrievalTools } from './tools/retrieval.js';\nexport { registerContextWindowTools } from './tools/context-window.js';\nexport { registerIngestTool } from './tools/ingest.js';\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CopassClient } from '@copass/core';\nimport { registerToMcpServer } from '@copass/management/adapters/mcp';\nimport type { ServerConfig } from './config.js';\nimport { WindowRegistry } from './windows.js';\nimport { registerRetrievalTools } from './tools/retrieval.js';\nimport { registerContextWindowTools } from './tools/context-window.js';\nimport { registerIngestTool } from './tools/ingest.js';\n\nexport interface BuildServerOptions {\n client: CopassClient;\n config: ServerConfig;\n /**\n * Optional pre-populated window registry. Useful when a parent process has\n * already created or attached to a Context Window and wants the server to\n * start with it as the active window.\n */\n windows?: WindowRegistry;\n}\n\n/**\n * Assemble the Copass MCP server with every tool registered. Doesn't connect\n * a transport — see `startStdioServer` in `./bin.ts` for the default path,\n * or wire your own transport if embedding.\n */\nexport function buildServer({ client, config, windows }: BuildServerOptions): McpServer {\n const server = new McpServer(\n {\n name: 'copass',\n version: '0.3.0',\n },\n {\n capabilities: { tools: {} },\n },\n );\n\n const registry = windows ?? new WindowRegistry();\n const deps = { client, config, windows: registry };\n\n registerRetrievalTools(server, deps);\n registerContextWindowTools(server, deps);\n registerIngestTool(server, { client, config });\n\n // The full management tool corpus is exposed regardless of sandbox role.\n // Viewer-role users see write tools but get structured 403 from the\n // service layer when they call them. Avoids an extra HTTP round-trip\n // at MCP startup. See ADR 0007 Phase 4 brief §10 for trade-off.\n registerToMcpServer(server, client, { sandboxId: config.sandbox_id });\n\n return server;\n}\n","import type { ContextWindow } from '@copass/core';\n\n/**\n * In-memory registry of open Context Windows.\n *\n * Tracks a map of `data_source_id → ContextWindow` plus a single \"active\"\n * window id. Retrieval and add-turn tools use the active window implicitly\n * so the LLM doesn't have to thread an id on every call.\n *\n * For multi-window use cases, callers pass `data_source_id` explicitly and\n * the active id is ignored.\n */\nexport class WindowRegistry {\n private readonly windows = new Map<string, ContextWindow>();\n private activeId: string | null = null;\n\n /** Register a new window and make it active. */\n set(window: ContextWindow): void {\n this.windows.set(window.dataSourceId, window);\n this.activeId = window.dataSourceId;\n }\n\n /** Look up a window by id, or fall back to the active one. */\n resolve(dataSourceId?: string): ContextWindow | undefined {\n const id = dataSourceId ?? this.activeId;\n return id ? this.windows.get(id) : undefined;\n }\n\n /** Make an existing window the active one. Throws if unknown. */\n activate(dataSourceId: string): ContextWindow {\n const window = this.windows.get(dataSourceId);\n if (!window) {\n throw new Error(\n `No window registered for data_source_id=\"${dataSourceId}\" — call context_window_create or context_window_attach first.`,\n );\n }\n this.activeId = dataSourceId;\n return window;\n }\n\n /** Drop a window from the registry. Clears active if it matched. */\n drop(dataSourceId: string): void {\n this.windows.delete(dataSourceId);\n if (this.activeId === dataSourceId) this.activeId = null;\n }\n\n get activeDataSourceId(): string | null {\n return this.activeId;\n }\n}\n","import { z } from 'zod';\nimport {\n DISCOVER_QUERY_PARAM,\n INTERPRET_DESCRIPTION,\n INTERPRET_ITEMS_PARAM,\n INTERPRET_QUERY_PARAM,\n MCP_DISCOVER_DESCRIPTION,\n PRESET_PARAM,\n PROJECT_ID_PARAM,\n SEARCH_DESCRIPTION,\n SEARCH_QUERY_PARAM,\n} from '@copass/config';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\nimport type { WindowRegistry } from '../windows.js';\n\ninterface RetrievalDeps {\n client: CopassClient;\n config: ServerConfig;\n windows: WindowRegistry;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerRetrievalTools(server: McpServer, deps: RetrievalDeps): void {\n const { client, config, windows } = deps;\n\n server.registerTool(\n 'discover',\n {\n description: MCP_DISCOVER_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(DISCOVER_QUERY_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n // Per-call preset override. `:thinking` variants are\n // /search-only and rejected on /discover, so the enum here\n // intentionally omits them.\n preset: z\n .enum([\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n ])\n .optional()\n .describe(PRESET_PARAM),\n },\n },\n async ({ query, project_id, preset }) => {\n try {\n const response = await client.retrieval.discover(config.sandbox_id, {\n query,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n // Per-call override wins; otherwise inherit the subprocess\n // default. `:thinking` variants are stripped at the server\n // (/discover rejects them) so we don't second-guess here.\n preset: preset ?? config.preset,\n });\n return mcpResult({\n header: response.header,\n // Project the v2 fields (`subgraph` + `matched_query_nodes`)\n // alongside the v1 fields. Populated only under\n // `copass/copass_2.0` (or its `copass/2.0` alias); `null`\n // under v1.\n items: response.items.map((item) => ({\n score: item.score,\n summary: item.summary,\n canonical_ids: item.canonical_ids,\n subgraph: item.subgraph ?? null,\n matched_query_nodes: item.matched_query_nodes ?? null,\n })),\n next_steps: response.next_steps,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'interpret',\n {\n description: INTERPRET_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(INTERPRET_QUERY_PARAM),\n items: z.array(z.array(z.string()).min(1)).min(1).describe(INTERPRET_ITEMS_PARAM),\n preset: z.enum([\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n ]).optional().describe(PRESET_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n },\n },\n async ({ query, items, preset, project_id }) => {\n try {\n const response = await client.retrieval.interpret(config.sandbox_id, {\n query,\n items,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n preset: preset ?? config.preset,\n });\n return mcpResult({ brief: response.brief });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'search',\n {\n description: SEARCH_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(SEARCH_QUERY_PARAM),\n // `:thinking` variants split the question into sub-questions and\n // run the base preset on each before a combined synthesis.\n preset: z\n .enum([\n // Canonical names\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n 'copass/copass_1.0:thinking',\n 'copass/copass_2.0:thinking',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n 'copass/1.0:thinking',\n 'copass/2.0:thinking',\n ])\n .optional()\n .describe(PRESET_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n },\n },\n async ({ query, preset, project_id }) => {\n try {\n const response = await client.retrieval.search(config.sandbox_id, {\n query,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n preset: preset ?? config.preset,\n });\n return mcpResult({ answer: response.answer });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n","import { z } from 'zod';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\nimport type { WindowRegistry } from '../windows.js';\n\ninterface ContextWindowDeps {\n client: CopassClient;\n config: ServerConfig;\n windows: WindowRegistry;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerContextWindowTools(server: McpServer, deps: ContextWindowDeps): void {\n const { client, config, windows } = deps;\n\n server.registerTool(\n 'context_window_create',\n {\n description:\n 'Open a new Context Window — an ephemeral data source tracking this conversation. ' +\n 'Subsequent retrieval calls become automatically window-aware, and every turn you ' +\n 'record via `context_window_add_turn` is ingested into the graph so past turns ' +\n 'become retrievable. Returns a `data_source_id`; persist it on your side if you ' +\n 'may want to resume this conversation later via `context_window_attach`. Pass ' +\n '`participants` to set a default conversation roster — forwarded as the envelope ' +\n '`participants` field on every turn so downstream extraction can resolve ' +\n 'second-person pronouns (\"your X\" → the OTHER listed participant).',\n inputSchema: {\n project_id: z.string().optional().describe('Override the server default project_id.'),\n name: z.string().optional().describe('Optional stable name for the underlying data source.'),\n participants: z\n .array(z.string())\n .optional()\n .describe(\n 'Default conversation participant roster (e.g. [\"User\", \"Assistant\"]). ' +\n 'Forwarded on every turn unless add_turn passes its own override.',\n ),\n },\n },\n async ({ project_id, name, participants }) => {\n try {\n const window = await client.contextWindow.create({\n sandbox_id: config.sandbox_id,\n project_id: project_id ?? config.project_id,\n name,\n participants,\n });\n windows.set(window);\n return mcpResult({\n data_source_id: window.dataSourceId,\n active: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_add_turn',\n {\n description:\n 'Append a turn to the active Context Window and push it into the graph. Call on ' +\n 'every user message AND every assistant message so the thread itself becomes ' +\n 'retrievable. If you omit `data_source_id`, the active window is used. Pass `name` ' +\n 'when the speaker has a richer identity than `role` alone (e.g. an actual user name ' +\n 'like \"Alice\"); falls back to capitalized `role` when absent. Pass `participants` ' +\n 'to override the window\\'s default roster for this turn only.',\n inputSchema: {\n role: z\n .enum(['user', 'assistant', 'system'])\n .describe('Role of the speaker for this turn.'),\n content: z.string().describe('The turn content.'),\n name: z\n .string()\n .optional()\n .describe(\n 'Optional named participant for this turn — forwarded as the envelope ' +\n '`speaker`. Caller-decided literal (e.g. \"Alice\", \"support-bot\"). When ' +\n 'omitted, falls back to the capitalized role.',\n ),\n participants: z\n .array(z.string())\n .optional()\n .describe(\n 'Per-turn participants override. Falls back to the window\\'s ' +\n 'constructor-time roster when omitted.',\n ),\n data_source_id: z\n .string()\n .optional()\n .describe('Override the active window id (for multi-window use cases).'),\n },\n },\n async ({ role, content, name, participants, data_source_id }) => {\n try {\n const window = windows.resolve(data_source_id);\n if (!window) {\n throw new Error(\n 'No active Context Window — call context_window_create or context_window_attach first.',\n );\n }\n await window.addTurn({ role, content, name }, { participants });\n return mcpResult({\n data_source_id: window.dataSourceId,\n turn_count: window.getTurns().length,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_attach',\n {\n description:\n 'Resume an existing Context Window by `data_source_id` (one you previously persisted ' +\n 'on your side after `context_window_create`). Makes the resumed window the active ' +\n 'window. Pass `initial_turns` to seed the local buffer for immediate window-aware ' +\n 'retrieval, otherwise the buffer starts empty and fills as you call add_turn.',\n inputSchema: {\n data_source_id: z.string().describe('Id of the Context Window data source to resume.'),\n initial_turns: z\n .array(\n z.object({\n role: z.enum(['user', 'assistant', 'system']),\n content: z.string(),\n name: z.string().optional(),\n }),\n )\n .optional()\n .describe('Optional pre-existing turns to seed the local buffer.'),\n participants: z\n .array(z.string())\n .optional()\n .describe(\n 'Default conversation participant roster — forwarded on every turn ' +\n 'pushed via add_turn unless that call overrides.',\n ),\n },\n },\n async ({ data_source_id, initial_turns, participants }) => {\n try {\n const window = await client.contextWindow.attach({\n sandbox_id: config.sandbox_id,\n data_source_id,\n initialTurns: initial_turns,\n participants,\n });\n windows.set(window);\n return mcpResult({\n data_source_id: window.dataSourceId,\n turn_count: window.getTurns().length,\n active: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_close',\n {\n description:\n 'Close a Context Window — flips the underlying data source to `disconnected` ' +\n 'immediately instead of waiting for TTL. Best-effort; idempotent. If you omit ' +\n '`data_source_id`, the active window is closed.',\n inputSchema: {\n data_source_id: z\n .string()\n .optional()\n .describe('Override the active window id.'),\n },\n },\n async ({ data_source_id }) => {\n try {\n const window = windows.resolve(data_source_id);\n if (!window) {\n throw new Error('No active Context Window to close.');\n }\n await window.close();\n windows.drop(window.dataSourceId);\n return mcpResult({\n data_source_id: window.dataSourceId,\n closed: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n","import { z } from 'zod';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\n\ninterface IngestDeps {\n client: CopassClient;\n config: ServerConfig;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerIngestTool(server: McpServer, deps: IngestDeps): void {\n const { client, config } = deps;\n\n server.registerTool(\n 'ingest',\n {\n description:\n 'Push content into the knowledge graph via a data source. Use for: architecture ' +\n 'decisions (source_type: \"decision\"), user-shared context (\"user_input\"), ' +\n 'corrections (\"correction\"), durable notes, any significant new concept. Do NOT ' +\n 'ingest trivial changes or ephemeral debug context — those belong in the Context ' +\n 'Window (via context_window_add_turn). Pass `data_source_id` explicitly or set ' +\n '`COPASS_INGEST_DATA_SOURCE_ID` in the server env for an implicit target.',\n inputSchema: {\n content: z.string().min(1).describe('The content to ingest.'),\n source_type: z\n .string()\n .optional()\n .describe(\n 'Type tag: code, markdown, json, text, conversation, decision, correction, ' +\n 'user_input. Defaults to \"text\" when omitted.',\n ),\n data_source_id: z\n .string()\n .optional()\n .describe(\n 'Target data source. Falls back to COPASS_INGEST_DATA_SOURCE_ID env var.',\n ),\n project_id: z.string().optional().describe('Override the server default project_id.'),\n storage_only: z\n .boolean()\n .optional()\n .describe('If true, chunk and store but skip ontology ingestion.'),\n occurred_at: z\n .string()\n .optional()\n .describe(\n 'Optional ISO 8601 timestamp anchoring the content to a real-world moment ' +\n '(e.g. when this decision was made, when the user said it). Used as the ' +\n 'default occurred_at for any composed event whose own timestamp is null, ' +\n 'so temporal queries can find it.',\n ),\n },\n },\n async ({ content, source_type, data_source_id, project_id, storage_only, occurred_at }) => {\n try {\n const sourceId = data_source_id ?? config.ingest_data_source_id;\n if (!sourceId) {\n throw new Error(\n 'ingest requires a `data_source_id` argument or COPASS_INGEST_DATA_SOURCE_ID env var. ' +\n 'Register a data source via the REST API or CLI, then pass its id here.',\n );\n }\n\n const response = await client.sources.ingest(config.sandbox_id, sourceId, {\n text: content,\n source_type: source_type ?? 'text',\n project_id: project_id ?? config.project_id,\n storage_only,\n occurred_at,\n });\n\n return mcpResult({\n job_id: response.job_id,\n status: response.status,\n data_source_id: sourceId,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n","import type { ChatMessage, SearchPreset } from '@copass/core';\n\nexport interface ServerConfig {\n api_url: string;\n /**\n * Required when the standalone `copass-mcp` bin builds its own\n * `CopassClient` from this config (`bin.ts`). Optional for embedded\n * use — when a host process (e.g. another MCP server) constructs the\n * `CopassClient` itself and passes it into `buildServer({ client })`,\n * the tool handlers never read this field.\n */\n api_key?: string;\n sandbox_id: string;\n project_id?: string;\n preset: SearchPreset;\n /** Default data_source_id for `ingest` when the caller doesn't pass one. */\n ingest_data_source_id?: string;\n /**\n * If set, the server attaches to this Context Window on startup and makes\n * it the active window. Use when another process (e.g. a Hono server)\n * already created the window and you're launching the MCP server as a\n * subprocess that should share it.\n */\n context_window_id?: string;\n /**\n * Optional pre-existing turns to seed the Context Window's buffer on\n * startup. Makes retrieval immediately window-aware on the first tool\n * call — without these, the first `discover` / `interpret` / `search`\n * after a subprocess spawn sees an empty history.\n *\n * Required when `context_window_id` refers to a thread that has prior\n * turns and the server should dedupe retrieval against them.\n */\n context_window_initial_turns?: ChatMessage[];\n}\n\nconst VALID_PRESETS: readonly SearchPreset[] = [\n // Canonical names\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n // `:thinking` variants are /search-only. Setting one of these as the\n // MCP subprocess default makes `interpret` fail (decomposition isn't\n // valid on /interpret) — fine when the subprocess is only used for\n // `search`, otherwise override per-call via the `preset` tool arg.\n 'copass/copass_1.0:thinking',\n 'copass/copass_2.0:thinking',\n 'copass/1.0:thinking',\n 'copass/2.0:thinking',\n] as const;\n\n/**\n * Read config from `process.env`.\n *\n * - `COPASS_API_KEY` (required)\n * - `COPASS_SANDBOX_ID` (required)\n * - `COPASS_API_URL` (default: https://ai.copass.id)\n * - `COPASS_PROJECT_ID` (optional — default for retrieval/ingest)\n * - `COPASS_PRESET` (default: `copass/copass_1.0`). Accepts any\n * `SearchPreset` (including the `copass/X.0` short aliases), but\n * `:thinking` variants only work on `/search` — setting one as the\n * subprocess default breaks `interpret`. Leave at `copass/copass_1.0`\n * unless the subprocess is search-only, and override per-call via\n * the `preset` tool arg.\n *\n * Throws a descriptive error for missing/invalid values so the MCP client\n * sees an immediate startup failure with actionable text.\n */\nexport function loadConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig {\n const missing: string[] = [];\n const api_key = env.COPASS_API_KEY ?? '';\n const sandbox_id = env.COPASS_SANDBOX_ID ?? '';\n\n if (!api_key) missing.push('COPASS_API_KEY');\n if (!sandbox_id) missing.push('COPASS_SANDBOX_ID');\n\n if (missing.length > 0) {\n throw new Error(\n `@copass/mcp: missing required env var(s): ${missing.join(', ')}. ` +\n `Set them before launching the server.`,\n );\n }\n\n const api_url = env.COPASS_API_URL?.trim() || 'https://ai.copass.id';\n const project_id = env.COPASS_PROJECT_ID?.trim() || undefined;\n const rawPreset = env.COPASS_PRESET?.trim() || 'copass/copass_1.0';\n\n if (!VALID_PRESETS.includes(rawPreset as SearchPreset)) {\n throw new Error(\n `@copass/mcp: COPASS_PRESET must be one of ${VALID_PRESETS.join(', ')}; got \"${rawPreset}\"`,\n );\n }\n\n const ingest_data_source_id = env.COPASS_INGEST_DATA_SOURCE_ID?.trim() || undefined;\n const context_window_id = env.COPASS_CONTEXT_WINDOW_ID?.trim() || undefined;\n const context_window_initial_turns = parseInitialTurns(\n env.COPASS_CONTEXT_WINDOW_INITIAL_TURNS,\n );\n\n return {\n api_url,\n api_key,\n sandbox_id,\n project_id,\n preset: rawPreset as SearchPreset,\n ingest_data_source_id,\n context_window_id,\n context_window_initial_turns,\n };\n}\n\nfunction parseInitialTurns(raw: string | undefined): ChatMessage[] | undefined {\n if (!raw || !raw.trim()) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS is not valid JSON: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n if (!Array.isArray(parsed)) {\n throw new Error(\n '@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS must be a JSON array of {role, content} objects',\n );\n }\n const turns: ChatMessage[] = [];\n for (const [i, entry] of parsed.entries()) {\n if (\n !entry ||\n typeof entry !== 'object' ||\n typeof (entry as { role?: unknown }).role !== 'string' ||\n typeof (entry as { content?: unknown }).content !== 'string'\n ) {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS[${i}] must be {role: string, content: string}`,\n );\n }\n const { role, content } = entry as { role: string; content: string };\n if (role !== 'user' && role !== 'assistant' && role !== 'system') {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS[${i}].role must be \"user\" | \"assistant\" | \"system\"; got \"${role}\"`,\n );\n }\n turns.push({ role, content });\n }\n return turns;\n}\n"],"mappings":"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","names":["import_mcp","import_zod","mcpResult","mcpError","import_zod","mcpResult","mcpError"]} |
+26
-11
@@ -207,9 +207,12 @@ // src/server.ts | ||
| { | ||
| description: "Open a new Context Window \u2014 an ephemeral data source tracking this conversation. Subsequent retrieval calls become automatically window-aware, and every turn you record via `context_window_add_turn` is ingested into the graph so past turns become retrievable. Returns a `data_source_id`; persist it on your side if you may want to resume this conversation later via `context_window_attach`.", | ||
| description: 'Open a new Context Window \u2014 an ephemeral data source tracking this conversation. Subsequent retrieval calls become automatically window-aware, and every turn you record via `context_window_add_turn` is ingested into the graph so past turns become retrievable. Returns a `data_source_id`; persist it on your side if you may want to resume this conversation later via `context_window_attach`. Pass `participants` to set a default conversation roster \u2014 forwarded as the envelope `participants` field on every turn so downstream extraction can resolve second-person pronouns ("your X" \u2192 the OTHER listed participant).', | ||
| inputSchema: { | ||
| project_id: z2.string().optional().describe("Override the server default project_id."), | ||
| name: z2.string().optional().describe("Optional stable name for the underlying data source.") | ||
| name: z2.string().optional().describe("Optional stable name for the underlying data source."), | ||
| participants: z2.array(z2.string()).optional().describe( | ||
| 'Default conversation participant roster (e.g. ["User", "Assistant"]). Forwarded on every turn unless add_turn passes its own override.' | ||
| ) | ||
| } | ||
| }, | ||
| async ({ project_id, name }) => { | ||
| async ({ project_id, name, participants }) => { | ||
| try { | ||
@@ -219,3 +222,4 @@ const window = await client.contextWindow.create({ | ||
| project_id: project_id ?? config.project_id, | ||
| name | ||
| name, | ||
| participants | ||
| }); | ||
@@ -235,10 +239,16 @@ windows.set(window); | ||
| { | ||
| description: "Append a turn to the active Context Window and push it into the graph. Call on every user message AND every assistant message so the thread itself becomes retrievable. If you omit `data_source_id`, the active window is used.", | ||
| description: 'Append a turn to the active Context Window and push it into the graph. Call on every user message AND every assistant message so the thread itself becomes retrievable. If you omit `data_source_id`, the active window is used. Pass `name` when the speaker has a richer identity than `role` alone (e.g. an actual user name like "Alice"); falls back to capitalized `role` when absent. Pass `participants` to override the window\'s default roster for this turn only.', | ||
| inputSchema: { | ||
| role: z2.enum(["user", "assistant", "system"]).describe("Role of the speaker for this turn."), | ||
| content: z2.string().describe("The turn content."), | ||
| name: z2.string().optional().describe( | ||
| 'Optional named participant for this turn \u2014 forwarded as the envelope `speaker`. Caller-decided literal (e.g. "Alice", "support-bot"). When omitted, falls back to the capitalized role.' | ||
| ), | ||
| participants: z2.array(z2.string()).optional().describe( | ||
| "Per-turn participants override. Falls back to the window's constructor-time roster when omitted." | ||
| ), | ||
| data_source_id: z2.string().optional().describe("Override the active window id (for multi-window use cases).") | ||
| } | ||
| }, | ||
| async ({ role, content, data_source_id }) => { | ||
| async ({ role, content, name, participants, data_source_id }) => { | ||
| try { | ||
@@ -251,3 +261,3 @@ const window = windows.resolve(data_source_id); | ||
| } | ||
| await window.addTurn({ role, content }); | ||
| await window.addTurn({ role, content, name }, { participants }); | ||
| return mcpResult2({ | ||
@@ -271,8 +281,12 @@ data_source_id: window.dataSourceId, | ||
| role: z2.enum(["user", "assistant", "system"]), | ||
| content: z2.string() | ||
| content: z2.string(), | ||
| name: z2.string().optional() | ||
| }) | ||
| ).optional().describe("Optional pre-existing turns to seed the local buffer.") | ||
| ).optional().describe("Optional pre-existing turns to seed the local buffer."), | ||
| participants: z2.array(z2.string()).optional().describe( | ||
| "Default conversation participant roster \u2014 forwarded on every turn pushed via add_turn unless that call overrides." | ||
| ) | ||
| } | ||
| }, | ||
| async ({ data_source_id, initial_turns }) => { | ||
| async ({ data_source_id, initial_turns, participants }) => { | ||
| try { | ||
@@ -282,3 +296,4 @@ const window = await client.contextWindow.attach({ | ||
| data_source_id, | ||
| initialTurns: initial_turns | ||
| initialTurns: initial_turns, | ||
| participants | ||
| }); | ||
@@ -285,0 +300,0 @@ windows.set(window); |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/server.ts","../src/windows.ts","../src/tools/retrieval.ts","../src/tools/context-window.ts","../src/tools/ingest.ts","../src/config.ts"],"sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CopassClient } from '@copass/core';\nimport { registerToMcpServer } from '@copass/management/adapters/mcp';\nimport type { ServerConfig } from './config.js';\nimport { WindowRegistry } from './windows.js';\nimport { registerRetrievalTools } from './tools/retrieval.js';\nimport { registerContextWindowTools } from './tools/context-window.js';\nimport { registerIngestTool } from './tools/ingest.js';\n\nexport interface BuildServerOptions {\n client: CopassClient;\n config: ServerConfig;\n /**\n * Optional pre-populated window registry. Useful when a parent process has\n * already created or attached to a Context Window and wants the server to\n * start with it as the active window.\n */\n windows?: WindowRegistry;\n}\n\n/**\n * Assemble the Copass MCP server with every tool registered. Doesn't connect\n * a transport — see `startStdioServer` in `./bin.ts` for the default path,\n * or wire your own transport if embedding.\n */\nexport function buildServer({ client, config, windows }: BuildServerOptions): McpServer {\n const server = new McpServer(\n {\n name: 'copass',\n version: '0.3.0',\n },\n {\n capabilities: { tools: {} },\n },\n );\n\n const registry = windows ?? new WindowRegistry();\n const deps = { client, config, windows: registry };\n\n registerRetrievalTools(server, deps);\n registerContextWindowTools(server, deps);\n registerIngestTool(server, { client, config });\n\n // The full management tool corpus is exposed regardless of sandbox role.\n // Viewer-role users see write tools but get structured 403 from the\n // service layer when they call them. Avoids an extra HTTP round-trip\n // at MCP startup. See ADR 0007 Phase 4 brief §10 for trade-off.\n registerToMcpServer(server, client, { sandboxId: config.sandbox_id });\n\n return server;\n}\n","import type { ContextWindow } from '@copass/core';\n\n/**\n * In-memory registry of open Context Windows.\n *\n * Tracks a map of `data_source_id → ContextWindow` plus a single \"active\"\n * window id. Retrieval and add-turn tools use the active window implicitly\n * so the LLM doesn't have to thread an id on every call.\n *\n * For multi-window use cases, callers pass `data_source_id` explicitly and\n * the active id is ignored.\n */\nexport class WindowRegistry {\n private readonly windows = new Map<string, ContextWindow>();\n private activeId: string | null = null;\n\n /** Register a new window and make it active. */\n set(window: ContextWindow): void {\n this.windows.set(window.dataSourceId, window);\n this.activeId = window.dataSourceId;\n }\n\n /** Look up a window by id, or fall back to the active one. */\n resolve(dataSourceId?: string): ContextWindow | undefined {\n const id = dataSourceId ?? this.activeId;\n return id ? this.windows.get(id) : undefined;\n }\n\n /** Make an existing window the active one. Throws if unknown. */\n activate(dataSourceId: string): ContextWindow {\n const window = this.windows.get(dataSourceId);\n if (!window) {\n throw new Error(\n `No window registered for data_source_id=\"${dataSourceId}\" — call context_window_create or context_window_attach first.`,\n );\n }\n this.activeId = dataSourceId;\n return window;\n }\n\n /** Drop a window from the registry. Clears active if it matched. */\n drop(dataSourceId: string): void {\n this.windows.delete(dataSourceId);\n if (this.activeId === dataSourceId) this.activeId = null;\n }\n\n get activeDataSourceId(): string | null {\n return this.activeId;\n }\n}\n","import { z } from 'zod';\nimport {\n DISCOVER_QUERY_PARAM,\n INTERPRET_DESCRIPTION,\n INTERPRET_ITEMS_PARAM,\n INTERPRET_QUERY_PARAM,\n MCP_DISCOVER_DESCRIPTION,\n PRESET_PARAM,\n PROJECT_ID_PARAM,\n SEARCH_DESCRIPTION,\n SEARCH_QUERY_PARAM,\n} from '@copass/config';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\nimport type { WindowRegistry } from '../windows.js';\n\ninterface RetrievalDeps {\n client: CopassClient;\n config: ServerConfig;\n windows: WindowRegistry;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerRetrievalTools(server: McpServer, deps: RetrievalDeps): void {\n const { client, config, windows } = deps;\n\n server.registerTool(\n 'discover',\n {\n description: MCP_DISCOVER_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(DISCOVER_QUERY_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n // Per-call preset override. `:thinking` variants are\n // /search-only and rejected on /discover, so the enum here\n // intentionally omits them.\n preset: z\n .enum([\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n ])\n .optional()\n .describe(PRESET_PARAM),\n },\n },\n async ({ query, project_id, preset }) => {\n try {\n const response = await client.retrieval.discover(config.sandbox_id, {\n query,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n // Per-call override wins; otherwise inherit the subprocess\n // default. `:thinking` variants are stripped at the server\n // (/discover rejects them) so we don't second-guess here.\n preset: preset ?? config.preset,\n });\n return mcpResult({\n header: response.header,\n // Project the v2 fields (`subgraph` + `matched_query_nodes`)\n // alongside the v1 fields. Populated only under\n // `copass/copass_2.0` (or its `copass/2.0` alias); `null`\n // under v1.\n items: response.items.map((item) => ({\n score: item.score,\n summary: item.summary,\n canonical_ids: item.canonical_ids,\n subgraph: item.subgraph ?? null,\n matched_query_nodes: item.matched_query_nodes ?? null,\n })),\n next_steps: response.next_steps,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'interpret',\n {\n description: INTERPRET_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(INTERPRET_QUERY_PARAM),\n items: z.array(z.array(z.string()).min(1)).min(1).describe(INTERPRET_ITEMS_PARAM),\n preset: z.enum([\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n ]).optional().describe(PRESET_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n },\n },\n async ({ query, items, preset, project_id }) => {\n try {\n const response = await client.retrieval.interpret(config.sandbox_id, {\n query,\n items,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n preset: preset ?? config.preset,\n });\n return mcpResult({ brief: response.brief });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'search',\n {\n description: SEARCH_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(SEARCH_QUERY_PARAM),\n // `:thinking` variants split the question into sub-questions and\n // run the base preset on each before a combined synthesis.\n preset: z\n .enum([\n // Canonical names\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n 'copass/copass_1.0:thinking',\n 'copass/copass_2.0:thinking',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n 'copass/1.0:thinking',\n 'copass/2.0:thinking',\n ])\n .optional()\n .describe(PRESET_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n },\n },\n async ({ query, preset, project_id }) => {\n try {\n const response = await client.retrieval.search(config.sandbox_id, {\n query,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n preset: preset ?? config.preset,\n });\n return mcpResult({ answer: response.answer });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n","import { z } from 'zod';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\nimport type { WindowRegistry } from '../windows.js';\n\ninterface ContextWindowDeps {\n client: CopassClient;\n config: ServerConfig;\n windows: WindowRegistry;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerContextWindowTools(server: McpServer, deps: ContextWindowDeps): void {\n const { client, config, windows } = deps;\n\n server.registerTool(\n 'context_window_create',\n {\n description:\n 'Open a new Context Window — an ephemeral data source tracking this conversation. ' +\n 'Subsequent retrieval calls become automatically window-aware, and every turn you ' +\n 'record via `context_window_add_turn` is ingested into the graph so past turns ' +\n 'become retrievable. Returns a `data_source_id`; persist it on your side if you ' +\n 'may want to resume this conversation later via `context_window_attach`.',\n inputSchema: {\n project_id: z.string().optional().describe('Override the server default project_id.'),\n name: z.string().optional().describe('Optional stable name for the underlying data source.'),\n },\n },\n async ({ project_id, name }) => {\n try {\n const window = await client.contextWindow.create({\n sandbox_id: config.sandbox_id,\n project_id: project_id ?? config.project_id,\n name,\n });\n windows.set(window);\n return mcpResult({\n data_source_id: window.dataSourceId,\n active: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_add_turn',\n {\n description:\n 'Append a turn to the active Context Window and push it into the graph. Call on ' +\n 'every user message AND every assistant message so the thread itself becomes ' +\n 'retrievable. If you omit `data_source_id`, the active window is used.',\n inputSchema: {\n role: z\n .enum(['user', 'assistant', 'system'])\n .describe('Role of the speaker for this turn.'),\n content: z.string().describe('The turn content.'),\n data_source_id: z\n .string()\n .optional()\n .describe('Override the active window id (for multi-window use cases).'),\n },\n },\n async ({ role, content, data_source_id }) => {\n try {\n const window = windows.resolve(data_source_id);\n if (!window) {\n throw new Error(\n 'No active Context Window — call context_window_create or context_window_attach first.',\n );\n }\n await window.addTurn({ role, content });\n return mcpResult({\n data_source_id: window.dataSourceId,\n turn_count: window.getTurns().length,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_attach',\n {\n description:\n 'Resume an existing Context Window by `data_source_id` (one you previously persisted ' +\n 'on your side after `context_window_create`). Makes the resumed window the active ' +\n 'window. Pass `initial_turns` to seed the local buffer for immediate window-aware ' +\n 'retrieval, otherwise the buffer starts empty and fills as you call add_turn.',\n inputSchema: {\n data_source_id: z.string().describe('Id of the Context Window data source to resume.'),\n initial_turns: z\n .array(\n z.object({\n role: z.enum(['user', 'assistant', 'system']),\n content: z.string(),\n }),\n )\n .optional()\n .describe('Optional pre-existing turns to seed the local buffer.'),\n },\n },\n async ({ data_source_id, initial_turns }) => {\n try {\n const window = await client.contextWindow.attach({\n sandbox_id: config.sandbox_id,\n data_source_id,\n initialTurns: initial_turns,\n });\n windows.set(window);\n return mcpResult({\n data_source_id: window.dataSourceId,\n turn_count: window.getTurns().length,\n active: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_close',\n {\n description:\n 'Close a Context Window — flips the underlying data source to `disconnected` ' +\n 'immediately instead of waiting for TTL. Best-effort; idempotent. If you omit ' +\n '`data_source_id`, the active window is closed.',\n inputSchema: {\n data_source_id: z\n .string()\n .optional()\n .describe('Override the active window id.'),\n },\n },\n async ({ data_source_id }) => {\n try {\n const window = windows.resolve(data_source_id);\n if (!window) {\n throw new Error('No active Context Window to close.');\n }\n await window.close();\n windows.drop(window.dataSourceId);\n return mcpResult({\n data_source_id: window.dataSourceId,\n closed: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n","import { z } from 'zod';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\n\ninterface IngestDeps {\n client: CopassClient;\n config: ServerConfig;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerIngestTool(server: McpServer, deps: IngestDeps): void {\n const { client, config } = deps;\n\n server.registerTool(\n 'ingest',\n {\n description:\n 'Push content into the knowledge graph via a data source. Use for: architecture ' +\n 'decisions (source_type: \"decision\"), user-shared context (\"user_input\"), ' +\n 'corrections (\"correction\"), durable notes, any significant new concept. Do NOT ' +\n 'ingest trivial changes or ephemeral debug context — those belong in the Context ' +\n 'Window (via context_window_add_turn). Pass `data_source_id` explicitly or set ' +\n '`COPASS_INGEST_DATA_SOURCE_ID` in the server env for an implicit target.',\n inputSchema: {\n content: z.string().min(1).describe('The content to ingest.'),\n source_type: z\n .string()\n .optional()\n .describe(\n 'Type tag: code, markdown, json, text, conversation, decision, correction, ' +\n 'user_input. Defaults to \"text\" when omitted.',\n ),\n data_source_id: z\n .string()\n .optional()\n .describe(\n 'Target data source. Falls back to COPASS_INGEST_DATA_SOURCE_ID env var.',\n ),\n project_id: z.string().optional().describe('Override the server default project_id.'),\n storage_only: z\n .boolean()\n .optional()\n .describe('If true, chunk and store but skip ontology ingestion.'),\n occurred_at: z\n .string()\n .optional()\n .describe(\n 'Optional ISO 8601 timestamp anchoring the content to a real-world moment ' +\n '(e.g. when this decision was made, when the user said it). Used as the ' +\n 'default occurred_at for any composed event whose own timestamp is null, ' +\n 'so temporal queries can find it.',\n ),\n },\n },\n async ({ content, source_type, data_source_id, project_id, storage_only, occurred_at }) => {\n try {\n const sourceId = data_source_id ?? config.ingest_data_source_id;\n if (!sourceId) {\n throw new Error(\n 'ingest requires a `data_source_id` argument or COPASS_INGEST_DATA_SOURCE_ID env var. ' +\n 'Register a data source via the REST API or CLI, then pass its id here.',\n );\n }\n\n const response = await client.sources.ingest(config.sandbox_id, sourceId, {\n text: content,\n source_type: source_type ?? 'text',\n project_id: project_id ?? config.project_id,\n storage_only,\n occurred_at,\n });\n\n return mcpResult({\n job_id: response.job_id,\n status: response.status,\n data_source_id: sourceId,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n","import type { ChatMessage, SearchPreset } from '@copass/core';\n\nexport interface ServerConfig {\n api_url: string;\n /**\n * Required when the standalone `copass-mcp` bin builds its own\n * `CopassClient` from this config (`bin.ts`). Optional for embedded\n * use — when a host process (e.g. another MCP server) constructs the\n * `CopassClient` itself and passes it into `buildServer({ client })`,\n * the tool handlers never read this field.\n */\n api_key?: string;\n sandbox_id: string;\n project_id?: string;\n preset: SearchPreset;\n /** Default data_source_id for `ingest` when the caller doesn't pass one. */\n ingest_data_source_id?: string;\n /**\n * If set, the server attaches to this Context Window on startup and makes\n * it the active window. Use when another process (e.g. a Hono server)\n * already created the window and you're launching the MCP server as a\n * subprocess that should share it.\n */\n context_window_id?: string;\n /**\n * Optional pre-existing turns to seed the Context Window's buffer on\n * startup. Makes retrieval immediately window-aware on the first tool\n * call — without these, the first `discover` / `interpret` / `search`\n * after a subprocess spawn sees an empty history.\n *\n * Required when `context_window_id` refers to a thread that has prior\n * turns and the server should dedupe retrieval against them.\n */\n context_window_initial_turns?: ChatMessage[];\n}\n\nconst VALID_PRESETS: readonly SearchPreset[] = [\n // Canonical names\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n // `:thinking` variants are /search-only. Setting one of these as the\n // MCP subprocess default makes `interpret` fail (decomposition isn't\n // valid on /interpret) — fine when the subprocess is only used for\n // `search`, otherwise override per-call via the `preset` tool arg.\n 'copass/copass_1.0:thinking',\n 'copass/copass_2.0:thinking',\n 'copass/1.0:thinking',\n 'copass/2.0:thinking',\n] as const;\n\n/**\n * Read config from `process.env`.\n *\n * - `COPASS_API_KEY` (required)\n * - `COPASS_SANDBOX_ID` (required)\n * - `COPASS_API_URL` (default: https://ai.copass.id)\n * - `COPASS_PROJECT_ID` (optional — default for retrieval/ingest)\n * - `COPASS_PRESET` (default: `copass/copass_1.0`). Accepts any\n * `SearchPreset` (including the `copass/X.0` short aliases), but\n * `:thinking` variants only work on `/search` — setting one as the\n * subprocess default breaks `interpret`. Leave at `copass/copass_1.0`\n * unless the subprocess is search-only, and override per-call via\n * the `preset` tool arg.\n *\n * Throws a descriptive error for missing/invalid values so the MCP client\n * sees an immediate startup failure with actionable text.\n */\nexport function loadConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig {\n const missing: string[] = [];\n const api_key = env.COPASS_API_KEY ?? '';\n const sandbox_id = env.COPASS_SANDBOX_ID ?? '';\n\n if (!api_key) missing.push('COPASS_API_KEY');\n if (!sandbox_id) missing.push('COPASS_SANDBOX_ID');\n\n if (missing.length > 0) {\n throw new Error(\n `@copass/mcp: missing required env var(s): ${missing.join(', ')}. ` +\n `Set them before launching the server.`,\n );\n }\n\n const api_url = env.COPASS_API_URL?.trim() || 'https://ai.copass.id';\n const project_id = env.COPASS_PROJECT_ID?.trim() || undefined;\n const rawPreset = env.COPASS_PRESET?.trim() || 'copass/copass_1.0';\n\n if (!VALID_PRESETS.includes(rawPreset as SearchPreset)) {\n throw new Error(\n `@copass/mcp: COPASS_PRESET must be one of ${VALID_PRESETS.join(', ')}; got \"${rawPreset}\"`,\n );\n }\n\n const ingest_data_source_id = env.COPASS_INGEST_DATA_SOURCE_ID?.trim() || undefined;\n const context_window_id = env.COPASS_CONTEXT_WINDOW_ID?.trim() || undefined;\n const context_window_initial_turns = parseInitialTurns(\n env.COPASS_CONTEXT_WINDOW_INITIAL_TURNS,\n );\n\n return {\n api_url,\n api_key,\n sandbox_id,\n project_id,\n preset: rawPreset as SearchPreset,\n ingest_data_source_id,\n context_window_id,\n context_window_initial_turns,\n };\n}\n\nfunction parseInitialTurns(raw: string | undefined): ChatMessage[] | undefined {\n if (!raw || !raw.trim()) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS is not valid JSON: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n if (!Array.isArray(parsed)) {\n throw new Error(\n '@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS must be a JSON array of {role, content} objects',\n );\n }\n const turns: ChatMessage[] = [];\n for (const [i, entry] of parsed.entries()) {\n if (\n !entry ||\n typeof entry !== 'object' ||\n typeof (entry as { role?: unknown }).role !== 'string' ||\n typeof (entry as { content?: unknown }).content !== 'string'\n ) {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS[${i}] must be {role: string, content: string}`,\n );\n }\n const { role, content } = entry as { role: string; content: string };\n if (role !== 'user' && role !== 'assistant' && role !== 'system') {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS[${i}].role must be \"user\" | \"assistant\" | \"system\"; got \"${role}\"`,\n );\n }\n turns.push({ role, content });\n }\n return turns;\n}\n"],"mappings":"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","names":["z","mcpResult","mcpError","z","mcpResult","mcpError"]} | ||
| {"version":3,"sources":["../src/server.ts","../src/windows.ts","../src/tools/retrieval.ts","../src/tools/context-window.ts","../src/tools/ingest.ts","../src/config.ts"],"sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CopassClient } from '@copass/core';\nimport { registerToMcpServer } from '@copass/management/adapters/mcp';\nimport type { ServerConfig } from './config.js';\nimport { WindowRegistry } from './windows.js';\nimport { registerRetrievalTools } from './tools/retrieval.js';\nimport { registerContextWindowTools } from './tools/context-window.js';\nimport { registerIngestTool } from './tools/ingest.js';\n\nexport interface BuildServerOptions {\n client: CopassClient;\n config: ServerConfig;\n /**\n * Optional pre-populated window registry. Useful when a parent process has\n * already created or attached to a Context Window and wants the server to\n * start with it as the active window.\n */\n windows?: WindowRegistry;\n}\n\n/**\n * Assemble the Copass MCP server with every tool registered. Doesn't connect\n * a transport — see `startStdioServer` in `./bin.ts` for the default path,\n * or wire your own transport if embedding.\n */\nexport function buildServer({ client, config, windows }: BuildServerOptions): McpServer {\n const server = new McpServer(\n {\n name: 'copass',\n version: '0.3.0',\n },\n {\n capabilities: { tools: {} },\n },\n );\n\n const registry = windows ?? new WindowRegistry();\n const deps = { client, config, windows: registry };\n\n registerRetrievalTools(server, deps);\n registerContextWindowTools(server, deps);\n registerIngestTool(server, { client, config });\n\n // The full management tool corpus is exposed regardless of sandbox role.\n // Viewer-role users see write tools but get structured 403 from the\n // service layer when they call them. Avoids an extra HTTP round-trip\n // at MCP startup. See ADR 0007 Phase 4 brief §10 for trade-off.\n registerToMcpServer(server, client, { sandboxId: config.sandbox_id });\n\n return server;\n}\n","import type { ContextWindow } from '@copass/core';\n\n/**\n * In-memory registry of open Context Windows.\n *\n * Tracks a map of `data_source_id → ContextWindow` plus a single \"active\"\n * window id. Retrieval and add-turn tools use the active window implicitly\n * so the LLM doesn't have to thread an id on every call.\n *\n * For multi-window use cases, callers pass `data_source_id` explicitly and\n * the active id is ignored.\n */\nexport class WindowRegistry {\n private readonly windows = new Map<string, ContextWindow>();\n private activeId: string | null = null;\n\n /** Register a new window and make it active. */\n set(window: ContextWindow): void {\n this.windows.set(window.dataSourceId, window);\n this.activeId = window.dataSourceId;\n }\n\n /** Look up a window by id, or fall back to the active one. */\n resolve(dataSourceId?: string): ContextWindow | undefined {\n const id = dataSourceId ?? this.activeId;\n return id ? this.windows.get(id) : undefined;\n }\n\n /** Make an existing window the active one. Throws if unknown. */\n activate(dataSourceId: string): ContextWindow {\n const window = this.windows.get(dataSourceId);\n if (!window) {\n throw new Error(\n `No window registered for data_source_id=\"${dataSourceId}\" — call context_window_create or context_window_attach first.`,\n );\n }\n this.activeId = dataSourceId;\n return window;\n }\n\n /** Drop a window from the registry. Clears active if it matched. */\n drop(dataSourceId: string): void {\n this.windows.delete(dataSourceId);\n if (this.activeId === dataSourceId) this.activeId = null;\n }\n\n get activeDataSourceId(): string | null {\n return this.activeId;\n }\n}\n","import { z } from 'zod';\nimport {\n DISCOVER_QUERY_PARAM,\n INTERPRET_DESCRIPTION,\n INTERPRET_ITEMS_PARAM,\n INTERPRET_QUERY_PARAM,\n MCP_DISCOVER_DESCRIPTION,\n PRESET_PARAM,\n PROJECT_ID_PARAM,\n SEARCH_DESCRIPTION,\n SEARCH_QUERY_PARAM,\n} from '@copass/config';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\nimport type { WindowRegistry } from '../windows.js';\n\ninterface RetrievalDeps {\n client: CopassClient;\n config: ServerConfig;\n windows: WindowRegistry;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerRetrievalTools(server: McpServer, deps: RetrievalDeps): void {\n const { client, config, windows } = deps;\n\n server.registerTool(\n 'discover',\n {\n description: MCP_DISCOVER_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(DISCOVER_QUERY_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n // Per-call preset override. `:thinking` variants are\n // /search-only and rejected on /discover, so the enum here\n // intentionally omits them.\n preset: z\n .enum([\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n ])\n .optional()\n .describe(PRESET_PARAM),\n },\n },\n async ({ query, project_id, preset }) => {\n try {\n const response = await client.retrieval.discover(config.sandbox_id, {\n query,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n // Per-call override wins; otherwise inherit the subprocess\n // default. `:thinking` variants are stripped at the server\n // (/discover rejects them) so we don't second-guess here.\n preset: preset ?? config.preset,\n });\n return mcpResult({\n header: response.header,\n // Project the v2 fields (`subgraph` + `matched_query_nodes`)\n // alongside the v1 fields. Populated only under\n // `copass/copass_2.0` (or its `copass/2.0` alias); `null`\n // under v1.\n items: response.items.map((item) => ({\n score: item.score,\n summary: item.summary,\n canonical_ids: item.canonical_ids,\n subgraph: item.subgraph ?? null,\n matched_query_nodes: item.matched_query_nodes ?? null,\n })),\n next_steps: response.next_steps,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'interpret',\n {\n description: INTERPRET_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(INTERPRET_QUERY_PARAM),\n items: z.array(z.array(z.string()).min(1)).min(1).describe(INTERPRET_ITEMS_PARAM),\n preset: z.enum([\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n ]).optional().describe(PRESET_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n },\n },\n async ({ query, items, preset, project_id }) => {\n try {\n const response = await client.retrieval.interpret(config.sandbox_id, {\n query,\n items,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n preset: preset ?? config.preset,\n });\n return mcpResult({ brief: response.brief });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'search',\n {\n description: SEARCH_DESCRIPTION,\n inputSchema: {\n query: z.string().describe(SEARCH_QUERY_PARAM),\n // `:thinking` variants split the question into sub-questions and\n // run the base preset on each before a combined synthesis.\n preset: z\n .enum([\n // Canonical names\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n 'copass/copass_1.0:thinking',\n 'copass/copass_2.0:thinking',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n 'copass/1.0:thinking',\n 'copass/2.0:thinking',\n ])\n .optional()\n .describe(PRESET_PARAM),\n project_id: z.string().optional().describe(PROJECT_ID_PARAM),\n },\n },\n async ({ query, preset, project_id }) => {\n try {\n const response = await client.retrieval.search(config.sandbox_id, {\n query,\n project_id: project_id ?? config.project_id,\n window: windows.resolve(),\n preset: preset ?? config.preset,\n });\n return mcpResult({ answer: response.answer });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n","import { z } from 'zod';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\nimport type { WindowRegistry } from '../windows.js';\n\ninterface ContextWindowDeps {\n client: CopassClient;\n config: ServerConfig;\n windows: WindowRegistry;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerContextWindowTools(server: McpServer, deps: ContextWindowDeps): void {\n const { client, config, windows } = deps;\n\n server.registerTool(\n 'context_window_create',\n {\n description:\n 'Open a new Context Window — an ephemeral data source tracking this conversation. ' +\n 'Subsequent retrieval calls become automatically window-aware, and every turn you ' +\n 'record via `context_window_add_turn` is ingested into the graph so past turns ' +\n 'become retrievable. Returns a `data_source_id`; persist it on your side if you ' +\n 'may want to resume this conversation later via `context_window_attach`. Pass ' +\n '`participants` to set a default conversation roster — forwarded as the envelope ' +\n '`participants` field on every turn so downstream extraction can resolve ' +\n 'second-person pronouns (\"your X\" → the OTHER listed participant).',\n inputSchema: {\n project_id: z.string().optional().describe('Override the server default project_id.'),\n name: z.string().optional().describe('Optional stable name for the underlying data source.'),\n participants: z\n .array(z.string())\n .optional()\n .describe(\n 'Default conversation participant roster (e.g. [\"User\", \"Assistant\"]). ' +\n 'Forwarded on every turn unless add_turn passes its own override.',\n ),\n },\n },\n async ({ project_id, name, participants }) => {\n try {\n const window = await client.contextWindow.create({\n sandbox_id: config.sandbox_id,\n project_id: project_id ?? config.project_id,\n name,\n participants,\n });\n windows.set(window);\n return mcpResult({\n data_source_id: window.dataSourceId,\n active: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_add_turn',\n {\n description:\n 'Append a turn to the active Context Window and push it into the graph. Call on ' +\n 'every user message AND every assistant message so the thread itself becomes ' +\n 'retrievable. If you omit `data_source_id`, the active window is used. Pass `name` ' +\n 'when the speaker has a richer identity than `role` alone (e.g. an actual user name ' +\n 'like \"Alice\"); falls back to capitalized `role` when absent. Pass `participants` ' +\n 'to override the window\\'s default roster for this turn only.',\n inputSchema: {\n role: z\n .enum(['user', 'assistant', 'system'])\n .describe('Role of the speaker for this turn.'),\n content: z.string().describe('The turn content.'),\n name: z\n .string()\n .optional()\n .describe(\n 'Optional named participant for this turn — forwarded as the envelope ' +\n '`speaker`. Caller-decided literal (e.g. \"Alice\", \"support-bot\"). When ' +\n 'omitted, falls back to the capitalized role.',\n ),\n participants: z\n .array(z.string())\n .optional()\n .describe(\n 'Per-turn participants override. Falls back to the window\\'s ' +\n 'constructor-time roster when omitted.',\n ),\n data_source_id: z\n .string()\n .optional()\n .describe('Override the active window id (for multi-window use cases).'),\n },\n },\n async ({ role, content, name, participants, data_source_id }) => {\n try {\n const window = windows.resolve(data_source_id);\n if (!window) {\n throw new Error(\n 'No active Context Window — call context_window_create or context_window_attach first.',\n );\n }\n await window.addTurn({ role, content, name }, { participants });\n return mcpResult({\n data_source_id: window.dataSourceId,\n turn_count: window.getTurns().length,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_attach',\n {\n description:\n 'Resume an existing Context Window by `data_source_id` (one you previously persisted ' +\n 'on your side after `context_window_create`). Makes the resumed window the active ' +\n 'window. Pass `initial_turns` to seed the local buffer for immediate window-aware ' +\n 'retrieval, otherwise the buffer starts empty and fills as you call add_turn.',\n inputSchema: {\n data_source_id: z.string().describe('Id of the Context Window data source to resume.'),\n initial_turns: z\n .array(\n z.object({\n role: z.enum(['user', 'assistant', 'system']),\n content: z.string(),\n name: z.string().optional(),\n }),\n )\n .optional()\n .describe('Optional pre-existing turns to seed the local buffer.'),\n participants: z\n .array(z.string())\n .optional()\n .describe(\n 'Default conversation participant roster — forwarded on every turn ' +\n 'pushed via add_turn unless that call overrides.',\n ),\n },\n },\n async ({ data_source_id, initial_turns, participants }) => {\n try {\n const window = await client.contextWindow.attach({\n sandbox_id: config.sandbox_id,\n data_source_id,\n initialTurns: initial_turns,\n participants,\n });\n windows.set(window);\n return mcpResult({\n data_source_id: window.dataSourceId,\n turn_count: window.getTurns().length,\n active: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n\n server.registerTool(\n 'context_window_close',\n {\n description:\n 'Close a Context Window — flips the underlying data source to `disconnected` ' +\n 'immediately instead of waiting for TTL. Best-effort; idempotent. If you omit ' +\n '`data_source_id`, the active window is closed.',\n inputSchema: {\n data_source_id: z\n .string()\n .optional()\n .describe('Override the active window id.'),\n },\n },\n async ({ data_source_id }) => {\n try {\n const window = windows.resolve(data_source_id);\n if (!window) {\n throw new Error('No active Context Window to close.');\n }\n await window.close();\n windows.drop(window.dataSourceId);\n return mcpResult({\n data_source_id: window.dataSourceId,\n closed: true,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n","import { z } from 'zod';\nimport type { CopassClient } from '@copass/core';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ServerConfig } from '../config.js';\n\ninterface IngestDeps {\n client: CopassClient;\n config: ServerConfig;\n}\n\nfunction mcpResult(payload: unknown) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction mcpError(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n}\n\nexport function registerIngestTool(server: McpServer, deps: IngestDeps): void {\n const { client, config } = deps;\n\n server.registerTool(\n 'ingest',\n {\n description:\n 'Push content into the knowledge graph via a data source. Use for: architecture ' +\n 'decisions (source_type: \"decision\"), user-shared context (\"user_input\"), ' +\n 'corrections (\"correction\"), durable notes, any significant new concept. Do NOT ' +\n 'ingest trivial changes or ephemeral debug context — those belong in the Context ' +\n 'Window (via context_window_add_turn). Pass `data_source_id` explicitly or set ' +\n '`COPASS_INGEST_DATA_SOURCE_ID` in the server env for an implicit target.',\n inputSchema: {\n content: z.string().min(1).describe('The content to ingest.'),\n source_type: z\n .string()\n .optional()\n .describe(\n 'Type tag: code, markdown, json, text, conversation, decision, correction, ' +\n 'user_input. Defaults to \"text\" when omitted.',\n ),\n data_source_id: z\n .string()\n .optional()\n .describe(\n 'Target data source. Falls back to COPASS_INGEST_DATA_SOURCE_ID env var.',\n ),\n project_id: z.string().optional().describe('Override the server default project_id.'),\n storage_only: z\n .boolean()\n .optional()\n .describe('If true, chunk and store but skip ontology ingestion.'),\n occurred_at: z\n .string()\n .optional()\n .describe(\n 'Optional ISO 8601 timestamp anchoring the content to a real-world moment ' +\n '(e.g. when this decision was made, when the user said it). Used as the ' +\n 'default occurred_at for any composed event whose own timestamp is null, ' +\n 'so temporal queries can find it.',\n ),\n },\n },\n async ({ content, source_type, data_source_id, project_id, storage_only, occurred_at }) => {\n try {\n const sourceId = data_source_id ?? config.ingest_data_source_id;\n if (!sourceId) {\n throw new Error(\n 'ingest requires a `data_source_id` argument or COPASS_INGEST_DATA_SOURCE_ID env var. ' +\n 'Register a data source via the REST API or CLI, then pass its id here.',\n );\n }\n\n const response = await client.sources.ingest(config.sandbox_id, sourceId, {\n text: content,\n source_type: source_type ?? 'text',\n project_id: project_id ?? config.project_id,\n storage_only,\n occurred_at,\n });\n\n return mcpResult({\n job_id: response.job_id,\n status: response.status,\n data_source_id: sourceId,\n });\n } catch (e) {\n return mcpError(e);\n }\n },\n );\n}\n","import type { ChatMessage, SearchPreset } from '@copass/core';\n\nexport interface ServerConfig {\n api_url: string;\n /**\n * Required when the standalone `copass-mcp` bin builds its own\n * `CopassClient` from this config (`bin.ts`). Optional for embedded\n * use — when a host process (e.g. another MCP server) constructs the\n * `CopassClient` itself and passes it into `buildServer({ client })`,\n * the tool handlers never read this field.\n */\n api_key?: string;\n sandbox_id: string;\n project_id?: string;\n preset: SearchPreset;\n /** Default data_source_id for `ingest` when the caller doesn't pass one. */\n ingest_data_source_id?: string;\n /**\n * If set, the server attaches to this Context Window on startup and makes\n * it the active window. Use when another process (e.g. a Hono server)\n * already created the window and you're launching the MCP server as a\n * subprocess that should share it.\n */\n context_window_id?: string;\n /**\n * Optional pre-existing turns to seed the Context Window's buffer on\n * startup. Makes retrieval immediately window-aware on the first tool\n * call — without these, the first `discover` / `interpret` / `search`\n * after a subprocess spawn sees an empty history.\n *\n * Required when `context_window_id` refers to a thread that has prior\n * turns and the server should dedupe retrieval against them.\n */\n context_window_initial_turns?: ChatMessage[];\n}\n\nconst VALID_PRESETS: readonly SearchPreset[] = [\n // Canonical names\n 'copass/copass_1.0',\n 'copass/copass_2.0',\n // Short aliases (kept for backward-compat)\n 'copass/1.0',\n 'copass/2.0',\n // `:thinking` variants are /search-only. Setting one of these as the\n // MCP subprocess default makes `interpret` fail (decomposition isn't\n // valid on /interpret) — fine when the subprocess is only used for\n // `search`, otherwise override per-call via the `preset` tool arg.\n 'copass/copass_1.0:thinking',\n 'copass/copass_2.0:thinking',\n 'copass/1.0:thinking',\n 'copass/2.0:thinking',\n] as const;\n\n/**\n * Read config from `process.env`.\n *\n * - `COPASS_API_KEY` (required)\n * - `COPASS_SANDBOX_ID` (required)\n * - `COPASS_API_URL` (default: https://ai.copass.id)\n * - `COPASS_PROJECT_ID` (optional — default for retrieval/ingest)\n * - `COPASS_PRESET` (default: `copass/copass_1.0`). Accepts any\n * `SearchPreset` (including the `copass/X.0` short aliases), but\n * `:thinking` variants only work on `/search` — setting one as the\n * subprocess default breaks `interpret`. Leave at `copass/copass_1.0`\n * unless the subprocess is search-only, and override per-call via\n * the `preset` tool arg.\n *\n * Throws a descriptive error for missing/invalid values so the MCP client\n * sees an immediate startup failure with actionable text.\n */\nexport function loadConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig {\n const missing: string[] = [];\n const api_key = env.COPASS_API_KEY ?? '';\n const sandbox_id = env.COPASS_SANDBOX_ID ?? '';\n\n if (!api_key) missing.push('COPASS_API_KEY');\n if (!sandbox_id) missing.push('COPASS_SANDBOX_ID');\n\n if (missing.length > 0) {\n throw new Error(\n `@copass/mcp: missing required env var(s): ${missing.join(', ')}. ` +\n `Set them before launching the server.`,\n );\n }\n\n const api_url = env.COPASS_API_URL?.trim() || 'https://ai.copass.id';\n const project_id = env.COPASS_PROJECT_ID?.trim() || undefined;\n const rawPreset = env.COPASS_PRESET?.trim() || 'copass/copass_1.0';\n\n if (!VALID_PRESETS.includes(rawPreset as SearchPreset)) {\n throw new Error(\n `@copass/mcp: COPASS_PRESET must be one of ${VALID_PRESETS.join(', ')}; got \"${rawPreset}\"`,\n );\n }\n\n const ingest_data_source_id = env.COPASS_INGEST_DATA_SOURCE_ID?.trim() || undefined;\n const context_window_id = env.COPASS_CONTEXT_WINDOW_ID?.trim() || undefined;\n const context_window_initial_turns = parseInitialTurns(\n env.COPASS_CONTEXT_WINDOW_INITIAL_TURNS,\n );\n\n return {\n api_url,\n api_key,\n sandbox_id,\n project_id,\n preset: rawPreset as SearchPreset,\n ingest_data_source_id,\n context_window_id,\n context_window_initial_turns,\n };\n}\n\nfunction parseInitialTurns(raw: string | undefined): ChatMessage[] | undefined {\n if (!raw || !raw.trim()) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS is not valid JSON: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n if (!Array.isArray(parsed)) {\n throw new Error(\n '@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS must be a JSON array of {role, content} objects',\n );\n }\n const turns: ChatMessage[] = [];\n for (const [i, entry] of parsed.entries()) {\n if (\n !entry ||\n typeof entry !== 'object' ||\n typeof (entry as { role?: unknown }).role !== 'string' ||\n typeof (entry as { content?: unknown }).content !== 'string'\n ) {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS[${i}] must be {role: string, content: string}`,\n );\n }\n const { role, content } = entry as { role: string; content: string };\n if (role !== 'user' && role !== 'assistant' && role !== 'system') {\n throw new Error(\n `@copass/mcp: COPASS_CONTEXT_WINDOW_INITIAL_TURNS[${i}].role must be \"user\" | \"assistant\" | \"system\"; got \"${role}\"`,\n );\n }\n turns.push({ role, content });\n }\n return turns;\n}\n"],"mappings":"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","names":["z","mcpResult","mcpError","z","mcpResult","mcpError"]} |
+2
-2
| { | ||
| "name": "@copass/mcp", | ||
| "version": "0.5.9", | ||
| "version": "0.5.10", | ||
| "description": "Standalone MCP server for Copass — drop-in discover/interpret/search + Context Window tools for any MCP client (Claude Agent SDK, Claude Code, Claude Desktop, Cursor)", | ||
@@ -77,3 +77,3 @@ "publishConfig": { | ||
| }, | ||
| "gitHead": "89abb7a320314feed66ff82fcb251f41ad2a70cb" | ||
| "gitHead": "9d619cc3e016244aa2d1245973c702e38f7dac17" | ||
| } |
+37
-0
@@ -78,2 +78,39 @@ # @copass/mcp | ||
| ### Conversation metadata: speaker, participants, name | ||
| `context_window_create` and `context_window_attach` accept an optional `participants` array — the default conversation roster forwarded on every turn. `context_window_add_turn` accepts an optional `name` (per-turn speaker) and `participants` (per-turn override). | ||
| ```jsonc | ||
| // Establish a multi-party roster once at create-time. | ||
| { | ||
| "tool": "context_window_create", | ||
| "arguments": { | ||
| "participants": ["Alice", "Bob", "Carol"] | ||
| } | ||
| } | ||
| // Per-turn name → richer speaker than the role-derived default. | ||
| { | ||
| "tool": "context_window_add_turn", | ||
| "arguments": { | ||
| "role": "user", | ||
| "content": "Hey Bob, did you finish the report?", | ||
| "name": "Alice" | ||
| } | ||
| } | ||
| // Per-turn participants override (roster shifted mid-thread). | ||
| { | ||
| "tool": "context_window_add_turn", | ||
| "arguments": { | ||
| "role": "user", | ||
| "content": "Carol just joined.", | ||
| "name": "Alice", | ||
| "participants": ["Alice", "Bob", "Carol", "Dave"] | ||
| } | ||
| } | ||
| ``` | ||
| Omit any of these fields and the existing role-derived defaults apply (`speaker = capitalize(role)`, no participants). Useful when you want richer attribution than role alone, e.g. real user names, multi-party chats, or thread-scoped rosters. | ||
| **Writeback:** | ||
@@ -80,0 +117,0 @@ - `ingest` — push durable content into the graph |
253470
6.92%2213
3.17%215
20.79%