@agimon-ai/doompi-mcp 0.0.1-alpha.13 → 0.0.1-alpha.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/README.md +21 -14
  2. package/dist/adapters/node/configSources.cjs +2 -0
  3. package/dist/adapters/node/configSources.cjs.map +1 -0
  4. package/dist/adapters/node/configSources.d.cts +16 -0
  5. package/dist/adapters/node/configSources.d.cts.map +1 -0
  6. package/dist/adapters/node/configSources.d.mts +16 -0
  7. package/dist/adapters/node/configSources.d.mts.map +1 -0
  8. package/dist/adapters/node/configSources.mjs.map +1 -0
  9. package/dist/adapters/node/keyringTokenStore.cjs.map +1 -0
  10. package/dist/adapters/node/keyringTokenStore.mjs.map +1 -0
  11. package/dist/adapters/node/mcpRuntime.cjs.map +1 -0
  12. package/dist/{services → adapters/node}/mcpRuntime.d.cts +4 -11
  13. package/dist/adapters/node/mcpRuntime.d.cts.map +1 -0
  14. package/dist/{services → adapters/node}/mcpRuntime.d.mts +4 -11
  15. package/dist/adapters/node/mcpRuntime.d.mts.map +1 -0
  16. package/dist/adapters/node/mcpRuntime.mjs.map +1 -0
  17. package/dist/adapters/pi/doomExtension.cjs +2 -0
  18. package/dist/adapters/pi/doomExtension.cjs.map +1 -0
  19. package/dist/adapters/pi/doomExtension.d.cts +7 -0
  20. package/dist/adapters/pi/doomExtension.d.cts.map +1 -0
  21. package/dist/adapters/pi/doomExtension.d.mts +7 -0
  22. package/dist/adapters/pi/doomExtension.d.mts.map +1 -0
  23. package/dist/adapters/pi/doomExtension.mjs +2 -0
  24. package/dist/adapters/pi/doomExtension.mjs.map +1 -0
  25. package/dist/adapters/pi/extension.cjs +1 -1
  26. package/dist/adapters/pi/extension.cjs.map +1 -1
  27. package/dist/adapters/pi/extension.d.cts +2 -1
  28. package/dist/adapters/pi/extension.d.cts.map +1 -1
  29. package/dist/adapters/pi/extension.d.mts +2 -1
  30. package/dist/adapters/pi/extension.d.mts.map +1 -1
  31. package/dist/adapters/pi/extension.mjs +1 -1
  32. package/dist/adapters/pi/extension.mjs.map +1 -1
  33. package/dist/adapters/pi/leader.cjs +1 -1
  34. package/dist/adapters/pi/leader.cjs.map +1 -1
  35. package/dist/adapters/pi/leader.mjs +1 -1
  36. package/dist/adapters/pi/leader.mjs.map +1 -1
  37. package/dist/adapters/pi/mcpConstants.cjs +2 -0
  38. package/dist/adapters/pi/mcpConstants.cjs.map +1 -0
  39. package/dist/adapters/pi/mcpConstants.mjs +2 -0
  40. package/dist/adapters/pi/mcpConstants.mjs.map +1 -0
  41. package/dist/adapters/pi/mcpSession.cjs +2 -0
  42. package/dist/adapters/pi/mcpSession.cjs.map +1 -0
  43. package/dist/adapters/pi/mcpSession.mjs +2 -0
  44. package/dist/adapters/pi/mcpSession.mjs.map +1 -0
  45. package/dist/{commands/tools → adapters/pi}/mcpTools.cjs +1 -1
  46. package/dist/adapters/pi/mcpTools.cjs.map +1 -0
  47. package/dist/{commands/tools → adapters/pi}/mcpTools.mjs +1 -1
  48. package/dist/adapters/pi/mcpTools.mjs.map +1 -0
  49. package/dist/adapters/process/directToolsEnvironment.cjs +2 -0
  50. package/dist/adapters/process/directToolsEnvironment.cjs.map +1 -0
  51. package/dist/adapters/process/directToolsEnvironment.mjs +2 -0
  52. package/dist/adapters/process/directToolsEnvironment.mjs.map +1 -0
  53. package/dist/adapters/process/sessionConfig.cjs +2 -0
  54. package/dist/adapters/process/sessionConfig.cjs.map +1 -0
  55. package/dist/adapters/process/sessionConfig.d.cts +16 -0
  56. package/dist/adapters/process/sessionConfig.d.cts.map +1 -0
  57. package/dist/adapters/process/sessionConfig.d.mts +16 -0
  58. package/dist/adapters/process/sessionConfig.d.mts.map +1 -0
  59. package/dist/adapters/process/sessionConfig.mjs +2 -0
  60. package/dist/adapters/process/sessionConfig.mjs.map +1 -0
  61. package/dist/commands/mcpCommand.cjs +2 -2
  62. package/dist/commands/mcpCommand.cjs.map +1 -1
  63. package/dist/commands/mcpCommand.mjs +2 -2
  64. package/dist/commands/mcpCommand.mjs.map +1 -1
  65. package/dist/extensions/doom.cjs +1 -2
  66. package/dist/extensions/doom.d.cts +2 -6
  67. package/dist/extensions/doom.d.mts +2 -7
  68. package/dist/extensions/doom.mjs +1 -2
  69. package/dist/extensions/pi.cjs +1 -2
  70. package/dist/extensions/pi.mjs +1 -2
  71. package/dist/index.cjs +1 -1
  72. package/dist/index.d.cts +7 -4
  73. package/dist/index.d.mts +7 -4
  74. package/dist/index.mjs +1 -1
  75. package/dist/schemas/directTools.cjs +2 -0
  76. package/dist/schemas/directTools.cjs.map +1 -0
  77. package/dist/schemas/directTools.d.cts +8 -0
  78. package/dist/schemas/directTools.d.cts.map +1 -0
  79. package/dist/schemas/directTools.d.mts +8 -0
  80. package/dist/schemas/directTools.d.mts.map +1 -0
  81. package/dist/schemas/directTools.mjs +2 -0
  82. package/dist/schemas/directTools.mjs.map +1 -0
  83. package/dist/schemas/sessionConfig.cjs +2 -0
  84. package/dist/schemas/sessionConfig.cjs.map +1 -0
  85. package/dist/schemas/sessionConfig.d.cts +6 -0
  86. package/dist/schemas/sessionConfig.d.cts.map +1 -0
  87. package/dist/schemas/sessionConfig.d.mts +6 -0
  88. package/dist/schemas/sessionConfig.d.mts.map +1 -0
  89. package/dist/schemas/sessionConfig.mjs +2 -0
  90. package/dist/schemas/sessionConfig.mjs.map +1 -0
  91. package/dist/services/directTools.cjs +1 -1
  92. package/dist/services/directTools.cjs.map +1 -1
  93. package/dist/services/directTools.mjs +1 -1
  94. package/dist/services/directTools.mjs.map +1 -1
  95. package/dist/services/mcpCatalog.cjs.map +1 -1
  96. package/dist/services/mcpCatalog.d.cts +3 -4
  97. package/dist/services/mcpCatalog.d.cts.map +1 -1
  98. package/dist/services/mcpCatalog.d.mts +3 -4
  99. package/dist/services/mcpCatalog.d.mts.map +1 -1
  100. package/dist/services/mcpCatalog.mjs.map +1 -1
  101. package/dist/tui/mcpOverlay.cjs +1 -1
  102. package/dist/tui/mcpOverlay.cjs.map +1 -1
  103. package/dist/tui/mcpOverlay.mjs +1 -1
  104. package/dist/tui/mcpOverlay.mjs.map +1 -1
  105. package/dist/tui/mcpToolRender.cjs.map +1 -0
  106. package/dist/tui/mcpToolRender.mjs.map +1 -0
  107. package/dist/types/mcp.d.cts +1 -0
  108. package/dist/types/mcp.d.mts +1 -0
  109. package/dist/types/mcpCatalog.d.cts +1 -0
  110. package/dist/types/mcpCatalog.d.mts +1 -0
  111. package/dist/{services/configSources.d.cts → types/mcpConfig.d.cts} +4 -14
  112. package/dist/types/mcpConfig.d.cts.map +1 -0
  113. package/dist/{services/configSources.d.mts → types/mcpConfig.d.mts} +4 -14
  114. package/dist/types/mcpConfig.d.mts.map +1 -0
  115. package/dist/types/mcpRuntime.d.cts +13 -0
  116. package/dist/types/mcpRuntime.d.cts.map +1 -0
  117. package/dist/types/mcpRuntime.d.mts +13 -0
  118. package/dist/types/mcpRuntime.d.mts.map +1 -0
  119. package/package.json +6 -5
  120. package/dist/commands/tools/mcpToolRender.cjs.map +0 -1
  121. package/dist/commands/tools/mcpToolRender.mjs.map +0 -1
  122. package/dist/commands/tools/mcpTools.cjs.map +0 -1
  123. package/dist/commands/tools/mcpTools.mjs.map +0 -1
  124. package/dist/extensions/doom.cjs.map +0 -1
  125. package/dist/extensions/doom.d.cts.map +0 -1
  126. package/dist/extensions/doom.d.mts.map +0 -1
  127. package/dist/extensions/doom.mjs.map +0 -1
  128. package/dist/extensions/pi.cjs.map +0 -1
  129. package/dist/extensions/pi.mjs.map +0 -1
  130. package/dist/services/configSources.cjs +0 -2
  131. package/dist/services/configSources.cjs.map +0 -1
  132. package/dist/services/configSources.d.cts.map +0 -1
  133. package/dist/services/configSources.d.mts.map +0 -1
  134. package/dist/services/configSources.mjs.map +0 -1
  135. package/dist/services/directTools.d.cts +0 -14
  136. package/dist/services/directTools.d.cts.map +0 -1
  137. package/dist/services/directTools.d.mts +0 -14
  138. package/dist/services/directTools.d.mts.map +0 -1
  139. package/dist/services/keyringTokenStore.cjs.map +0 -1
  140. package/dist/services/keyringTokenStore.mjs.map +0 -1
  141. package/dist/services/mcpRuntime.cjs.map +0 -1
  142. package/dist/services/mcpRuntime.d.cts.map +0 -1
  143. package/dist/services/mcpRuntime.d.mts.map +0 -1
  144. package/dist/services/mcpRuntime.mjs.map +0 -1
  145. package/dist/services/mcpSession.cjs +0 -2
  146. package/dist/services/mcpSession.cjs.map +0 -1
  147. package/dist/services/mcpSession.mjs +0 -2
  148. package/dist/services/mcpSession.mjs.map +0 -1
  149. package/dist/services/sessionConfig.cjs +0 -2
  150. package/dist/services/sessionConfig.cjs.map +0 -1
  151. package/dist/services/sessionConfig.d.cts +0 -25
  152. package/dist/services/sessionConfig.d.cts.map +0 -1
  153. package/dist/services/sessionConfig.d.mts +0 -25
  154. package/dist/services/sessionConfig.d.mts.map +0 -1
  155. package/dist/services/sessionConfig.mjs +0 -2
  156. package/dist/services/sessionConfig.mjs.map +0 -1
  157. package/dist/types/constants.cjs +0 -2
  158. package/dist/types/constants.cjs.map +0 -1
  159. package/dist/types/constants.mjs +0 -2
  160. package/dist/types/constants.mjs.map +0 -1
  161. package/dist/types/index.d.cts +0 -2
  162. package/dist/types/index.d.mts +0 -2
  163. /package/dist/{services → adapters/node}/configSources.mjs +0 -0
  164. /package/dist/{services → adapters/node}/keyringTokenStore.cjs +0 -0
  165. /package/dist/{services → adapters/node}/keyringTokenStore.mjs +0 -0
  166. /package/dist/{services → adapters/node}/mcpRuntime.cjs +0 -0
  167. /package/dist/{services → adapters/node}/mcpRuntime.mjs +0 -0
  168. /package/dist/{commands/tools → tui}/mcpToolRender.cjs +0 -0
  169. /package/dist/{commands/tools → tui}/mcpToolRender.mjs +0 -0
package/README.md CHANGED
@@ -1,13 +1,18 @@
1
1
  # @agimon-ai/doompi-mcp
2
2
 
3
- MCP server tools for Doompi.
3
+ **A session should not start a server it cannot use.**
4
4
 
5
- The MCP adapter. Resolves the servers a selection allows and exposes them to the session, so
6
- a domain with an `mcp` allowlist reaches only the servers and proxy upstreams it names.
5
+ This is Doompi's MCP gate. It reads repository and plugin MCP config, applies the selected
6
+ domains' allowlist, and removes disallowed servers before any stdio process is spawned. A
7
+ filtered server is absent, not merely hidden from the model.
7
8
 
8
- Part of [Doompi](https://www.npmjs.com/package/@agimon-ai/doompi), a coding agent that loads
9
- only the skills and tools you name. This package is composed by Doompi and is not usually
10
- installed on its own.
9
+ Direct servers come from `.mcp.json`. Proxy upstreams come from `mcp-config.yaml`. Doompi
10
+ can filter both, emit the resolved config for Pi, and rebuild it when domains change.
11
+ Outside Doompi, the adapter still works as a plain Pi extension and uses the repository's
12
+ `.mcp.json` without a domain allowlist.
13
+
14
+ This is part of [Doompi](https://www.npmjs.com/package/@agimon-ai/doompi). Most users get
15
+ it with the distribution.
11
16
 
12
17
  ## Install
13
18
 
@@ -17,18 +22,20 @@ npm install @agimon-ai/doompi-mcp
17
22
 
18
23
  ## How it loads
19
24
 
20
- Doompi loads this unconditionally. It is core, not a layer, so it does not appear in
21
- `.doom/modes.yaml`: making it optional would only create broken configurations.
25
+ Doompi loads the adapter as core; server access is what the domain selection makes
26
+ optional. The package does not belong in `.doom/modes.yaml`.
22
27
 
23
- Extension entry: `@agimon-ai/doompi-mcp/extensions/pi`
28
+ The bare package name follows its Pi manifest and selects the Doom adapter. Use
29
+ `/extensions/pi` for a standalone Pi session or `/extensions/doom` when selecting the Doom
30
+ integration explicitly.
24
31
 
25
32
  ## Entry points
26
33
 
27
- | Import |
28
- | --------------------------------------- |
29
- | `@agimon-ai/doompi-mcp` |
30
- | `@agimon-ai/doompi-mcp/extensions/pi` |
31
- | `@agimon-ai/doompi-mcp/extensions/doom` |
34
+ | Import | Purpose |
35
+ | --------------------------------------- | --------------------- |
36
+ | `@agimon-ai/doompi-mcp` | Library API |
37
+ | `@agimon-ai/doompi-mcp/extensions/pi` | Standalone Pi adapter |
38
+ | `@agimon-ai/doompi-mcp/extensions/doom` | Doompi adapter |
32
39
 
33
40
  ## License
34
41
 
@@ -0,0 +1,2 @@
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:crypto"),n=require("node:fs");n=e.__toESM(n,1);let r=require("node:path");r=e.__toESM(r,1);let i=require("yaml");const a=`mcp-proxy`,o=new Set([a,`agiflow-proxy`]);function s(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function c(e){let t=r.default.extname(e).toLowerCase();return t===`.yaml`||t===`.yml`}function l(e){try{let t=n.default.readFileSync(e,`utf8`),r=c(e)?(0,i.parse)(t):JSON.parse(t);return s(r)?r:void 0}catch{return}}function u(e){return s(e?.mcpServers)?e.mcpServers:{}}function d(e){return!s(e)||e.disabled!==!0}function f(e){return o.has(e)}function p(e){let t=u(l(r.default.join(e,`.mcp.json`))),i=[...o].map(e=>t[e]).find(e=>s(e))?.args,a=Array.isArray(i)?i:[],c=a.indexOf(`--config`),d=a[c+1],f=c>=0&&typeof d==`string`?r.default.resolve(e,d):r.default.join(e,`mcp-config.yaml`);return n.default.existsSync(f)?f:void 0}function m(e,t){return!t||t.length===0||t.includes(e)}function h(e,n,i){let a=(0,t.createHash)(`sha256`).update(e).update(`\0`).update(n).digest(`hex`).slice(0,16),o=c(e)?`.yaml`:`.json`;return r.default.join(i,`mcp-${a}${o}`)}function g(e,t){return c(e)?(0,i.stringify)(t):`${JSON.stringify(t,null,2)}\n`}function _(e,t,r,i){let a=l(e);if(!a)return;let o=u(a),s={},c=[];for(let[e,n]of Object.entries(o))i.stripProxyWrappers&&f(e)||!m(e,t)?c.push(e):s[e]=n;let p=Object.entries(s).filter(([,e])=>d(e)).map(([e])=>e);if(c.length===0)return{path:e,kept:p,dropped:c};n.default.mkdirSync(r,{mode:448,recursive:!0});let _=g(e,{...a,mcpServers:s}),v=h(e,_,r);return n.default.writeFileSync(v,_,{mode:384}),{path:v,kept:p,dropped:c}}function v(e){let t=[r.default.join(e.repoRoot,`.mcp.json`),...(e.pluginConfigPaths??[]).map(t=>r.default.resolve(e.repoRoot,t))].filter(e=>n.default.existsSync(e)),i=[],a=[],o=[];for(let n of t){let t=_(n,e.allowlist?.servers,e.stagingDirectory,{stripProxyWrappers:!0});t&&(i.push(t.path),a.push(...t.kept),o.push(...t.dropped))}let s=p(e.repoRoot),c=s?_(s,e.allowlist?.proxy,e.stagingDirectory,{stripProxyWrappers:!0}):void 0;return c&&o.push(...c.dropped),{shared:{configPaths:c?[c.path]:[],serverNames:c?c.kept:[]},sessionLocal:{configPaths:i,serverNames:[...new Set(a)]},droppedServers:[...new Set(o)]}}exports.PROXY_SERVER_NAME=a,exports.buildMcpConfigGroups=v,exports.resolveSharedConfigPath=p;
2
+ //# sourceMappingURL=configSources.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configSources.cjs","names":["path","fs","parseYaml","createHash","stringifyYaml"],"sources":["../../../src/adapters/node/configSources.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport type { McpConfigGroups, McpConfigGroupsInput } from '../../types/mcpConfig.ts';\n\n/** Wrapper entry used by non-Doom clients to expose `mcp-config.yaml` as one MCP. */\nexport const PROXY_SERVER_NAME = 'mcp-proxy';\n\nconst PROXY_SERVER_ALIASES = new Set([PROXY_SERVER_NAME, 'agiflow-proxy']);\nconst PRIVATE_FILE_MODE = 0o600;\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst DEFAULT_UPSTREAM_CONFIG = 'mcp-config.yaml';\nconst CONFIG_ARGUMENT = '--config';\n\ntype JsonObject = Record<string, unknown>;\n\ninterface FilteredLayer {\n path: string;\n kept: string[];\n dropped: string[];\n}\n\nfunction isObject(value: unknown): value is JsonObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isYamlPath(filePath: string): boolean {\n const extension = path.extname(filePath).toLowerCase();\n return extension === '.yaml' || extension === '.yml';\n}\n\nfunction readConfigObject(filePath: string): JsonObject | undefined {\n try {\n const content = fs.readFileSync(filePath, 'utf8');\n const parsed: unknown = isYamlPath(filePath) ? parseYaml(content) : JSON.parse(content);\n return isObject(parsed) ? parsed : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction serverDefinitions(config: JsonObject | undefined): Record<string, unknown> {\n return isObject(config?.mcpServers) ? config.mcpServers : {};\n}\n\nfunction isEnabledDefinition(definition: unknown): boolean {\n return !isObject(definition) || definition.disabled !== true;\n}\n\nfunction isProxyWrapper(name: string): boolean {\n return PROXY_SERVER_ALIASES.has(name);\n}\n\n/**\n * Resolves the upstream config referenced by the repository proxy wrapper.\n *\n * The wrapper remains useful to Claude, Codex, and other external clients. Doom\n * embeds the same client/runtime code, so it reads this path and connects each\n * declared server itself instead of starting the wrapper as a nested MCP.\n */\nexport function resolveSharedConfigPath(repoRoot: string): string | undefined {\n const servers = serverDefinitions(readConfigObject(path.join(repoRoot, '.mcp.json')));\n const proxy = [...PROXY_SERVER_ALIASES]\n .map((name) => servers[name])\n .find((definition): definition is JsonObject => isObject(definition));\n const rawArgs: unknown = proxy?.args;\n const args = Array.isArray(rawArgs) ? (rawArgs as unknown[]) : [];\n const configArgumentIndex = args.indexOf(CONFIG_ARGUMENT);\n const declared = args[configArgumentIndex + 1];\n const candidate =\n configArgumentIndex >= 0 && typeof declared === 'string'\n ? path.resolve(repoRoot, declared)\n : path.join(repoRoot, DEFAULT_UPSTREAM_CONFIG);\n return fs.existsSync(candidate) ? candidate : undefined;\n}\n\n/** Empty and absent allowlists both mean \"keep everything\". */\nfunction isAllowed(name: string, allowed: string[] | undefined): boolean {\n return !allowed || allowed.length === 0 || allowed.includes(name);\n}\n\nfunction stagedPath(filePath: string, contents: string, stagingDirectory: string): string {\n // The filtered bytes are part of the identity because two live sessions can\n // select different domains. Sharing a path would let the later one rewrite the\n // configuration underneath the first one's deferred startup.\n const digest = createHash('sha256').update(filePath).update('\\0').update(contents).digest('hex').slice(0, 16);\n const extension = isYamlPath(filePath) ? '.yaml' : '.json';\n return path.join(stagingDirectory, `mcp-${digest}${extension}`);\n}\n\nfunction serializeConfig(filePath: string, config: JsonObject): string {\n return isYamlPath(filePath) ? stringifyYaml(config) : `${JSON.stringify(config, null, 2)}\\n`;\n}\n\n/**\n * Rewrites one config layer with disallowed servers removed.\n *\n * Proxy wrapper entries are stripped from `.mcp.json` layers because Doom reaches\n * their upstream config directly. Entries are removed rather than marked disabled,\n * so a filtered stdio server is never spawned at all.\n */\nfunction filterLayer(\n filePath: string,\n allowed: string[] | undefined,\n stagingDirectory: string,\n options: { stripProxyWrappers: boolean },\n): FilteredLayer | undefined {\n const config = readConfigObject(filePath);\n if (!config) return undefined;\n const servers = serverDefinitions(config);\n const kept: Record<string, unknown> = {};\n const dropped: string[] = [];\n\n for (const [name, definition] of Object.entries(servers)) {\n const shouldDrop = (options.stripProxyWrappers && isProxyWrapper(name)) || !isAllowed(name, allowed);\n if (shouldDrop) dropped.push(name);\n else kept[name] = definition;\n }\n\n const keptNames = Object.entries(kept)\n .filter(([, definition]) => isEnabledDefinition(definition))\n .map(([name]) => name);\n if (dropped.length === 0) return { path: filePath, kept: keptNames, dropped };\n\n fs.mkdirSync(stagingDirectory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n const contents = serializeConfig(filePath, { ...config, mcpServers: kept });\n const target = stagedPath(filePath, contents, stagingDirectory);\n fs.writeFileSync(target, contents, { mode: PRIVATE_FILE_MODE });\n return { path: target, kept: keptNames, dropped };\n}\n\n/**\n * Resolves every config layer for this session and applies its domain allowlist.\n *\n * All returned layers go into one embedded `@agimon-ai/mcp-proxy` client container.\n * The package is used as a library only: its wrapper MCP never becomes a Doom MCP\n * server, so status, enable/disable, resources, and OAuth stay attached to each\n * actual downstream server.\n */\nexport function buildMcpConfigGroups(input: McpConfigGroupsInput): McpConfigGroups {\n const layers = [\n path.join(input.repoRoot, '.mcp.json'),\n ...(input.pluginConfigPaths ?? []).map((candidate) => path.resolve(input.repoRoot, candidate)),\n ].filter((candidate) => fs.existsSync(candidate));\n\n const configPaths: string[] = [];\n const serverNames: string[] = [];\n const droppedServers: string[] = [];\n for (const layer of layers) {\n const filtered = filterLayer(layer, input.allowlist?.servers, input.stagingDirectory, {\n stripProxyWrappers: true,\n });\n if (!filtered) continue;\n configPaths.push(filtered.path);\n serverNames.push(...filtered.kept);\n droppedServers.push(...filtered.dropped);\n }\n\n const upstreamPath = resolveSharedConfigPath(input.repoRoot);\n const upstream = upstreamPath\n ? filterLayer(upstreamPath, input.allowlist?.proxy, input.stagingDirectory, { stripProxyWrappers: true })\n : undefined;\n if (upstream) droppedServers.push(...upstream.dropped);\n\n return {\n shared: {\n configPaths: upstream ? [upstream.path] : [],\n serverNames: upstream ? upstream.kept : [],\n },\n // A later layer overriding an earlier one contributes the same name twice.\n sessionLocal: { configPaths, serverNames: [...new Set(serverNames)] },\n droppedServers: [...new Set(droppedServers)],\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { McpConfigGroups, McpConfigGroupsInput } from "../../types/mcpConfig.cjs";
2
+ //#region src/adapters/node/configSources.d.ts
3
+ /** Wrapper entry used by non-Doom clients to expose `mcp-config.yaml` as one MCP. */
4
+ declare const PROXY_SERVER_NAME = "mcp-proxy";
5
+ /**
6
+ * Resolves every config layer for this session and applies its domain allowlist.
7
+ *
8
+ * All returned layers go into one embedded `@agimon-ai/mcp-proxy` client container.
9
+ * The package is used as a library only: its wrapper MCP never becomes a Doom MCP
10
+ * server, so status, enable/disable, resources, and OAuth stay attached to each
11
+ * actual downstream server.
12
+ */
13
+ declare function buildMcpConfigGroups(input: McpConfigGroupsInput): McpConfigGroups;
14
+ //#endregion
15
+ export { PROXY_SERVER_NAME, buildMcpConfigGroups };
16
+ //# sourceMappingURL=configSources.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configSources.d.cts","names":[],"sources":["../../../src/adapters/node/configSources.ts"],"mappings":";;;cAOa;;;;;;;;;iBAqIG,qBAAqB,OAAO,uBAAuB"}
@@ -0,0 +1,16 @@
1
+ import { McpConfigGroups, McpConfigGroupsInput } from "../../types/mcpConfig.mjs";
2
+ //#region src/adapters/node/configSources.d.ts
3
+ /** Wrapper entry used by non-Doom clients to expose `mcp-config.yaml` as one MCP. */
4
+ declare const PROXY_SERVER_NAME = "mcp-proxy";
5
+ /**
6
+ * Resolves every config layer for this session and applies its domain allowlist.
7
+ *
8
+ * All returned layers go into one embedded `@agimon-ai/mcp-proxy` client container.
9
+ * The package is used as a library only: its wrapper MCP never becomes a Doom MCP
10
+ * server, so status, enable/disable, resources, and OAuth stay attached to each
11
+ * actual downstream server.
12
+ */
13
+ declare function buildMcpConfigGroups(input: McpConfigGroupsInput): McpConfigGroups;
14
+ //#endregion
15
+ export { PROXY_SERVER_NAME, buildMcpConfigGroups };
16
+ //# sourceMappingURL=configSources.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configSources.d.mts","names":[],"sources":["../../../src/adapters/node/configSources.ts"],"mappings":";;;cAOa;;;;;;;;;iBAqIG,qBAAqB,OAAO,uBAAuB"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configSources.mjs","names":["parseYaml","stringifyYaml"],"sources":["../../../src/adapters/node/configSources.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport type { McpConfigGroups, McpConfigGroupsInput } from '../../types/mcpConfig.ts';\n\n/** Wrapper entry used by non-Doom clients to expose `mcp-config.yaml` as one MCP. */\nexport const PROXY_SERVER_NAME = 'mcp-proxy';\n\nconst PROXY_SERVER_ALIASES = new Set([PROXY_SERVER_NAME, 'agiflow-proxy']);\nconst PRIVATE_FILE_MODE = 0o600;\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst DEFAULT_UPSTREAM_CONFIG = 'mcp-config.yaml';\nconst CONFIG_ARGUMENT = '--config';\n\ntype JsonObject = Record<string, unknown>;\n\ninterface FilteredLayer {\n path: string;\n kept: string[];\n dropped: string[];\n}\n\nfunction isObject(value: unknown): value is JsonObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isYamlPath(filePath: string): boolean {\n const extension = path.extname(filePath).toLowerCase();\n return extension === '.yaml' || extension === '.yml';\n}\n\nfunction readConfigObject(filePath: string): JsonObject | undefined {\n try {\n const content = fs.readFileSync(filePath, 'utf8');\n const parsed: unknown = isYamlPath(filePath) ? parseYaml(content) : JSON.parse(content);\n return isObject(parsed) ? parsed : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction serverDefinitions(config: JsonObject | undefined): Record<string, unknown> {\n return isObject(config?.mcpServers) ? config.mcpServers : {};\n}\n\nfunction isEnabledDefinition(definition: unknown): boolean {\n return !isObject(definition) || definition.disabled !== true;\n}\n\nfunction isProxyWrapper(name: string): boolean {\n return PROXY_SERVER_ALIASES.has(name);\n}\n\n/**\n * Resolves the upstream config referenced by the repository proxy wrapper.\n *\n * The wrapper remains useful to Claude, Codex, and other external clients. Doom\n * embeds the same client/runtime code, so it reads this path and connects each\n * declared server itself instead of starting the wrapper as a nested MCP.\n */\nexport function resolveSharedConfigPath(repoRoot: string): string | undefined {\n const servers = serverDefinitions(readConfigObject(path.join(repoRoot, '.mcp.json')));\n const proxy = [...PROXY_SERVER_ALIASES]\n .map((name) => servers[name])\n .find((definition): definition is JsonObject => isObject(definition));\n const rawArgs: unknown = proxy?.args;\n const args = Array.isArray(rawArgs) ? (rawArgs as unknown[]) : [];\n const configArgumentIndex = args.indexOf(CONFIG_ARGUMENT);\n const declared = args[configArgumentIndex + 1];\n const candidate =\n configArgumentIndex >= 0 && typeof declared === 'string'\n ? path.resolve(repoRoot, declared)\n : path.join(repoRoot, DEFAULT_UPSTREAM_CONFIG);\n return fs.existsSync(candidate) ? candidate : undefined;\n}\n\n/** Empty and absent allowlists both mean \"keep everything\". */\nfunction isAllowed(name: string, allowed: string[] | undefined): boolean {\n return !allowed || allowed.length === 0 || allowed.includes(name);\n}\n\nfunction stagedPath(filePath: string, contents: string, stagingDirectory: string): string {\n // The filtered bytes are part of the identity because two live sessions can\n // select different domains. Sharing a path would let the later one rewrite the\n // configuration underneath the first one's deferred startup.\n const digest = createHash('sha256').update(filePath).update('\\0').update(contents).digest('hex').slice(0, 16);\n const extension = isYamlPath(filePath) ? '.yaml' : '.json';\n return path.join(stagingDirectory, `mcp-${digest}${extension}`);\n}\n\nfunction serializeConfig(filePath: string, config: JsonObject): string {\n return isYamlPath(filePath) ? stringifyYaml(config) : `${JSON.stringify(config, null, 2)}\\n`;\n}\n\n/**\n * Rewrites one config layer with disallowed servers removed.\n *\n * Proxy wrapper entries are stripped from `.mcp.json` layers because Doom reaches\n * their upstream config directly. Entries are removed rather than marked disabled,\n * so a filtered stdio server is never spawned at all.\n */\nfunction filterLayer(\n filePath: string,\n allowed: string[] | undefined,\n stagingDirectory: string,\n options: { stripProxyWrappers: boolean },\n): FilteredLayer | undefined {\n const config = readConfigObject(filePath);\n if (!config) return undefined;\n const servers = serverDefinitions(config);\n const kept: Record<string, unknown> = {};\n const dropped: string[] = [];\n\n for (const [name, definition] of Object.entries(servers)) {\n const shouldDrop = (options.stripProxyWrappers && isProxyWrapper(name)) || !isAllowed(name, allowed);\n if (shouldDrop) dropped.push(name);\n else kept[name] = definition;\n }\n\n const keptNames = Object.entries(kept)\n .filter(([, definition]) => isEnabledDefinition(definition))\n .map(([name]) => name);\n if (dropped.length === 0) return { path: filePath, kept: keptNames, dropped };\n\n fs.mkdirSync(stagingDirectory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n const contents = serializeConfig(filePath, { ...config, mcpServers: kept });\n const target = stagedPath(filePath, contents, stagingDirectory);\n fs.writeFileSync(target, contents, { mode: PRIVATE_FILE_MODE });\n return { path: target, kept: keptNames, dropped };\n}\n\n/**\n * Resolves every config layer for this session and applies its domain allowlist.\n *\n * All returned layers go into one embedded `@agimon-ai/mcp-proxy` client container.\n * The package is used as a library only: its wrapper MCP never becomes a Doom MCP\n * server, so status, enable/disable, resources, and OAuth stay attached to each\n * actual downstream server.\n */\nexport function buildMcpConfigGroups(input: McpConfigGroupsInput): McpConfigGroups {\n const layers = [\n path.join(input.repoRoot, '.mcp.json'),\n ...(input.pluginConfigPaths ?? []).map((candidate) => path.resolve(input.repoRoot, candidate)),\n ].filter((candidate) => fs.existsSync(candidate));\n\n const configPaths: string[] = [];\n const serverNames: string[] = [];\n const droppedServers: string[] = [];\n for (const layer of layers) {\n const filtered = filterLayer(layer, input.allowlist?.servers, input.stagingDirectory, {\n stripProxyWrappers: true,\n });\n if (!filtered) continue;\n configPaths.push(filtered.path);\n serverNames.push(...filtered.kept);\n droppedServers.push(...filtered.dropped);\n }\n\n const upstreamPath = resolveSharedConfigPath(input.repoRoot);\n const upstream = upstreamPath\n ? filterLayer(upstreamPath, input.allowlist?.proxy, input.stagingDirectory, { stripProxyWrappers: true })\n : undefined;\n if (upstream) droppedServers.push(...upstream.dropped);\n\n return {\n shared: {\n configPaths: upstream ? [upstream.path] : [],\n serverNames: upstream ? upstream.kept : [],\n },\n // A later layer overriding an earlier one contributes the same name twice.\n sessionLocal: { configPaths, serverNames: [...new Set(serverNames)] },\n droppedServers: [...new Set(droppedServers)],\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyringTokenStore.cjs","names":["FileTokenStore"],"sources":["../../../src/adapters/node/keyringTokenStore.ts"],"sourcesContent":["import { type AuthEntry, FileTokenStore, type TokenStore } from '@agimon-ai/mcp-proxy';\n\n/** Keyring service name. One credential per downstream MCP server. */\nexport const KEYRING_SERVICE = 'ai.agimon.doom-mcp.oauth';\n\n/** The slice of `@napi-rs/keyring`'s `AsyncEntry` this store uses. */\nexport interface KeyringEntry {\n getPassword(): Promise<string | undefined>;\n setPassword(password: string): Promise<void>;\n deletePassword(): Promise<unknown>;\n}\n\nexport type KeyringEntryFactory = (service: string, account: string) => KeyringEntry;\n\n/**\n * OAuth credentials in the OS keyring, with an owner-only file store behind it.\n *\n * The keyring is a native module and the platform service it talks to may be absent\n * (a headless Linux box with no secret service, an unsupported architecture). A\n * missing credential is reported as `undefined` rather than raised, so a rejection\n * means the keyring itself is unusable and the store degrades to the file fallback\n * for the rest of the session instead of failing the run.\n */\nexport class KeyringTokenStore implements TokenStore {\n private degraded = false;\n\n constructor(\n private readonly createEntry: KeyringEntryFactory,\n private readonly fallback: TokenStore = new FileTokenStore(),\n ) {}\n\n /** True once the keyring has failed and the file store has taken over. */\n get isDegraded(): boolean {\n return this.degraded;\n }\n\n async read(serverName: string): Promise<AuthEntry | undefined> {\n if (this.degraded) return this.fallback.read(serverName);\n let raw: string | undefined;\n try {\n raw = await this.createEntry(KEYRING_SERVICE, serverName).getPassword();\n } catch {\n this.degraded = true;\n return this.fallback.read(serverName);\n }\n if (!raw) return undefined;\n try {\n return JSON.parse(raw) as AuthEntry;\n } catch {\n // A credential we cannot parse is treated as absent so the next authorization\n // overwrites it, rather than wedging the server on every start.\n return undefined;\n }\n }\n\n async write(serverName: string, entry: AuthEntry): Promise<void> {\n if (!this.degraded) {\n try {\n await this.createEntry(KEYRING_SERVICE, serverName).setPassword(JSON.stringify(entry));\n return;\n } catch {\n this.degraded = true;\n }\n }\n await this.fallback.write(serverName, entry);\n }\n\n /**\n * Removes the credential from both stores.\n *\n * A session that degraded part way through can have left a copy in either place,\n * and \"signed out\" has to mean no credential survives anywhere. A keyring that\n * refuses the delete is reported rather than absorbed: the caller was told the\n * credential is gone, and it is not. The fallback is still cleared first so the\n * failure leaves one surviving copy instead of two.\n */\n async clear(serverName: string): Promise<void> {\n let keyringFailure: unknown;\n if (!this.degraded) {\n try {\n await this.createEntry(KEYRING_SERVICE, serverName).deletePassword();\n } catch (error) {\n keyringFailure = error;\n }\n }\n await this.fallback.clear(serverName);\n if (keyringFailure !== undefined) {\n const detail = keyringFailure instanceof Error ? keyringFailure.message : JSON.stringify(keyringFailure);\n throw new Error(`Keyring credential for \"${serverName}\" could not be removed: ${detail}`, {\n cause: keyringFailure,\n });\n }\n }\n}\n\n/**\n * Builds the token store for this host, falling back when the native module is absent.\n *\n * doom-mcp loads inside Pi, so a platform without a prebuilt keyring binary must\n * still get a working session rather than a failed extension install.\n */\nexport async function createTokenStore(fallback: TokenStore = new FileTokenStore()): Promise<TokenStore> {\n try {\n const { AsyncEntry } = await import('@napi-rs/keyring');\n return new KeyringTokenStore((service, account) => new AsyncEntry(service, account), fallback);\n } catch {\n return fallback;\n }\n}\n"],"mappings":"sCAGA,MAAa,EAAkB,2BAoB/B,IAAa,EAAb,KAAqD,CAIhC,YACA,SAJnB,SAAmB,GAEnB,YACE,EACA,EAAwC,IAAIA,EAAAA,eAC5C,CAFiB,KAAA,YAAA,EACA,KAAA,SAAA,CAChB,CAGH,IAAI,YAAsB,CACxB,OAAO,KAAK,QACd,CAEA,MAAM,KAAK,EAAoD,CAC7D,GAAI,KAAK,SAAU,OAAO,KAAK,SAAS,KAAK,CAAU,EACvD,IAAI,EACJ,GAAI,CACF,EAAM,MAAM,KAAK,YAAY,EAAiB,CAAU,CAAC,CAAC,YAAY,CACxE,MAAQ,CAEN,MADA,MAAK,SAAW,GACT,KAAK,SAAS,KAAK,CAAU,CACtC,CACK,KACL,GAAI,CACF,OAAO,KAAK,MAAM,CAAG,CACvB,MAAQ,CAGN,MACF,CACF,CAEA,MAAM,MAAM,EAAoB,EAAiC,CAC/D,GAAI,CAAC,KAAK,SACR,GAAI,CACF,MAAM,KAAK,YAAY,EAAiB,CAAU,CAAC,CAAC,YAAY,KAAK,UAAU,CAAK,CAAC,EACrF,MACF,MAAQ,CACN,KAAK,SAAW,EAClB,CAEF,MAAM,KAAK,SAAS,MAAM,EAAY,CAAK,CAC7C,CAWA,MAAM,MAAM,EAAmC,CAC7C,IAAI,EACJ,GAAI,CAAC,KAAK,SACR,GAAI,CACF,MAAM,KAAK,YAAY,EAAiB,CAAU,CAAC,CAAC,eAAe,CACrE,OAAS,EAAO,CACd,EAAiB,CACnB,CAGF,GADA,MAAM,KAAK,SAAS,MAAM,CAAU,EAChC,IAAmB,IAAA,GAAW,CAChC,IAAM,EAAS,aAA0B,MAAQ,EAAe,QAAU,KAAK,UAAU,CAAc,EACvG,MAAU,MAAM,2BAA2B,EAAW,0BAA0B,IAAU,CACxF,MAAO,CACT,CAAC,CACH,CACF,CACF,EAQA,eAAsB,EAAiB,EAAuB,IAAIA,EAAAA,eAAuC,CACvG,GAAI,CACF,GAAM,CAAE,cAAe,MAAM,OAAO,oBACpC,OAAO,IAAI,GAAmB,EAAS,IAAY,IAAI,EAAW,EAAS,CAAO,EAAG,CAAQ,CAC/F,MAAQ,CACN,OAAO,CACT,CACF"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyringTokenStore.mjs","names":[],"sources":["../../../src/adapters/node/keyringTokenStore.ts"],"sourcesContent":["import { type AuthEntry, FileTokenStore, type TokenStore } from '@agimon-ai/mcp-proxy';\n\n/** Keyring service name. One credential per downstream MCP server. */\nexport const KEYRING_SERVICE = 'ai.agimon.doom-mcp.oauth';\n\n/** The slice of `@napi-rs/keyring`'s `AsyncEntry` this store uses. */\nexport interface KeyringEntry {\n getPassword(): Promise<string | undefined>;\n setPassword(password: string): Promise<void>;\n deletePassword(): Promise<unknown>;\n}\n\nexport type KeyringEntryFactory = (service: string, account: string) => KeyringEntry;\n\n/**\n * OAuth credentials in the OS keyring, with an owner-only file store behind it.\n *\n * The keyring is a native module and the platform service it talks to may be absent\n * (a headless Linux box with no secret service, an unsupported architecture). A\n * missing credential is reported as `undefined` rather than raised, so a rejection\n * means the keyring itself is unusable and the store degrades to the file fallback\n * for the rest of the session instead of failing the run.\n */\nexport class KeyringTokenStore implements TokenStore {\n private degraded = false;\n\n constructor(\n private readonly createEntry: KeyringEntryFactory,\n private readonly fallback: TokenStore = new FileTokenStore(),\n ) {}\n\n /** True once the keyring has failed and the file store has taken over. */\n get isDegraded(): boolean {\n return this.degraded;\n }\n\n async read(serverName: string): Promise<AuthEntry | undefined> {\n if (this.degraded) return this.fallback.read(serverName);\n let raw: string | undefined;\n try {\n raw = await this.createEntry(KEYRING_SERVICE, serverName).getPassword();\n } catch {\n this.degraded = true;\n return this.fallback.read(serverName);\n }\n if (!raw) return undefined;\n try {\n return JSON.parse(raw) as AuthEntry;\n } catch {\n // A credential we cannot parse is treated as absent so the next authorization\n // overwrites it, rather than wedging the server on every start.\n return undefined;\n }\n }\n\n async write(serverName: string, entry: AuthEntry): Promise<void> {\n if (!this.degraded) {\n try {\n await this.createEntry(KEYRING_SERVICE, serverName).setPassword(JSON.stringify(entry));\n return;\n } catch {\n this.degraded = true;\n }\n }\n await this.fallback.write(serverName, entry);\n }\n\n /**\n * Removes the credential from both stores.\n *\n * A session that degraded part way through can have left a copy in either place,\n * and \"signed out\" has to mean no credential survives anywhere. A keyring that\n * refuses the delete is reported rather than absorbed: the caller was told the\n * credential is gone, and it is not. The fallback is still cleared first so the\n * failure leaves one surviving copy instead of two.\n */\n async clear(serverName: string): Promise<void> {\n let keyringFailure: unknown;\n if (!this.degraded) {\n try {\n await this.createEntry(KEYRING_SERVICE, serverName).deletePassword();\n } catch (error) {\n keyringFailure = error;\n }\n }\n await this.fallback.clear(serverName);\n if (keyringFailure !== undefined) {\n const detail = keyringFailure instanceof Error ? keyringFailure.message : JSON.stringify(keyringFailure);\n throw new Error(`Keyring credential for \"${serverName}\" could not be removed: ${detail}`, {\n cause: keyringFailure,\n });\n }\n }\n}\n\n/**\n * Builds the token store for this host, falling back when the native module is absent.\n *\n * doom-mcp loads inside Pi, so a platform without a prebuilt keyring binary must\n * still get a working session rather than a failed extension install.\n */\nexport async function createTokenStore(fallback: TokenStore = new FileTokenStore()): Promise<TokenStore> {\n try {\n const { AsyncEntry } = await import('@napi-rs/keyring');\n return new KeyringTokenStore((service, account) => new AsyncEntry(service, account), fallback);\n } catch {\n return fallback;\n }\n}\n"],"mappings":"sDAGA,MAAa,EAAkB,2BAoB/B,IAAa,EAAb,KAAqD,CAIhC,YACA,SAJnB,SAAmB,GAEnB,YACE,EACA,EAAwC,IAAI,EAC5C,CAFiB,KAAA,YAAA,EACA,KAAA,SAAA,CAChB,CAGH,IAAI,YAAsB,CACxB,OAAO,KAAK,QACd,CAEA,MAAM,KAAK,EAAoD,CAC7D,GAAI,KAAK,SAAU,OAAO,KAAK,SAAS,KAAK,CAAU,EACvD,IAAI,EACJ,GAAI,CACF,EAAM,MAAM,KAAK,YAAY,EAAiB,CAAU,CAAC,CAAC,YAAY,CACxE,MAAQ,CAEN,MADA,MAAK,SAAW,GACT,KAAK,SAAS,KAAK,CAAU,CACtC,CACK,KACL,GAAI,CACF,OAAO,KAAK,MAAM,CAAG,CACvB,MAAQ,CAGN,MACF,CACF,CAEA,MAAM,MAAM,EAAoB,EAAiC,CAC/D,GAAI,CAAC,KAAK,SACR,GAAI,CACF,MAAM,KAAK,YAAY,EAAiB,CAAU,CAAC,CAAC,YAAY,KAAK,UAAU,CAAK,CAAC,EACrF,MACF,MAAQ,CACN,KAAK,SAAW,EAClB,CAEF,MAAM,KAAK,SAAS,MAAM,EAAY,CAAK,CAC7C,CAWA,MAAM,MAAM,EAAmC,CAC7C,IAAI,EACJ,GAAI,CAAC,KAAK,SACR,GAAI,CACF,MAAM,KAAK,YAAY,EAAiB,CAAU,CAAC,CAAC,eAAe,CACrE,OAAS,EAAO,CACd,EAAiB,CACnB,CAGF,GADA,MAAM,KAAK,SAAS,MAAM,CAAU,EAChC,IAAmB,IAAA,GAAW,CAChC,IAAM,EAAS,aAA0B,MAAQ,EAAe,QAAU,KAAK,UAAU,CAAc,EACvG,MAAU,MAAM,2BAA2B,EAAW,0BAA0B,IAAU,CACxF,MAAO,CACT,CAAC,CACH,CACF,CACF,EAQA,eAAsB,EAAiB,EAAuB,IAAI,EAAuC,CACvG,GAAI,CACF,GAAM,CAAE,cAAe,MAAM,OAAO,oBACpC,OAAO,IAAI,GAAmB,EAAS,IAAY,IAAI,EAAW,EAAS,CAAO,EAAG,CAAQ,CAC/F,MAAQ,CACN,OAAO,CACT,CACF"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcpRuntime.cjs","names":["resolve","createHash","join","homedir","readFileSync"],"sources":["../../../src/adapters/node/mcpRuntime.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport type { McpServerStateChange, McpToolInfo, SharedServices, TokenStore } from '@agimon-ai/mcp-proxy';\nimport type { CachedCatalog } from '../../types/mcpRuntime.ts';\n\nexport interface McpRuntimeOptions {\n /** Ordered config layers. Later layers override earlier ones. */\n configPaths: string[];\n tokenStore?: TokenStore;\n onAuthorizationUrl?: (url: URL, serverName: string) => void;\n onServerStateChange?: (change: McpServerStateChange) => void;\n}\n\nexport interface McpRuntimeHandle {\n generation: number;\n services: SharedServices;\n /** Resolves once the first connection pass settles. Never rejects. */\n connectionsSettled: Promise<void>;\n}\n\ninterface DefinitionsCacheFile {\n servers?: Record<string, { serverName: string; tools?: McpToolInfo[] }>;\n}\n\nfunction sanitizeConfigPathForFilename(configFilePath: string): string {\n const normalized = resolve(configFilePath).replace(/^([A-Z]):/, (_match, drive: string) => `${drive.toLowerCase()}:`);\n return normalized.replace(/[^a-zA-Z0-9._-]+/g, '_').replace(/^_+|_+$/g, '');\n}\n\n/** Lightweight equivalent of mcp-proxy's cache path calculation. */\nexport function definitionsCachePath(configPaths: string[]): string {\n if (configPaths.length === 0) throw new Error('definitionsCachePath requires at least one config path');\n const sanitized = sanitizeConfigPathForFilename(configPaths[0]!);\n const suffix =\n configPaths.length === 1\n ? ''\n : `.${createHash('sha256')\n .update(configPaths.map((configPath) => resolve(configPath)).join('\\0'))\n .digest('hex')\n .slice(0, 12)}`;\n return join(homedir(), '.mcp-proxy', `${sanitized}${suffix}.definitions-cache.json`);\n}\n\n/**\n * Reads the catalog the previous run left behind.\n *\n * Synchronous on purpose: Pi has to be able to register these tools during\n * extension install, before it takes its first turn, and awaiting a file read\n * would let that turn start with an empty tool set. A miss is normal on a first\n * run and resolves to an empty catalog rather than an error.\n *\n * The catalog is not checked against the current configuration: a server that has\n * since been removed simply never reaches `connected` and is dropped from the\n * active tool set when its state lands.\n */\nexport function readCachedCatalog(\n configPaths: string[],\n cachePathFor: (paths: string[]) => string = definitionsCachePath,\n): CachedCatalog {\n if (configPaths.length === 0) return { servers: [] };\n try {\n const cachePath = cachePathFor(configPaths);\n const cache = JSON.parse(readFileSync(cachePath, 'utf8')) as Partial<DefinitionsCacheFile>;\n const servers = Object.values(cache.servers ?? {}).map((server) => ({\n name: server.serverName,\n tools: server.tools ?? [],\n }));\n return { servers };\n } catch {\n return { servers: [] };\n }\n}\n\n/**\n * Owns the one live mcp-proxy container for this extension.\n *\n * Pi reloads extensions in process, so a connect that lands after a session restart\n * or a `/domains` switch would otherwise fold tools into a dead context. Every start\n * takes the next generation; anything an older generation produces is dropped and its\n * container disposed.\n */\nexport class McpRuntimeOwner {\n private generation = 0;\n private active: SharedServices | undefined;\n private unsubscribe: (() => void) | undefined;\n\n get currentGeneration(): number {\n return this.generation;\n }\n\n /** The live container, or undefined before the first start and after dispose. */\n getServices(): SharedServices | undefined {\n return this.active;\n }\n\n /**\n * Whether a container a caller captured earlier is still the live one.\n *\n * An async continuation that started under one container has to check this\n * before writing its result, or a `/domains` switch would fold stale tools into\n * the new session.\n */\n isCurrent(services: SharedServices | undefined): boolean {\n return services !== undefined && services === this.active;\n }\n\n /**\n * Starts a container for the current configuration, replacing any predecessor.\n *\n * Returns undefined when a newer generation started while this one was building,\n * which is the signal for the caller to abandon its half-finished work.\n */\n async start(options: McpRuntimeOptions): Promise<McpRuntimeHandle | undefined> {\n const generation = ++this.generation;\n // Retired before the replacement is built so two containers never hold the same\n // stdio children open at once.\n await this.retire();\n if (options.configPaths.length === 0) return undefined;\n\n // The proxy container pulls in transports, OAuth, schema conversion, and\n // server implementations. Cached tool registration needs none of those, so\n // load the runtime only after session_start has yielded control to Pi.\n const { createProxyContainer } = await import('@agimon-ai/mcp-proxy');\n const services = await createProxyContainer({\n configFilePath: options.configPaths,\n // Never blocking: an unreachable server must not delay Pi's first prompt.\n startupMode: 'background',\n auth: { tokenStore: options.tokenStore, onAuthorizationUrl: options.onAuthorizationUrl },\n });\n\n if (generation !== this.generation) {\n await services.dispose().catch(() => undefined);\n return undefined;\n }\n\n this.active = services;\n const forward = options.onServerStateChange;\n if (forward) {\n this.unsubscribe = services.clientManager.onServerStateChange((change) => {\n if (generation === this.generation) forward(change);\n });\n }\n\n return { generation, services, connectionsSettled: services.connectionsSettled };\n }\n\n /** Tears the container down and invalidates every generation still in flight. */\n async dispose(): Promise<void> {\n ++this.generation;\n await this.retire();\n }\n\n private async retire(): Promise<void> {\n this.unsubscribe?.();\n this.unsubscribe = undefined;\n const services = this.active;\n this.active = undefined;\n // A failed teardown must not leave the owner believing a dead container is live.\n if (services) await services.dispose().catch(() => undefined);\n }\n}\n"],"mappings":"8FA0BA,SAAS,EAA8B,EAAgC,CAErE,OAAA,EADmBA,EAAAA,QAAAA,CAAQ,CAAc,CAAC,CAAC,QAAQ,aAAc,EAAQ,IAAkB,GAAG,EAAM,YAAY,EAAE,EAClG,CAAC,CAAC,QAAQ,oBAAqB,GAAG,CAAC,CAAC,QAAQ,WAAY,EAAE,CAC5E,CAGA,SAAgB,EAAqB,EAA+B,CAClE,GAAI,EAAY,SAAW,EAAG,MAAU,MAAM,wDAAwD,EACtG,IAAM,EAAY,EAA8B,EAAY,EAAG,EACzD,EACJ,EAAY,SAAW,EACnB,GACA,KAAA,EAAIC,EAAAA,WAAAA,CAAW,QAAQ,CAAC,CACrB,OAAO,EAAY,IAAK,IAAA,EAAeD,EAAAA,QAAAA,CAAQ,CAAU,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CACvE,OAAO,KAAK,CAAC,CACb,MAAM,EAAG,EAAE,IACpB,OAAA,EAAOE,EAAAA,KAAAA,EAAAA,EAAKC,EAAAA,QAAAA,CAAQ,EAAG,aAAc,GAAG,IAAY,EAAO,wBAAwB,CACrF,CAcA,SAAgB,EACd,EACA,EAA4C,EAC7B,CACf,GAAI,EAAY,SAAW,EAAG,MAAO,CAAE,QAAS,CAAC,CAAE,EACnD,GAAI,CACF,IAAM,EAAY,EAAa,CAAW,EACpC,EAAQ,KAAK,OAAA,EAAMC,EAAAA,aAAAA,CAAa,EAAW,MAAM,CAAC,EAKxD,MAAO,CAAE,QAJO,OAAO,OAAO,EAAM,SAAW,CAAC,CAAC,CAAC,CAAC,IAAK,IAAY,CAClE,KAAM,EAAO,WACb,MAAO,EAAO,OAAS,CAAC,CAC1B,EACe,CAAE,CACnB,MAAQ,CACN,MAAO,CAAE,QAAS,CAAC,CAAE,CACvB,CACF,CAUA,IAAa,EAAb,KAA6B,CAC3B,WAAqB,EACrB,OACA,YAEA,IAAI,mBAA4B,CAC9B,OAAO,KAAK,UACd,CAGA,aAA0C,CACxC,OAAO,KAAK,MACd,CASA,UAAU,EAA+C,CACvD,OAAO,IAAa,IAAA,IAAa,IAAa,KAAK,MACrD,CAQA,MAAM,MAAM,EAAmE,CAC7E,IAAM,EAAa,EAAE,KAAK,WAI1B,GADA,MAAM,KAAK,OAAO,EACd,EAAQ,YAAY,SAAW,EAAG,OAKtC,GAAM,CAAE,wBAAyB,MAAM,OAAO,wBACxC,EAAW,MAAM,EAAqB,CAC1C,eAAgB,EAAQ,YAExB,YAAa,aACb,KAAM,CAAE,WAAY,EAAQ,WAAY,mBAAoB,EAAQ,kBAAmB,CACzF,CAAC,EAED,GAAI,IAAe,KAAK,WAAY,CAClC,MAAM,EAAS,QAAQ,CAAC,CAAC,UAAY,IAAA,EAAS,EAC9C,MACF,CAEA,KAAK,OAAS,EACd,IAAM,EAAU,EAAQ,oBAOxB,OANI,IACF,KAAK,YAAc,EAAS,cAAc,oBAAqB,GAAW,CACpE,IAAe,KAAK,YAAY,EAAQ,CAAM,CACpD,CAAC,GAGI,CAAE,aAAY,WAAU,mBAAoB,EAAS,kBAAmB,CACjF,CAGA,MAAM,SAAyB,CAC7B,EAAE,KAAK,WACP,MAAM,KAAK,OAAO,CACpB,CAEA,MAAc,QAAwB,CACpC,KAAK,cAAc,EACnB,KAAK,YAAc,IAAA,GACnB,IAAM,EAAW,KAAK,OACtB,KAAK,OAAS,IAAA,GAEV,GAAU,MAAM,EAAS,QAAQ,CAAC,CAAC,UAAY,IAAA,EAAS,CAC9D,CACF"}
@@ -1,13 +1,6 @@
1
- import { McpToolInfo } from "@agimon-ai/mcp-proxy";
2
- //#region src/services/mcpRuntime.d.ts
3
- interface CatalogServer {
4
- name: string;
5
- tools: McpToolInfo[];
6
- }
7
- /** Server definitions from the previous run, available before any socket opens. */
8
- interface CachedCatalog {
9
- servers: CatalogServer[];
10
- }
1
+ import { CachedCatalog } from "../../types/mcpRuntime.cjs";
2
+ import "@agimon-ai/mcp-proxy";
3
+ //#region src/adapters/node/mcpRuntime.d.ts
11
4
  /**
12
5
  * Reads the catalog the previous run left behind.
13
6
  *
@@ -22,5 +15,5 @@ interface CachedCatalog {
22
15
  */
23
16
  declare function readCachedCatalog(configPaths: string[], cachePathFor?: (paths: string[]) => string): CachedCatalog;
24
17
  //#endregion
25
- export { CachedCatalog, CatalogServer, readCachedCatalog };
18
+ export { readCachedCatalog };
26
19
  //# sourceMappingURL=mcpRuntime.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcpRuntime.d.cts","names":[],"sources":["../../../src/adapters/node/mcpRuntime.ts"],"mappings":";;;;;;;;;;;;;;;iBAyDgB,kBACd,uBACA,gBAAe,6BACd"}
@@ -1,13 +1,6 @@
1
- import { McpToolInfo } from "@agimon-ai/mcp-proxy";
2
- //#region src/services/mcpRuntime.d.ts
3
- interface CatalogServer {
4
- name: string;
5
- tools: McpToolInfo[];
6
- }
7
- /** Server definitions from the previous run, available before any socket opens. */
8
- interface CachedCatalog {
9
- servers: CatalogServer[];
10
- }
1
+ import { CachedCatalog } from "../../types/mcpRuntime.mjs";
2
+ import "@agimon-ai/mcp-proxy";
3
+ //#region src/adapters/node/mcpRuntime.d.ts
11
4
  /**
12
5
  * Reads the catalog the previous run left behind.
13
6
  *
@@ -22,5 +15,5 @@ interface CachedCatalog {
22
15
  */
23
16
  declare function readCachedCatalog(configPaths: string[], cachePathFor?: (paths: string[]) => string): CachedCatalog;
24
17
  //#endregion
25
- export { CachedCatalog, CatalogServer, readCachedCatalog };
18
+ export { readCachedCatalog };
26
19
  //# sourceMappingURL=mcpRuntime.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcpRuntime.d.mts","names":[],"sources":["../../../src/adapters/node/mcpRuntime.ts"],"mappings":";;;;;;;;;;;;;;;iBAyDgB,kBACd,uBACA,gBAAe,6BACd"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcpRuntime.mjs","names":[],"sources":["../../../src/adapters/node/mcpRuntime.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport type { McpServerStateChange, McpToolInfo, SharedServices, TokenStore } from '@agimon-ai/mcp-proxy';\nimport type { CachedCatalog } from '../../types/mcpRuntime.ts';\n\nexport interface McpRuntimeOptions {\n /** Ordered config layers. Later layers override earlier ones. */\n configPaths: string[];\n tokenStore?: TokenStore;\n onAuthorizationUrl?: (url: URL, serverName: string) => void;\n onServerStateChange?: (change: McpServerStateChange) => void;\n}\n\nexport interface McpRuntimeHandle {\n generation: number;\n services: SharedServices;\n /** Resolves once the first connection pass settles. Never rejects. */\n connectionsSettled: Promise<void>;\n}\n\ninterface DefinitionsCacheFile {\n servers?: Record<string, { serverName: string; tools?: McpToolInfo[] }>;\n}\n\nfunction sanitizeConfigPathForFilename(configFilePath: string): string {\n const normalized = resolve(configFilePath).replace(/^([A-Z]):/, (_match, drive: string) => `${drive.toLowerCase()}:`);\n return normalized.replace(/[^a-zA-Z0-9._-]+/g, '_').replace(/^_+|_+$/g, '');\n}\n\n/** Lightweight equivalent of mcp-proxy's cache path calculation. */\nexport function definitionsCachePath(configPaths: string[]): string {\n if (configPaths.length === 0) throw new Error('definitionsCachePath requires at least one config path');\n const sanitized = sanitizeConfigPathForFilename(configPaths[0]!);\n const suffix =\n configPaths.length === 1\n ? ''\n : `.${createHash('sha256')\n .update(configPaths.map((configPath) => resolve(configPath)).join('\\0'))\n .digest('hex')\n .slice(0, 12)}`;\n return join(homedir(), '.mcp-proxy', `${sanitized}${suffix}.definitions-cache.json`);\n}\n\n/**\n * Reads the catalog the previous run left behind.\n *\n * Synchronous on purpose: Pi has to be able to register these tools during\n * extension install, before it takes its first turn, and awaiting a file read\n * would let that turn start with an empty tool set. A miss is normal on a first\n * run and resolves to an empty catalog rather than an error.\n *\n * The catalog is not checked against the current configuration: a server that has\n * since been removed simply never reaches `connected` and is dropped from the\n * active tool set when its state lands.\n */\nexport function readCachedCatalog(\n configPaths: string[],\n cachePathFor: (paths: string[]) => string = definitionsCachePath,\n): CachedCatalog {\n if (configPaths.length === 0) return { servers: [] };\n try {\n const cachePath = cachePathFor(configPaths);\n const cache = JSON.parse(readFileSync(cachePath, 'utf8')) as Partial<DefinitionsCacheFile>;\n const servers = Object.values(cache.servers ?? {}).map((server) => ({\n name: server.serverName,\n tools: server.tools ?? [],\n }));\n return { servers };\n } catch {\n return { servers: [] };\n }\n}\n\n/**\n * Owns the one live mcp-proxy container for this extension.\n *\n * Pi reloads extensions in process, so a connect that lands after a session restart\n * or a `/domains` switch would otherwise fold tools into a dead context. Every start\n * takes the next generation; anything an older generation produces is dropped and its\n * container disposed.\n */\nexport class McpRuntimeOwner {\n private generation = 0;\n private active: SharedServices | undefined;\n private unsubscribe: (() => void) | undefined;\n\n get currentGeneration(): number {\n return this.generation;\n }\n\n /** The live container, or undefined before the first start and after dispose. */\n getServices(): SharedServices | undefined {\n return this.active;\n }\n\n /**\n * Whether a container a caller captured earlier is still the live one.\n *\n * An async continuation that started under one container has to check this\n * before writing its result, or a `/domains` switch would fold stale tools into\n * the new session.\n */\n isCurrent(services: SharedServices | undefined): boolean {\n return services !== undefined && services === this.active;\n }\n\n /**\n * Starts a container for the current configuration, replacing any predecessor.\n *\n * Returns undefined when a newer generation started while this one was building,\n * which is the signal for the caller to abandon its half-finished work.\n */\n async start(options: McpRuntimeOptions): Promise<McpRuntimeHandle | undefined> {\n const generation = ++this.generation;\n // Retired before the replacement is built so two containers never hold the same\n // stdio children open at once.\n await this.retire();\n if (options.configPaths.length === 0) return undefined;\n\n // The proxy container pulls in transports, OAuth, schema conversion, and\n // server implementations. Cached tool registration needs none of those, so\n // load the runtime only after session_start has yielded control to Pi.\n const { createProxyContainer } = await import('@agimon-ai/mcp-proxy');\n const services = await createProxyContainer({\n configFilePath: options.configPaths,\n // Never blocking: an unreachable server must not delay Pi's first prompt.\n startupMode: 'background',\n auth: { tokenStore: options.tokenStore, onAuthorizationUrl: options.onAuthorizationUrl },\n });\n\n if (generation !== this.generation) {\n await services.dispose().catch(() => undefined);\n return undefined;\n }\n\n this.active = services;\n const forward = options.onServerStateChange;\n if (forward) {\n this.unsubscribe = services.clientManager.onServerStateChange((change) => {\n if (generation === this.generation) forward(change);\n });\n }\n\n return { generation, services, connectionsSettled: services.connectionsSettled };\n }\n\n /** Tears the container down and invalidates every generation still in flight. */\n async dispose(): Promise<void> {\n ++this.generation;\n await this.retire();\n }\n\n private async retire(): Promise<void> {\n this.unsubscribe?.();\n this.unsubscribe = undefined;\n const services = this.active;\n this.active = undefined;\n // A failed teardown must not leave the owner believing a dead container is live.\n if (services) await services.dispose().catch(() => undefined);\n }\n}\n"],"mappings":"gKA0BA,SAAS,EAA8B,EAAgC,CAErE,OADmB,EAAQ,CAAc,CAAC,CAAC,QAAQ,aAAc,EAAQ,IAAkB,GAAG,EAAM,YAAY,EAAE,EAClG,CAAC,CAAC,QAAQ,oBAAqB,GAAG,CAAC,CAAC,QAAQ,WAAY,EAAE,CAC5E,CAGA,SAAgB,EAAqB,EAA+B,CAClE,GAAI,EAAY,SAAW,EAAG,MAAU,MAAM,wDAAwD,EACtG,IAAM,EAAY,EAA8B,EAAY,EAAG,EACzD,EACJ,EAAY,SAAW,EACnB,GACA,IAAI,EAAW,QAAQ,CAAC,CACrB,OAAO,EAAY,IAAK,GAAe,EAAQ,CAAU,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CACvE,OAAO,KAAK,CAAC,CACb,MAAM,EAAG,EAAE,IACpB,OAAO,EAAK,EAAQ,EAAG,aAAc,GAAG,IAAY,EAAO,wBAAwB,CACrF,CAcA,SAAgB,EACd,EACA,EAA4C,EAC7B,CACf,GAAI,EAAY,SAAW,EAAG,MAAO,CAAE,QAAS,CAAC,CAAE,EACnD,GAAI,CACF,IAAM,EAAY,EAAa,CAAW,EACpC,EAAQ,KAAK,MAAM,EAAa,EAAW,MAAM,CAAC,EAKxD,MAAO,CAAE,QAJO,OAAO,OAAO,EAAM,SAAW,CAAC,CAAC,CAAC,CAAC,IAAK,IAAY,CAClE,KAAM,EAAO,WACb,MAAO,EAAO,OAAS,CAAC,CAC1B,EACe,CAAE,CACnB,MAAQ,CACN,MAAO,CAAE,QAAS,CAAC,CAAE,CACvB,CACF,CAUA,IAAa,EAAb,KAA6B,CAC3B,WAAqB,EACrB,OACA,YAEA,IAAI,mBAA4B,CAC9B,OAAO,KAAK,UACd,CAGA,aAA0C,CACxC,OAAO,KAAK,MACd,CASA,UAAU,EAA+C,CACvD,OAAO,IAAa,IAAA,IAAa,IAAa,KAAK,MACrD,CAQA,MAAM,MAAM,EAAmE,CAC7E,IAAM,EAAa,EAAE,KAAK,WAI1B,GADA,MAAM,KAAK,OAAO,EACd,EAAQ,YAAY,SAAW,EAAG,OAKtC,GAAM,CAAE,wBAAyB,MAAM,OAAO,wBACxC,EAAW,MAAM,EAAqB,CAC1C,eAAgB,EAAQ,YAExB,YAAa,aACb,KAAM,CAAE,WAAY,EAAQ,WAAY,mBAAoB,EAAQ,kBAAmB,CACzF,CAAC,EAED,GAAI,IAAe,KAAK,WAAY,CAClC,MAAM,EAAS,QAAQ,CAAC,CAAC,UAAY,IAAA,EAAS,EAC9C,MACF,CAEA,KAAK,OAAS,EACd,IAAM,EAAU,EAAQ,oBAOxB,OANI,IACF,KAAK,YAAc,EAAS,cAAc,oBAAqB,GAAW,CACpE,IAAe,KAAK,YAAY,EAAQ,CAAM,CACpD,CAAC,GAGI,CAAE,aAAY,WAAU,mBAAoB,EAAS,kBAAmB,CACjF,CAGA,MAAM,SAAyB,CAC7B,EAAE,KAAK,WACP,MAAM,KAAK,OAAO,CACpB,CAEA,MAAc,QAAwB,CACpC,KAAK,cAAc,EACnB,KAAK,YAAc,IAAA,GACnB,IAAM,EAAW,KAAK,OACtB,KAAK,OAAS,IAAA,GAEV,GAAU,MAAM,EAAS,QAAQ,CAAC,CAAC,UAAY,IAAA,EAAS,CAC9D,CACF"}
@@ -0,0 +1,2 @@
1
+ const e=require("./extension.cjs");function t(t){e.registerMcpExtension(t,{configurationSource:`doom`})}exports.registerDoomMcpExtension=t;
2
+ //# sourceMappingURL=doomExtension.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doomExtension.cjs","names":[],"sources":["../../../src/adapters/pi/doomExtension.ts"],"sourcesContent":["import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { registerMcpExtension } from './extension.ts';\n\n/** Explicit Doom adapter retaining environment-provided overlays and allowlists. */\nexport function registerDoomMcpExtension(pi: ExtensionAPI): void {\n registerMcpExtension(pi, { configurationSource: 'doom' });\n}\n"],"mappings":"mCAIA,SAAgB,EAAyB,EAAwB,CAC/D,EAAA,qBAAqB,EAAI,CAAE,oBAAqB,MAAO,CAAC,CAC1D"}
@@ -0,0 +1,7 @@
1
+ import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ //#region src/adapters/pi/doomExtension.d.ts
3
+ /** Explicit Doom adapter retaining environment-provided overlays and allowlists. */
4
+ declare function registerDoomMcpExtension(pi: ExtensionAPI): void;
5
+ //#endregion
6
+ export { registerDoomMcpExtension };
7
+ //# sourceMappingURL=doomExtension.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doomExtension.d.cts","names":[],"sources":["../../../src/adapters/pi/doomExtension.ts"],"mappings":";;;iBAIgB,yBAAyB,IAAI"}
@@ -0,0 +1,7 @@
1
+ import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ //#region src/adapters/pi/doomExtension.d.ts
3
+ /** Explicit Doom adapter retaining environment-provided overlays and allowlists. */
4
+ declare function registerDoomMcpExtension(pi: ExtensionAPI): void;
5
+ //#endregion
6
+ export { registerDoomMcpExtension };
7
+ //# sourceMappingURL=doomExtension.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doomExtension.d.mts","names":[],"sources":["../../../src/adapters/pi/doomExtension.ts"],"mappings":";;;iBAIgB,yBAAyB,IAAI"}
@@ -0,0 +1,2 @@
1
+ import{registerMcpExtension as e}from"./extension.mjs";function t(t){e(t,{configurationSource:`doom`})}export{t as registerDoomMcpExtension};
2
+ //# sourceMappingURL=doomExtension.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doomExtension.mjs","names":[],"sources":["../../../src/adapters/pi/doomExtension.ts"],"sourcesContent":["import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { registerMcpExtension } from './extension.ts';\n\n/** Explicit Doom adapter retaining environment-provided overlays and allowlists. */\nexport function registerDoomMcpExtension(pi: ExtensionAPI): void {\n registerMcpExtension(pi, { configurationSource: 'doom' });\n}\n"],"mappings":"uDAIA,SAAgB,EAAyB,EAAwB,CAC/D,EAAqB,EAAI,CAAE,oBAAqB,MAAO,CAAC,CAC1D"}
@@ -1,2 +1,2 @@
1
- const e=require("../../types/constants.cjs"),t=require("../../commands/mcpCommand.cjs"),n=require("./leader.cjs"),r=require("../../services/sessionConfig.cjs"),i=require("../../services/mcpSession.cjs");let a=require("@agimon-ai/doompi-extension-contracts/mcp-status"),o=require("@agimon-ai/doompi-extension-contracts/protocol");function s(s,c={}){let l,u,d=new i.McpSession({pi:s,onAuthorizationUrl:(e,t)=>{l?.hasUI||l?.ui?.notify(`Authorize ${t} by opening:\n${e.toString()}`,`info`)}}),f=r.readSessionConfig();d.install(c.configurationSource===`doom`?f:{repoRoot:f.repoRoot,stagingDirectory:f.stagingDirectory}),t.registerCommand(s,d,{openOverlay:async e=>{let{openMcpOverlay:t}=await Promise.resolve().then(()=>require("../../tui/mcpOverlay.cjs"));return t(e,d)}}),n.registerLeaderContribution(s),s.on(`session_start`,(t,n)=>{l=n,c.configurationSource!==`doom`&&d.install(r.readSessionConfig({},n.cwd));let i=(0,o.createProtocolRuntime)({bus:s.events,source:e.PACKAGE_SOURCE,sessionId:n.sessionManager.getSessionId()});u?.(),u=i.provide(a.McpStatusQueryProtocol,()=>d.getSnapshot()),d.activate();for(let e of d.getDiagnostics())n.ui?.notify(e,`warning`);d.start()}),s.on(`session_shutdown`,()=>{u?.(),u=void 0,l=void 0,d.dispose()})}exports.registerMcpExtension=s;
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../commands/mcpCommand.cjs"),n=require("../process/sessionConfig.cjs"),r=require("./mcpConstants.cjs"),i=require("./leader.cjs"),a=require("./mcpSession.cjs");let o=require("@agimon-ai/doompi-extension-contracts/mcp-status"),s=require("@agimon-ai/doompi-extension-contracts/protocol"),c=require("open");c=e.__toESM(c,1);const l=`warning`,u=`doom`;function d(e,d={}){let f,p,m=new a.McpSession({pi:e,onAuthorizationUrl:async(e,t,{openBrowser:n})=>{if(f?.mode===`tui`){if(!n)return;try{await(0,c.default)(e.toString())}catch(e){throw f.ui.notify(`Could not open the authorization page for ${t}: ${e instanceof Error?e.message:String(e)}. Press a in the MCP overlay to retry.`,l),e}return}f?.ui?.notify(`Authorize ${t} by opening:\n${e.toString()}`,`info`)}}),h=n.readSessionConfig();m.install(d.configurationSource===u?h:{repoRoot:h.repoRoot,stagingDirectory:h.stagingDirectory}),t.registerCommand(e,m,{openOverlay:async e=>{let{openMcpOverlay:t}=await Promise.resolve().then(()=>require("../../tui/mcpOverlay.cjs"));return t(e,m)}}),i.registerLeaderContribution(e),e.on(`session_start`,(t,i)=>{f=i,d.configurationSource!==u&&m.install(n.readSessionConfig({},i.cwd));let a=(0,s.createProtocolRuntime)({bus:e.events,source:r.PACKAGE_SOURCE,sessionId:i.sessionManager.getSessionId()});p?.(),p=a.provide(o.McpStatusQueryProtocol,()=>m.getSnapshot()),m.activate();for(let e of m.getDiagnostics())i.ui?.notify(e,l);m.start()}),e.on(`session_shutdown`,()=>{p?.(),p=void 0,f=void 0,m.dispose()})}exports.registerMcpExtension=d;
2
2
  //# sourceMappingURL=extension.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.cjs","names":["McpSession","readSessionConfig","createProtocolRuntime","PACKAGE_SOURCE","McpStatusQueryProtocol"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { McpStatusQueryProtocol } from '@agimon-ai/doompi-extension-contracts/mcp-status';\nimport { createProtocolRuntime } from '@agimon-ai/doompi-extension-contracts/protocol';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerCommand } from '../../commands/mcpCommand.ts';\nimport { registerLeaderContribution } from './leader.ts';\nimport { McpSession } from '../../services/mcpSession.ts';\nimport { readSessionConfig } from '../../services/sessionConfig.ts';\nimport { PACKAGE_SOURCE } from '../../types/constants.ts';\n\nconst SESSION_START = 'session_start';\nconst SESSION_SHUTDOWN = 'session_shutdown';\nconst INFO = 'info';\nconst WARNING = 'warning';\n\nexport interface McpExtensionOptions {\n /** Doom loading retains the environment-provided plugin overlays and allowlists. */\n configurationSource?: 'standard' | 'doom';\n}\n\n/**\n * MCP support for a Pi session.\n *\n * Tools are registered here, synchronously, from the catalog the previous run left\n * behind. Connecting starts on `session_start` and runs detached, so the first\n * prompt is never held up by a slow or unreachable server; each server folds into\n * the active tool set as it lands.\n */\nexport function registerMcpExtension(pi: ExtensionAPI, options: McpExtensionOptions = {}): void {\n let activeContext: ExtensionContext | undefined;\n let disposeStatusProvider: (() => void) | undefined;\n\n const session = new McpSession({\n pi,\n onAuthorizationUrl: (url, serverName) => {\n // The overlay carries the URL for anyone who can see one, and it is where the\n // flow was started from. Announcing it here as well would only put it in the\n // transcript underneath a fullscreen panel. A session with no UI has no\n // overlay to read, so that is the one case still worth notifying.\n if (activeContext?.hasUI) return;\n activeContext?.ui?.notify(`Authorize ${serverName} by opening:\\n${url.toString()}`, INFO);\n },\n });\n\n const initialConfiguration = readSessionConfig();\n session.install(\n options.configurationSource === 'doom'\n ? initialConfiguration\n : {\n repoRoot: initialConfiguration.repoRoot,\n stagingDirectory: initialConfiguration.stagingDirectory,\n },\n );\n // The opener is injected rather than imported by the command: `src/commands/**`\n // may not reach into `src/tui/**`, and handing it in is also what lets the\n // command be tested without a terminal.\n registerCommand(pi, session, {\n openOverlay: async (ctx) => {\n const { openMcpOverlay } = await import('../../tui/mcpOverlay.ts');\n return openMcpOverlay(ctx, session);\n },\n });\n registerLeaderContribution(pi);\n\n pi.on(SESSION_START, (_event, ctx) => {\n activeContext = ctx;\n if (options.configurationSource !== 'doom') session.install(readSessionConfig({}, ctx.cwd));\n const protocol = createProtocolRuntime({\n bus: pi.events,\n source: PACKAGE_SOURCE,\n sessionId: ctx.sessionManager.getSessionId(),\n });\n disposeStatusProvider?.();\n disposeStatusProvider = protocol.provide(McpStatusQueryProtocol, () => session.getSnapshot());\n\n // First point Pi accepts an active-list call: the tools registered while the\n // extension loaded are held back until a server actually answers.\n session.activate();\n for (const diagnostic of session.getDiagnostics()) ctx.ui?.notify(diagnostic, WARNING);\n // Detached on purpose: awaiting here would put every downstream connection on\n // the path between launching Pi and its first prompt.\n void session.start();\n });\n\n pi.on(SESSION_SHUTDOWN, () => {\n disposeStatusProvider?.();\n disposeStatusProvider = undefined;\n activeContext = undefined;\n void session.dispose();\n });\n}\n"],"mappings":"yUA2BA,SAAgB,EAAqB,EAAkB,EAA+B,CAAC,EAAS,CAC9F,IAAI,EACA,EAEE,EAAU,IAAIA,EAAAA,WAAW,CAC7B,KACA,oBAAqB,EAAK,IAAe,CAKnC,GAAe,OACnB,GAAe,IAAI,OAAO,aAAa,EAAW,gBAAgB,EAAI,SAAS,IAAK,MAAI,CAC1F,CACF,CAAC,EAEK,EAAuBC,EAAAA,kBAAkB,EAC/C,EAAQ,QACN,EAAQ,sBAAwB,OAC5B,EACA,CACE,SAAU,EAAqB,SAC/B,iBAAkB,EAAqB,gBACzC,CACN,EAIA,EAAA,gBAAgB,EAAI,EAAS,CAC3B,YAAa,KAAO,IAAQ,CAC1B,GAAM,CAAE,kBAAmB,MAAA,QAAA,QAAA,CAAA,CAAA,SAAA,QAAM,0BAAA,CAAA,EACjC,OAAO,EAAe,EAAK,CAAO,CACpC,CACF,CAAC,EACD,EAAA,2BAA2B,CAAE,EAE7B,EAAG,GAAG,iBAAgB,EAAQ,IAAQ,CACpC,EAAgB,EACZ,EAAQ,sBAAwB,QAAQ,EAAQ,QAAQA,EAAAA,kBAAkB,CAAC,EAAG,EAAI,GAAG,CAAC,EAC1F,IAAM,GAAA,EAAWC,EAAAA,sBAAAA,CAAsB,CACrC,IAAK,EAAG,OACR,OAAQC,EAAAA,eACR,UAAW,EAAI,eAAe,aAAa,CAC7C,CAAC,EACD,IAAwB,EACxB,EAAwB,EAAS,QAAQC,EAAAA,2BAA8B,EAAQ,YAAY,CAAC,EAI5F,EAAQ,SAAS,EACjB,IAAK,IAAM,KAAc,EAAQ,eAAe,EAAG,EAAI,IAAI,OAAO,EAAY,SAAO,EAGrF,EAAa,MAAM,CACrB,CAAC,EAED,EAAG,GAAG,uBAAwB,CAC5B,IAAwB,EACxB,EAAwB,IAAA,GACxB,EAAgB,IAAA,GAChB,EAAa,QAAQ,CACvB,CAAC,CACH"}
1
+ {"version":3,"file":"extension.cjs","names":["McpSession","readSessionConfig","createProtocolRuntime","PACKAGE_SOURCE","McpStatusQueryProtocol"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { McpStatusQueryProtocol } from '@agimon-ai/doompi-extension-contracts/mcp-status';\nimport { createProtocolRuntime } from '@agimon-ai/doompi-extension-contracts/protocol';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport open from 'open';\nimport { registerCommand } from '../../commands/mcpCommand.ts';\nimport { readSessionConfig } from '../process/sessionConfig.ts';\nimport { registerLeaderContribution } from './leader.ts';\nimport { PACKAGE_SOURCE } from './mcpConstants.ts';\nimport { McpSession } from './mcpSession.ts';\n\nconst SESSION_START = 'session_start';\nconst SESSION_SHUTDOWN = 'session_shutdown';\nconst INFO = 'info';\nconst WARNING = 'warning';\nconst DOOM_CONFIGURATION_SOURCE = 'doom';\n\nexport interface McpExtensionOptions {\n /** Doom loading retains the environment-provided plugin overlays and allowlists. */\n configurationSource?: 'standard' | typeof DOOM_CONFIGURATION_SOURCE;\n}\n\n/**\n * MCP support for a Pi session.\n *\n * Tools are registered here, synchronously, from the catalog the previous run left\n * behind. Connecting starts on `session_start` and runs detached, so the first\n * prompt is never held up by a slow or unreachable server; each server folds into\n * the active tool set as it lands.\n */\nexport function registerMcpExtension(pi: ExtensionAPI, options: McpExtensionOptions = {}): void {\n let activeContext: ExtensionContext | undefined;\n let disposeStatusProvider: (() => void) | undefined;\n\n const session = new McpSession({\n pi,\n onAuthorizationUrl: async (url, serverName, { openBrowser }) => {\n // Explicit auth from a terminal owns a desktop browser. Background startup\n // still only records the URL, so opening Pi never creates surprise tabs.\n if (activeContext?.mode === 'tui') {\n if (!openBrowser) return;\n try {\n await open(url.toString());\n } catch (error) {\n activeContext.ui.notify(\n `Could not open the authorization page for ${serverName}: ${error instanceof Error ? error.message : String(error)}. Press a in the MCP overlay to retry.`,\n WARNING,\n );\n throw error;\n }\n return;\n }\n // Headless and RPC sessions cannot launch a local desktop browser, so their\n // usable fallback remains the complete URL in the host notification stream.\n activeContext?.ui?.notify(`Authorize ${serverName} by opening:\\n${url.toString()}`, INFO);\n },\n });\n\n const initialConfiguration = readSessionConfig();\n session.install(\n options.configurationSource === DOOM_CONFIGURATION_SOURCE\n ? initialConfiguration\n : {\n repoRoot: initialConfiguration.repoRoot,\n stagingDirectory: initialConfiguration.stagingDirectory,\n },\n );\n // The opener is injected rather than imported by the command: `src/commands/**`\n // may not reach into `src/tui/**`, and handing it in is also what lets the\n // command be tested without a terminal.\n registerCommand(pi, session, {\n openOverlay: async (ctx) => {\n const { openMcpOverlay } = await import('../../tui/mcpOverlay.ts');\n return openMcpOverlay(ctx, session);\n },\n });\n registerLeaderContribution(pi);\n\n pi.on(SESSION_START, (_event, ctx) => {\n activeContext = ctx;\n if (options.configurationSource !== DOOM_CONFIGURATION_SOURCE) session.install(readSessionConfig({}, ctx.cwd));\n const protocol = createProtocolRuntime({\n bus: pi.events,\n source: PACKAGE_SOURCE,\n sessionId: ctx.sessionManager.getSessionId(),\n });\n disposeStatusProvider?.();\n disposeStatusProvider = protocol.provide(McpStatusQueryProtocol, () => session.getSnapshot());\n\n // First point Pi accepts an active-list call: the tools registered while the\n // extension loaded are held back until a server actually answers.\n session.activate();\n for (const diagnostic of session.getDiagnostics()) ctx.ui?.notify(diagnostic, WARNING);\n // Detached on purpose: awaiting here would put every downstream connection on\n // the path between launching Pi and its first prompt.\n void session.start();\n });\n\n pi.on(SESSION_SHUTDOWN, () => {\n disposeStatusProvider?.();\n disposeStatusProvider = undefined;\n activeContext = undefined;\n void session.dispose();\n });\n}\n"],"mappings":"sYAUA,MAGM,EAAU,UACV,EAA4B,OAelC,SAAgB,EAAqB,EAAkB,EAA+B,CAAC,EAAS,CAC9F,IAAI,EACA,EAEE,EAAU,IAAIA,EAAAA,WAAW,CAC7B,KACA,mBAAoB,MAAO,EAAK,EAAY,CAAE,iBAAkB,CAG9D,GAAI,GAAe,OAAS,MAAO,CACjC,GAAI,CAAC,EAAa,OAClB,GAAI,CACF,MAAA,EAAM,EAAA,QAAA,CAAK,EAAI,SAAS,CAAC,CAC3B,OAAS,EAAO,CAKd,MAJA,EAAc,GAAG,OACf,6CAA6C,EAAW,IAAI,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EAAE,wCACnH,CACF,EACM,CACR,CACA,MACF,CAGA,GAAe,IAAI,OAAO,aAAa,EAAW,gBAAgB,EAAI,SAAS,IAAK,MAAI,CAC1F,CACF,CAAC,EAEK,EAAuBC,EAAAA,kBAAkB,EAC/C,EAAQ,QACN,EAAQ,sBAAwB,EAC5B,EACA,CACE,SAAU,EAAqB,SAC/B,iBAAkB,EAAqB,gBACzC,CACN,EAIA,EAAA,gBAAgB,EAAI,EAAS,CAC3B,YAAa,KAAO,IAAQ,CAC1B,GAAM,CAAE,kBAAmB,MAAA,QAAA,QAAA,CAAA,CAAA,SAAA,QAAM,0BAAA,CAAA,EACjC,OAAO,EAAe,EAAK,CAAO,CACpC,CACF,CAAC,EACD,EAAA,2BAA2B,CAAE,EAE7B,EAAG,GAAG,iBAAgB,EAAQ,IAAQ,CACpC,EAAgB,EACZ,EAAQ,sBAAwB,GAA2B,EAAQ,QAAQA,EAAAA,kBAAkB,CAAC,EAAG,EAAI,GAAG,CAAC,EAC7G,IAAM,GAAA,EAAWC,EAAAA,sBAAAA,CAAsB,CACrC,IAAK,EAAG,OACR,OAAQC,EAAAA,eACR,UAAW,EAAI,eAAe,aAAa,CAC7C,CAAC,EACD,IAAwB,EACxB,EAAwB,EAAS,QAAQC,EAAAA,2BAA8B,EAAQ,YAAY,CAAC,EAI5F,EAAQ,SAAS,EACjB,IAAK,IAAM,KAAc,EAAQ,eAAe,EAAG,EAAI,IAAI,OAAO,EAAY,CAAO,EAGrF,EAAa,MAAM,CACrB,CAAC,EAED,EAAG,GAAG,uBAAwB,CAC5B,IAAwB,EACxB,EAAwB,IAAA,GACxB,EAAgB,IAAA,GAChB,EAAa,QAAQ,CACvB,CAAC,CACH"}
@@ -1,8 +1,9 @@
1
1
  import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  //#region src/adapters/pi/extension.d.ts
3
+ declare const DOOM_CONFIGURATION_SOURCE = "doom";
3
4
  interface McpExtensionOptions {
4
5
  /** Doom loading retains the environment-provided plugin overlays and allowlists. */
5
- configurationSource?: 'standard' | 'doom';
6
+ configurationSource?: 'standard' | typeof DOOM_CONFIGURATION_SOURCE;
6
7
  }
7
8
  /**
8
9
  * MCP support for a Pi session.
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;UAciB;;EAEf;;;;;;;;;;iBAWc,qBAAqB,IAAI,cAAc,UAAS"}
1
+ {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;cAcM;UAEW;;EAEf,0CAA0C;;;;;;;;;;iBAW5B,qBAAqB,IAAI,cAAc,UAAS"}
@@ -1,8 +1,9 @@
1
1
  import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  //#region src/adapters/pi/extension.d.ts
3
+ declare const DOOM_CONFIGURATION_SOURCE = "doom";
3
4
  interface McpExtensionOptions {
4
5
  /** Doom loading retains the environment-provided plugin overlays and allowlists. */
5
- configurationSource?: 'standard' | 'doom';
6
+ configurationSource?: 'standard' | typeof DOOM_CONFIGURATION_SOURCE;
6
7
  }
7
8
  /**
8
9
  * MCP support for a Pi session.
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;UAciB;;EAEf;;;;;;;;;;iBAWc,qBAAqB,IAAI,cAAc,UAAS"}
1
+ {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;cAcM;UAEW;;EAEf,0CAA0C;;;;;;;;;;iBAW5B,qBAAqB,IAAI,cAAc,UAAS"}
@@ -1,2 +1,2 @@
1
- import{PACKAGE_SOURCE as e}from"../../types/constants.mjs";import{registerCommand as t}from"../../commands/mcpCommand.mjs";import{registerLeaderContribution as n}from"./leader.mjs";import{readSessionConfig as r}from"../../services/sessionConfig.mjs";import{McpSession as i}from"../../services/mcpSession.mjs";import{McpStatusQueryProtocol as a}from"@agimon-ai/doompi-extension-contracts/mcp-status";import{createProtocolRuntime as o}from"@agimon-ai/doompi-extension-contracts/protocol";function s(s,c={}){let l,u,d=new i({pi:s,onAuthorizationUrl:(e,t)=>{l?.hasUI||l?.ui?.notify(`Authorize ${t} by opening:\n${e.toString()}`,`info`)}}),f=r();d.install(c.configurationSource===`doom`?f:{repoRoot:f.repoRoot,stagingDirectory:f.stagingDirectory}),t(s,d,{openOverlay:async e=>{let{openMcpOverlay:t}=await import(`../../tui/mcpOverlay.mjs`);return t(e,d)}}),n(s),s.on(`session_start`,(t,n)=>{l=n,c.configurationSource!==`doom`&&d.install(r({},n.cwd));let i=o({bus:s.events,source:e,sessionId:n.sessionManager.getSessionId()});u?.(),u=i.provide(a,()=>d.getSnapshot()),d.activate();for(let e of d.getDiagnostics())n.ui?.notify(e,`warning`);d.start()}),s.on(`session_shutdown`,()=>{u?.(),u=void 0,l=void 0,d.dispose()})}export{s as registerMcpExtension};
1
+ import{registerCommand as e}from"../../commands/mcpCommand.mjs";import{readSessionConfig as t}from"../process/sessionConfig.mjs";import{PACKAGE_SOURCE as n}from"./mcpConstants.mjs";import{registerLeaderContribution as r}from"./leader.mjs";import{McpSession as i}from"./mcpSession.mjs";import{McpStatusQueryProtocol as a}from"@agimon-ai/doompi-extension-contracts/mcp-status";import{createProtocolRuntime as o}from"@agimon-ai/doompi-extension-contracts/protocol";import s from"open";const c=`warning`,l=`doom`;function u(u,d={}){let f,p,m=new i({pi:u,onAuthorizationUrl:async(e,t,{openBrowser:n})=>{if(f?.mode===`tui`){if(!n)return;try{await s(e.toString())}catch(e){throw f.ui.notify(`Could not open the authorization page for ${t}: ${e instanceof Error?e.message:String(e)}. Press a in the MCP overlay to retry.`,c),e}return}f?.ui?.notify(`Authorize ${t} by opening:\n${e.toString()}`,`info`)}}),h=t();m.install(d.configurationSource===l?h:{repoRoot:h.repoRoot,stagingDirectory:h.stagingDirectory}),e(u,m,{openOverlay:async e=>{let{openMcpOverlay:t}=await import(`../../tui/mcpOverlay.mjs`);return t(e,m)}}),r(u),u.on(`session_start`,(e,r)=>{f=r,d.configurationSource!==l&&m.install(t({},r.cwd));let i=o({bus:u.events,source:n,sessionId:r.sessionManager.getSessionId()});p?.(),p=i.provide(a,()=>m.getSnapshot()),m.activate();for(let e of m.getDiagnostics())r.ui?.notify(e,c);m.start()}),u.on(`session_shutdown`,()=>{p?.(),p=void 0,f=void 0,m.dispose()})}export{u as registerMcpExtension};
2
2
  //# sourceMappingURL=extension.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.mjs","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { McpStatusQueryProtocol } from '@agimon-ai/doompi-extension-contracts/mcp-status';\nimport { createProtocolRuntime } from '@agimon-ai/doompi-extension-contracts/protocol';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerCommand } from '../../commands/mcpCommand.ts';\nimport { registerLeaderContribution } from './leader.ts';\nimport { McpSession } from '../../services/mcpSession.ts';\nimport { readSessionConfig } from '../../services/sessionConfig.ts';\nimport { PACKAGE_SOURCE } from '../../types/constants.ts';\n\nconst SESSION_START = 'session_start';\nconst SESSION_SHUTDOWN = 'session_shutdown';\nconst INFO = 'info';\nconst WARNING = 'warning';\n\nexport interface McpExtensionOptions {\n /** Doom loading retains the environment-provided plugin overlays and allowlists. */\n configurationSource?: 'standard' | 'doom';\n}\n\n/**\n * MCP support for a Pi session.\n *\n * Tools are registered here, synchronously, from the catalog the previous run left\n * behind. Connecting starts on `session_start` and runs detached, so the first\n * prompt is never held up by a slow or unreachable server; each server folds into\n * the active tool set as it lands.\n */\nexport function registerMcpExtension(pi: ExtensionAPI, options: McpExtensionOptions = {}): void {\n let activeContext: ExtensionContext | undefined;\n let disposeStatusProvider: (() => void) | undefined;\n\n const session = new McpSession({\n pi,\n onAuthorizationUrl: (url, serverName) => {\n // The overlay carries the URL for anyone who can see one, and it is where the\n // flow was started from. Announcing it here as well would only put it in the\n // transcript underneath a fullscreen panel. A session with no UI has no\n // overlay to read, so that is the one case still worth notifying.\n if (activeContext?.hasUI) return;\n activeContext?.ui?.notify(`Authorize ${serverName} by opening:\\n${url.toString()}`, INFO);\n },\n });\n\n const initialConfiguration = readSessionConfig();\n session.install(\n options.configurationSource === 'doom'\n ? initialConfiguration\n : {\n repoRoot: initialConfiguration.repoRoot,\n stagingDirectory: initialConfiguration.stagingDirectory,\n },\n );\n // The opener is injected rather than imported by the command: `src/commands/**`\n // may not reach into `src/tui/**`, and handing it in is also what lets the\n // command be tested without a terminal.\n registerCommand(pi, session, {\n openOverlay: async (ctx) => {\n const { openMcpOverlay } = await import('../../tui/mcpOverlay.ts');\n return openMcpOverlay(ctx, session);\n },\n });\n registerLeaderContribution(pi);\n\n pi.on(SESSION_START, (_event, ctx) => {\n activeContext = ctx;\n if (options.configurationSource !== 'doom') session.install(readSessionConfig({}, ctx.cwd));\n const protocol = createProtocolRuntime({\n bus: pi.events,\n source: PACKAGE_SOURCE,\n sessionId: ctx.sessionManager.getSessionId(),\n });\n disposeStatusProvider?.();\n disposeStatusProvider = protocol.provide(McpStatusQueryProtocol, () => session.getSnapshot());\n\n // First point Pi accepts an active-list call: the tools registered while the\n // extension loaded are held back until a server actually answers.\n session.activate();\n for (const diagnostic of session.getDiagnostics()) ctx.ui?.notify(diagnostic, WARNING);\n // Detached on purpose: awaiting here would put every downstream connection on\n // the path between launching Pi and its first prompt.\n void session.start();\n });\n\n pi.on(SESSION_SHUTDOWN, () => {\n disposeStatusProvider?.();\n disposeStatusProvider = undefined;\n activeContext = undefined;\n void session.dispose();\n });\n}\n"],"mappings":"seA2BA,SAAgB,EAAqB,EAAkB,EAA+B,CAAC,EAAS,CAC9F,IAAI,EACA,EAEE,EAAU,IAAI,EAAW,CAC7B,KACA,oBAAqB,EAAK,IAAe,CAKnC,GAAe,OACnB,GAAe,IAAI,OAAO,aAAa,EAAW,gBAAgB,EAAI,SAAS,IAAK,MAAI,CAC1F,CACF,CAAC,EAEK,EAAuB,EAAkB,EAC/C,EAAQ,QACN,EAAQ,sBAAwB,OAC5B,EACA,CACE,SAAU,EAAqB,SAC/B,iBAAkB,EAAqB,gBACzC,CACN,EAIA,EAAgB,EAAI,EAAS,CAC3B,YAAa,KAAO,IAAQ,CAC1B,GAAM,CAAE,kBAAmB,MAAM,OAAO,4BACxC,OAAO,EAAe,EAAK,CAAO,CACpC,CACF,CAAC,EACD,EAA2B,CAAE,EAE7B,EAAG,GAAG,iBAAgB,EAAQ,IAAQ,CACpC,EAAgB,EACZ,EAAQ,sBAAwB,QAAQ,EAAQ,QAAQ,EAAkB,CAAC,EAAG,EAAI,GAAG,CAAC,EAC1F,IAAM,EAAW,EAAsB,CACrC,IAAK,EAAG,OACR,OAAQ,EACR,UAAW,EAAI,eAAe,aAAa,CAC7C,CAAC,EACD,IAAwB,EACxB,EAAwB,EAAS,QAAQ,MAA8B,EAAQ,YAAY,CAAC,EAI5F,EAAQ,SAAS,EACjB,IAAK,IAAM,KAAc,EAAQ,eAAe,EAAG,EAAI,IAAI,OAAO,EAAY,SAAO,EAGrF,EAAa,MAAM,CACrB,CAAC,EAED,EAAG,GAAG,uBAAwB,CAC5B,IAAwB,EACxB,EAAwB,IAAA,GACxB,EAAgB,IAAA,GAChB,EAAa,QAAQ,CACvB,CAAC,CACH"}
1
+ {"version":3,"file":"extension.mjs","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { McpStatusQueryProtocol } from '@agimon-ai/doompi-extension-contracts/mcp-status';\nimport { createProtocolRuntime } from '@agimon-ai/doompi-extension-contracts/protocol';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport open from 'open';\nimport { registerCommand } from '../../commands/mcpCommand.ts';\nimport { readSessionConfig } from '../process/sessionConfig.ts';\nimport { registerLeaderContribution } from './leader.ts';\nimport { PACKAGE_SOURCE } from './mcpConstants.ts';\nimport { McpSession } from './mcpSession.ts';\n\nconst SESSION_START = 'session_start';\nconst SESSION_SHUTDOWN = 'session_shutdown';\nconst INFO = 'info';\nconst WARNING = 'warning';\nconst DOOM_CONFIGURATION_SOURCE = 'doom';\n\nexport interface McpExtensionOptions {\n /** Doom loading retains the environment-provided plugin overlays and allowlists. */\n configurationSource?: 'standard' | typeof DOOM_CONFIGURATION_SOURCE;\n}\n\n/**\n * MCP support for a Pi session.\n *\n * Tools are registered here, synchronously, from the catalog the previous run left\n * behind. Connecting starts on `session_start` and runs detached, so the first\n * prompt is never held up by a slow or unreachable server; each server folds into\n * the active tool set as it lands.\n */\nexport function registerMcpExtension(pi: ExtensionAPI, options: McpExtensionOptions = {}): void {\n let activeContext: ExtensionContext | undefined;\n let disposeStatusProvider: (() => void) | undefined;\n\n const session = new McpSession({\n pi,\n onAuthorizationUrl: async (url, serverName, { openBrowser }) => {\n // Explicit auth from a terminal owns a desktop browser. Background startup\n // still only records the URL, so opening Pi never creates surprise tabs.\n if (activeContext?.mode === 'tui') {\n if (!openBrowser) return;\n try {\n await open(url.toString());\n } catch (error) {\n activeContext.ui.notify(\n `Could not open the authorization page for ${serverName}: ${error instanceof Error ? error.message : String(error)}. Press a in the MCP overlay to retry.`,\n WARNING,\n );\n throw error;\n }\n return;\n }\n // Headless and RPC sessions cannot launch a local desktop browser, so their\n // usable fallback remains the complete URL in the host notification stream.\n activeContext?.ui?.notify(`Authorize ${serverName} by opening:\\n${url.toString()}`, INFO);\n },\n });\n\n const initialConfiguration = readSessionConfig();\n session.install(\n options.configurationSource === DOOM_CONFIGURATION_SOURCE\n ? initialConfiguration\n : {\n repoRoot: initialConfiguration.repoRoot,\n stagingDirectory: initialConfiguration.stagingDirectory,\n },\n );\n // The opener is injected rather than imported by the command: `src/commands/**`\n // may not reach into `src/tui/**`, and handing it in is also what lets the\n // command be tested without a terminal.\n registerCommand(pi, session, {\n openOverlay: async (ctx) => {\n const { openMcpOverlay } = await import('../../tui/mcpOverlay.ts');\n return openMcpOverlay(ctx, session);\n },\n });\n registerLeaderContribution(pi);\n\n pi.on(SESSION_START, (_event, ctx) => {\n activeContext = ctx;\n if (options.configurationSource !== DOOM_CONFIGURATION_SOURCE) session.install(readSessionConfig({}, ctx.cwd));\n const protocol = createProtocolRuntime({\n bus: pi.events,\n source: PACKAGE_SOURCE,\n sessionId: ctx.sessionManager.getSessionId(),\n });\n disposeStatusProvider?.();\n disposeStatusProvider = protocol.provide(McpStatusQueryProtocol, () => session.getSnapshot());\n\n // First point Pi accepts an active-list call: the tools registered while the\n // extension loaded are held back until a server actually answers.\n session.activate();\n for (const diagnostic of session.getDiagnostics()) ctx.ui?.notify(diagnostic, WARNING);\n // Detached on purpose: awaiting here would put every downstream connection on\n // the path between launching Pi and its first prompt.\n void session.start();\n });\n\n pi.on(SESSION_SHUTDOWN, () => {\n disposeStatusProvider?.();\n disposeStatusProvider = undefined;\n activeContext = undefined;\n void session.dispose();\n });\n}\n"],"mappings":"keAUA,MAGM,EAAU,UACV,EAA4B,OAelC,SAAgB,EAAqB,EAAkB,EAA+B,CAAC,EAAS,CAC9F,IAAI,EACA,EAEE,EAAU,IAAI,EAAW,CAC7B,KACA,mBAAoB,MAAO,EAAK,EAAY,CAAE,iBAAkB,CAG9D,GAAI,GAAe,OAAS,MAAO,CACjC,GAAI,CAAC,EAAa,OAClB,GAAI,CACF,MAAM,EAAK,EAAI,SAAS,CAAC,CAC3B,OAAS,EAAO,CAKd,MAJA,EAAc,GAAG,OACf,6CAA6C,EAAW,IAAI,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EAAE,wCACnH,CACF,EACM,CACR,CACA,MACF,CAGA,GAAe,IAAI,OAAO,aAAa,EAAW,gBAAgB,EAAI,SAAS,IAAK,MAAI,CAC1F,CACF,CAAC,EAEK,EAAuB,EAAkB,EAC/C,EAAQ,QACN,EAAQ,sBAAwB,EAC5B,EACA,CACE,SAAU,EAAqB,SAC/B,iBAAkB,EAAqB,gBACzC,CACN,EAIA,EAAgB,EAAI,EAAS,CAC3B,YAAa,KAAO,IAAQ,CAC1B,GAAM,CAAE,kBAAmB,MAAM,OAAO,4BACxC,OAAO,EAAe,EAAK,CAAO,CACpC,CACF,CAAC,EACD,EAA2B,CAAE,EAE7B,EAAG,GAAG,iBAAgB,EAAQ,IAAQ,CACpC,EAAgB,EACZ,EAAQ,sBAAwB,GAA2B,EAAQ,QAAQ,EAAkB,CAAC,EAAG,EAAI,GAAG,CAAC,EAC7G,IAAM,EAAW,EAAsB,CACrC,IAAK,EAAG,OACR,OAAQ,EACR,UAAW,EAAI,eAAe,aAAa,CAC7C,CAAC,EACD,IAAwB,EACxB,EAAwB,EAAS,QAAQ,MAA8B,EAAQ,YAAY,CAAC,EAI5F,EAAQ,SAAS,EACjB,IAAK,IAAM,KAAc,EAAQ,eAAe,EAAG,EAAI,IAAI,OAAO,EAAY,CAAO,EAGrF,EAAa,MAAM,CACrB,CAAC,EAED,EAAG,GAAG,uBAAwB,CAC5B,IAAwB,EACxB,EAAwB,IAAA,GACxB,EAAgB,IAAA,GAChB,EAAa,QAAQ,CACvB,CAAC,CACH"}
@@ -1,2 +1,2 @@
1
- const e=require("../../types/constants.cjs");let t=require("@agimon-ai/doompi-extension-contracts/leader");function n(n){(0,t.registerDoomLeaderContribution)(n,{source:e.PACKAGE_SOURCE,bindings:[{id:`${e.LEADER_BINDING_PREFIX}.open`,path:[e.LEADER_GROUP,{key:`m`,label:`mcp`,detail:e.LEADER_DETAIL}],command:{name:`mcp`}}]})}exports.registerLeaderContribution=n;
1
+ const e=require("./mcpConstants.cjs");let t=require("@agimon-ai/doompi-extension-contracts/leader");function n(n){(0,t.registerDoomLeaderContribution)(n,{source:e.PACKAGE_SOURCE,bindings:[{id:`${e.LEADER_BINDING_PREFIX}.open`,path:[e.LEADER_GROUP,{key:`m`,label:`mcp`,detail:e.LEADER_DETAIL}],command:{name:`mcp`}}]})}exports.registerLeaderContribution=n;
2
2
  //# sourceMappingURL=leader.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"leader.cjs","names":["PACKAGE_SOURCE","LEADER_BINDING_PREFIX","LEADER_GROUP","LEADER_DETAIL"],"sources":["../../../src/adapters/pi/leader.ts"],"sourcesContent":["import { registerDoomLeaderContribution } from '@agimon-ai/doompi-extension-contracts/leader';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport {\n COMMAND_NAME,\n LEADER_BINDING_PREFIX,\n LEADER_DETAIL,\n LEADER_GROUP,\n LEADER_KEY,\n LEADER_LABEL,\n PACKAGE_SOURCE,\n} from '../../types/constants.ts';\n\nexport function registerLeaderContribution(pi: ExtensionAPI): void {\n registerDoomLeaderContribution(pi, {\n source: PACKAGE_SOURCE,\n bindings: [\n {\n id: `${LEADER_BINDING_PREFIX}.open`,\n path: [LEADER_GROUP, { key: LEADER_KEY, label: LEADER_LABEL, detail: LEADER_DETAIL }],\n command: { name: COMMAND_NAME },\n },\n ],\n });\n}\n"],"mappings":"2GAYA,SAAgB,EAA2B,EAAwB,EACjE,EAAA,EAAA,+BAAA,CAA+B,EAAI,CACjC,OAAQA,EAAAA,eACR,SAAU,CACR,CACE,GAAI,GAAGC,EAAAA,sBAAsB,OAC7B,KAAM,CAACC,EAAAA,aAAc,CAAE,IAAA,IAAiB,MAAA,MAAqB,OAAQC,EAAAA,aAAc,CAAC,EACpF,QAAS,CAAE,KAAA,KAAmB,CAChC,CACF,CACF,CAAC,CACH"}
1
+ {"version":3,"file":"leader.cjs","names":["PACKAGE_SOURCE","LEADER_BINDING_PREFIX","LEADER_GROUP","LEADER_DETAIL"],"sources":["../../../src/adapters/pi/leader.ts"],"sourcesContent":["import { registerDoomLeaderContribution } from '@agimon-ai/doompi-extension-contracts/leader';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { COMMAND_NAME } from '../../schemas/mcpCommands.ts';\nimport {\n LEADER_BINDING_PREFIX,\n LEADER_DETAIL,\n LEADER_GROUP,\n LEADER_KEY,\n LEADER_LABEL,\n PACKAGE_SOURCE,\n} from './mcpConstants.ts';\n\nexport function registerLeaderContribution(pi: ExtensionAPI): void {\n registerDoomLeaderContribution(pi, {\n source: PACKAGE_SOURCE,\n bindings: [\n {\n id: `${LEADER_BINDING_PREFIX}.open`,\n path: [LEADER_GROUP, { key: LEADER_KEY, label: LEADER_LABEL, detail: LEADER_DETAIL }],\n command: { name: COMMAND_NAME },\n },\n ],\n });\n}\n"],"mappings":"oGAYA,SAAgB,EAA2B,EAAwB,EACjE,EAAA,EAAA,+BAAA,CAA+B,EAAI,CACjC,OAAQA,EAAAA,eACR,SAAU,CACR,CACE,GAAI,GAAGC,EAAAA,sBAAsB,OAC7B,KAAM,CAACC,EAAAA,aAAc,CAAE,IAAA,IAAiB,MAAA,MAAqB,OAAQC,EAAAA,aAAc,CAAC,EACpF,QAAS,CAAE,KAAA,KAAmB,CAChC,CACF,CACF,CAAC,CACH"}
@@ -1,2 +1,2 @@
1
- import{LEADER_BINDING_PREFIX as e,LEADER_DETAIL as t,LEADER_GROUP as n,PACKAGE_SOURCE as r}from"../../types/constants.mjs";import{registerDoomLeaderContribution as i}from"@agimon-ai/doompi-extension-contracts/leader";function a(a){i(a,{source:r,bindings:[{id:`${e}.open`,path:[n,{key:`m`,label:`mcp`,detail:t}],command:{name:`mcp`}}]})}export{a as registerLeaderContribution};
1
+ import{LEADER_BINDING_PREFIX as e,LEADER_DETAIL as t,LEADER_GROUP as n,PACKAGE_SOURCE as r}from"./mcpConstants.mjs";import{registerDoomLeaderContribution as i}from"@agimon-ai/doompi-extension-contracts/leader";function a(a){i(a,{source:r,bindings:[{id:`${e}.open`,path:[n,{key:`m`,label:`mcp`,detail:t}],command:{name:`mcp`}}]})}export{a as registerLeaderContribution};
2
2
  //# sourceMappingURL=leader.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"leader.mjs","names":[],"sources":["../../../src/adapters/pi/leader.ts"],"sourcesContent":["import { registerDoomLeaderContribution } from '@agimon-ai/doompi-extension-contracts/leader';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport {\n COMMAND_NAME,\n LEADER_BINDING_PREFIX,\n LEADER_DETAIL,\n LEADER_GROUP,\n LEADER_KEY,\n LEADER_LABEL,\n PACKAGE_SOURCE,\n} from '../../types/constants.ts';\n\nexport function registerLeaderContribution(pi: ExtensionAPI): void {\n registerDoomLeaderContribution(pi, {\n source: PACKAGE_SOURCE,\n bindings: [\n {\n id: `${LEADER_BINDING_PREFIX}.open`,\n path: [LEADER_GROUP, { key: LEADER_KEY, label: LEADER_LABEL, detail: LEADER_DETAIL }],\n command: { name: COMMAND_NAME },\n },\n ],\n });\n}\n"],"mappings":"yNAYA,SAAgB,EAA2B,EAAwB,CACjE,EAA+B,EAAI,CACjC,OAAQ,EACR,SAAU,CACR,CACE,GAAI,GAAG,EAAsB,OAC7B,KAAM,CAAC,EAAc,CAAE,IAAA,IAAiB,MAAA,MAAqB,OAAQ,CAAc,CAAC,EACpF,QAAS,CAAE,KAAA,KAAmB,CAChC,CACF,CACF,CAAC,CACH"}
1
+ {"version":3,"file":"leader.mjs","names":[],"sources":["../../../src/adapters/pi/leader.ts"],"sourcesContent":["import { registerDoomLeaderContribution } from '@agimon-ai/doompi-extension-contracts/leader';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { COMMAND_NAME } from '../../schemas/mcpCommands.ts';\nimport {\n LEADER_BINDING_PREFIX,\n LEADER_DETAIL,\n LEADER_GROUP,\n LEADER_KEY,\n LEADER_LABEL,\n PACKAGE_SOURCE,\n} from './mcpConstants.ts';\n\nexport function registerLeaderContribution(pi: ExtensionAPI): void {\n registerDoomLeaderContribution(pi, {\n source: PACKAGE_SOURCE,\n bindings: [\n {\n id: `${LEADER_BINDING_PREFIX}.open`,\n path: [LEADER_GROUP, { key: LEADER_KEY, label: LEADER_LABEL, detail: LEADER_DETAIL }],\n command: { name: COMMAND_NAME },\n },\n ],\n });\n}\n"],"mappings":"kNAYA,SAAgB,EAA2B,EAAwB,CACjE,EAA+B,EAAI,CACjC,OAAQ,EACR,SAAU,CACR,CACE,GAAI,GAAG,EAAsB,OAC7B,KAAM,CAAC,EAAc,CAAE,IAAA,IAAiB,MAAA,MAAqB,OAAQ,CAAc,CAAC,EACpF,QAAS,CAAE,KAAA,KAAmB,CAChC,CACF,CACF,CAAC,CACH"}
@@ -0,0 +1,2 @@
1
+ const e={key:`e`,label:`extension`,order:50};exports.LEADER_BINDING_PREFIX=`doom-mcp`,exports.LEADER_DETAIL=`servers and tools`,exports.LEADER_GROUP=e,exports.PACKAGE_SOURCE=`@agimon-ai/doompi-mcp`;
2
+ //# sourceMappingURL=mcpConstants.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcpConstants.cjs","names":[],"sources":["../../../src/adapters/pi/mcpConstants.ts"],"sourcesContent":["export const PACKAGE_SOURCE = '@agimon-ai/doompi-mcp';\n/** Leader binding ids are restricted to `[a-z0-9][a-z0-9._:-]*`, so the scope and slash are dropped. */\nexport const LEADER_BINDING_PREFIX = 'doom-mcp';\n/**\n * Sits under the existing `extension` group, beside `tools` and `config`.\n *\n * A top-level key would have to be unique across every extension, and MCP belongs\n * next to the tool browser a user reaches for anyway. `t` in particular is taken:\n * it is Doom Task's top-level group.\n */\nexport const LEADER_GROUP = { key: 'e', label: 'extension', order: 50 } as const;\nexport const LEADER_KEY = 'm';\nexport const LEADER_LABEL = 'mcp';\nexport const LEADER_DETAIL = 'servers and tools';\n"],"mappings":"AAAA,MAUa,EAAe,CAAE,IAAK,IAAK,MAAO,YAAa,MAAO,EAAG"}
@@ -0,0 +1,2 @@
1
+ const e=`@agimon-ai/doompi-mcp`,t=`doom-mcp`,n={key:`e`,label:`extension`,order:50},r=`servers and tools`;export{t as LEADER_BINDING_PREFIX,r as LEADER_DETAIL,n as LEADER_GROUP,e as PACKAGE_SOURCE};
2
+ //# sourceMappingURL=mcpConstants.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcpConstants.mjs","names":[],"sources":["../../../src/adapters/pi/mcpConstants.ts"],"sourcesContent":["export const PACKAGE_SOURCE = '@agimon-ai/doompi-mcp';\n/** Leader binding ids are restricted to `[a-z0-9][a-z0-9._:-]*`, so the scope and slash are dropped. */\nexport const LEADER_BINDING_PREFIX = 'doom-mcp';\n/**\n * Sits under the existing `extension` group, beside `tools` and `config`.\n *\n * A top-level key would have to be unique across every extension, and MCP belongs\n * next to the tool browser a user reaches for anyway. `t` in particular is taken:\n * it is Doom Task's top-level group.\n */\nexport const LEADER_GROUP = { key: 'e', label: 'extension', order: 50 } as const;\nexport const LEADER_KEY = 'm';\nexport const LEADER_LABEL = 'mcp';\nexport const LEADER_DETAIL = 'servers and tools';\n"],"mappings":"AAAA,MAAa,EAAiB,wBAEjB,EAAwB,WAQxB,EAAe,CAAE,IAAK,IAAK,MAAO,YAAa,MAAO,EAAG,EAGzD,EAAgB"}
@@ -0,0 +1,2 @@
1
+ const e=require("../node/configSources.cjs"),t=require("../node/mcpRuntime.cjs"),n=require("../process/sessionConfig.cjs"),r=require("../../services/mcpCatalog.cjs"),i=require("../process/directToolsEnvironment.cjs"),a=require("./mcpTools.cjs"),o=`The MCP runtime has not started yet.`;var s=class{pi;runtime;catalog;groups={shared:{configPaths:[],serverNames:[]},sessionLocal:{configPaths:[],serverNames:[]},droppedServers:[]};options;resourceCache=new Map;changeListeners=new Set;authorizationUrls=new Map;browserAuthorizationRequests=new Set;lifecycleGeneration=0;clientManager=()=>this.runtime?.getServices()?.clientManager;constructor(e){this.pi=e.pi,this.options=e,this.catalog=this.createCatalog()}activate(){a.applyActiveTools(this.pi,this.catalog)}createCatalog(){return new r.McpCatalog(i.readDirectToolFilter(this.options.environment))}install(r=n.readSessionConfig(this.options.environment)){this.groups=e.buildMcpConfigGroups(r),this.catalog=this.createCatalog();let i=[...new Set([...this.groups.sessionLocal.serverNames,...this.groups.shared.serverNames])];return this.catalog.seed(t.readCachedCatalog(this.configPaths()),i),a.registerNewTools(this.pi,this.clientManager,this.catalog.allTools()),this.catalog.toSnapshot()}async start(){let e=++this.lifecycleGeneration,[{createTokenStore:t},{McpRuntimeOwner:n}]=await Promise.all([Promise.resolve().then(()=>require("../node/keyringTokenStore.cjs")),Promise.resolve().then(()=>require("../node/mcpRuntime.cjs"))]);if(e!==this.lifecycleGeneration)return;this.runtime??=new n;let r=this.runtime;this.resourceCache.clear();let i=this.configPaths();if(this.configuredServerNames().length===0)return;let a=this.options.tokenStore??await t();await r.start({configPaths:i,tokenStore:a,onAuthorizationUrl:(e,t)=>this.onAuthorizationUrl(e,t),onServerStateChange:e=>this.onServerStateChange(e)}),e!==this.lifecycleGeneration&&await r.dispose()}onAuthorizationUrl(e,t){this.authorizationUrls.set(t,e.toString()),this.emitChange();let n=this.browserAuthorizationRequests.has(t);Promise.resolve().then(()=>this.options.onAuthorizationUrl?.(e,t,{openBrowser:n})).catch(e=>{let n=e instanceof Error?e.message:String(e);this.catalog.addDiagnostic(`Could not open authorization page for "${t}": ${n}`),this.emitChange()})}configPaths(){return[...this.groups.sessionLocal.configPaths,...this.groups.shared.configPaths]}configuredServerNames(){return[...new Set([...this.groups.sessionLocal.serverNames,...this.groups.shared.serverNames])]}async reauthorize(e){let t=this.clientManager();if(!t)throw Error(o);this.resourceCache.delete(e),this.browserAuthorizationRequests.add(e);try{await t.disconnectServer(e),await t.ensureConnected(e)}finally{this.browserAuthorizationRequests.delete(e)}}async openAuthorizationPage(e){let t=this.authorizationUrls.get(e);if(!t)throw Error(`No authorization page is waiting for this MCP server.`);let n=this.options.onAuthorizationUrl;if(!n)throw Error(`This host cannot open authorization pages.`);await n(new URL(t),e,{openBrowser:!0})}getSnapshot(){return this.catalog.toSnapshot()}getServers(){return this.catalog.toView().map(e=>{let t=this.authorizationUrls.get(e.name);return t?{...e,authorizationUrl:t}:e})}setEnabled(e,t){this.catalog.setDisabled(e,!t),a.applyActiveTools(this.pi,this.catalog),this.emitChange()}async listResources(e,t={}){if(!t.refresh){let t=this.resourceCache.get(e);if(t)return t}let n=this.runtime,r=n?.getServices();if(!n||!r)throw Error(o);let i=await(await r.clientManager.ensureConnected(e)).listResources();return n.isCurrent(r)?(this.resourceCache.set(e,i),this.catalog.setResourceCount(e,i.length),this.emitChange(),i):i}onChange(e){return this.changeListeners.add(e),()=>{this.changeListeners.delete(e)}}emitChange(){for(let e of this.changeListeners)try{e()}catch(e){this.catalog.addDiagnostic(`An MCP change listener failed: ${e instanceof Error?e.message:String(e)}`)}}getDiagnostics(){return this.catalog.getDiagnostics()}async dispose(){++this.lifecycleGeneration,this.browserAuthorizationRequests.clear(),await this.runtime?.dispose()}onServerStateChange(e){e.state!==`connecting`&&this.authorizationUrls.delete(e.serverName);let t=this.runtime,n=t?.getServices();(e.state===`connected`&&n?n.clientManager.ensureConnected(e.serverName).then(e=>e.listTools()):Promise.resolve([])).catch(()=>[]).then(r=>{if(!t?.isCurrent(n))return;let i=this.catalog.applyStateChange(e,r);a.registerNewTools(this.pi,this.clientManager,i),a.applyActiveTools(this.pi,this.catalog),this.emitChange()})}};exports.McpSession=s;
2
+ //# sourceMappingURL=mcpSession.cjs.map