@agimon-ai/doompi-mcp 0.0.1-alpha.6 → 0.0.1-alpha.65
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.
- package/README.md +113 -17
- package/dist/adapters/node/configSources.cjs +2 -0
- package/dist/adapters/node/configSources.cjs.map +1 -0
- package/dist/adapters/node/configSources.d.cts +15 -0
- package/dist/adapters/node/configSources.d.cts.map +1 -0
- package/dist/adapters/node/configSources.d.mts +15 -0
- package/dist/adapters/node/configSources.d.mts.map +1 -0
- package/dist/adapters/node/configSources.mjs +2 -0
- package/dist/adapters/node/configSources.mjs.map +1 -0
- package/dist/adapters/node/keyringTokenStore.cjs.map +1 -0
- package/dist/adapters/node/keyringTokenStore.mjs.map +1 -0
- package/dist/adapters/node/mcpRuntime.cjs +2 -0
- package/dist/adapters/node/mcpRuntime.cjs.map +1 -0
- package/dist/adapters/node/mcpRuntime.d.cts +29 -0
- package/dist/adapters/node/mcpRuntime.d.cts.map +1 -0
- package/dist/adapters/node/mcpRuntime.d.mts +29 -0
- package/dist/adapters/node/mcpRuntime.d.mts.map +1 -0
- package/dist/adapters/node/mcpRuntime.mjs +2 -0
- package/dist/adapters/node/mcpRuntime.mjs.map +1 -0
- package/dist/adapters/node/mcpSettingsManager.cjs +2 -0
- package/dist/adapters/node/mcpSettingsManager.cjs.map +1 -0
- package/dist/adapters/node/mcpSettingsManager.mjs +2 -0
- package/dist/adapters/node/mcpSettingsManager.mjs.map +1 -0
- package/dist/adapters/node/projection.cjs +2 -0
- package/dist/adapters/node/projection.cjs.map +1 -0
- package/dist/adapters/node/projection.d.cts +12 -0
- package/dist/adapters/node/projection.d.cts.map +1 -0
- package/dist/adapters/node/projection.d.mts +12 -0
- package/dist/adapters/node/projection.d.mts.map +1 -0
- package/dist/adapters/node/projection.mjs +2 -0
- package/dist/adapters/node/projection.mjs.map +1 -0
- package/dist/adapters/pi/extension.cjs +2 -0
- package/dist/adapters/pi/extension.cjs.map +1 -0
- package/dist/adapters/pi/extension.d.cts +11 -0
- package/dist/adapters/pi/extension.d.cts.map +1 -0
- package/dist/adapters/pi/extension.d.mts +11 -0
- package/dist/adapters/pi/extension.d.mts.map +1 -0
- package/dist/adapters/pi/extension.mjs +2 -0
- package/dist/adapters/pi/extension.mjs.map +1 -0
- package/dist/adapters/pi/leader.cjs +2 -0
- package/dist/adapters/pi/leader.cjs.map +1 -0
- package/dist/adapters/pi/leader.mjs +2 -0
- package/dist/adapters/pi/leader.mjs.map +1 -0
- package/dist/adapters/pi/mcpConstants.cjs +2 -0
- package/dist/adapters/pi/mcpConstants.cjs.map +1 -0
- package/dist/adapters/pi/mcpConstants.mjs +2 -0
- package/dist/adapters/pi/mcpConstants.mjs.map +1 -0
- package/dist/adapters/pi/mcpSession.cjs +2 -0
- package/dist/adapters/pi/mcpSession.cjs.map +1 -0
- package/dist/adapters/pi/mcpSession.mjs +2 -0
- package/dist/adapters/pi/mcpSession.mjs.map +1 -0
- package/dist/adapters/pi/mcpTools.cjs +3 -0
- package/dist/adapters/pi/mcpTools.cjs.map +1 -0
- package/dist/adapters/pi/mcpTools.mjs +3 -0
- package/dist/adapters/pi/mcpTools.mjs.map +1 -0
- package/dist/adapters/process/directToolsEnvironment.cjs +2 -0
- package/dist/adapters/process/directToolsEnvironment.cjs.map +1 -0
- package/dist/adapters/process/directToolsEnvironment.mjs +2 -0
- package/dist/adapters/process/directToolsEnvironment.mjs.map +1 -0
- package/dist/adapters/process/sessionConfig.cjs +2 -0
- package/dist/adapters/process/sessionConfig.cjs.map +1 -0
- package/dist/adapters/process/sessionConfig.d.cts +15 -0
- package/dist/adapters/process/sessionConfig.d.cts.map +1 -0
- package/dist/adapters/process/sessionConfig.d.mts +15 -0
- package/dist/adapters/process/sessionConfig.d.mts.map +1 -0
- package/dist/adapters/process/sessionConfig.mjs +2 -0
- package/dist/adapters/process/sessionConfig.mjs.map +1 -0
- package/dist/adapters/web/mcpHubApi.cjs +2 -0
- package/dist/adapters/web/mcpHubApi.cjs.map +1 -0
- package/dist/adapters/web/mcpHubApi.d.cts +5 -0
- package/dist/adapters/web/mcpHubApi.d.cts.map +1 -0
- package/dist/adapters/web/mcpHubApi.d.mts +5 -0
- package/dist/adapters/web/mcpHubApi.d.mts.map +1 -0
- package/dist/adapters/web/mcpHubApi.mjs +2 -0
- package/dist/adapters/web/mcpHubApi.mjs.map +1 -0
- package/dist/commands/mcpCommand.cjs +2 -2
- package/dist/commands/mcpCommand.cjs.map +1 -1
- package/dist/commands/mcpCommand.mjs +2 -2
- package/dist/commands/mcpCommand.mjs.map +1 -1
- package/dist/extensions/pi.cjs +1 -2
- package/dist/extensions/pi.d.cts +1 -1
- package/dist/extensions/pi.d.mts +1 -1
- package/dist/extensions/pi.mjs +1 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +9 -6
- package/dist/index.d.mts +9 -6
- package/dist/index.mjs +1 -1
- package/dist/projection.cjs +1 -0
- package/dist/projection.d.cts +5 -0
- package/dist/projection.d.mts +5 -0
- package/dist/projection.mjs +1 -0
- package/dist/schemas/directTools.cjs +2 -0
- package/dist/schemas/directTools.cjs.map +1 -0
- package/dist/schemas/directTools.d.cts +7 -0
- package/dist/schemas/directTools.d.cts.map +1 -0
- package/dist/schemas/directTools.d.mts +7 -0
- package/dist/schemas/directTools.d.mts.map +1 -0
- package/dist/schemas/directTools.mjs +2 -0
- package/dist/schemas/directTools.mjs.map +1 -0
- package/dist/schemas/sessionConfig.cjs +2 -0
- package/dist/schemas/sessionConfig.cjs.map +1 -0
- package/dist/schemas/sessionConfig.d.cts +5 -0
- package/dist/schemas/sessionConfig.d.cts.map +1 -0
- package/dist/schemas/sessionConfig.d.mts +5 -0
- package/dist/schemas/sessionConfig.d.mts.map +1 -0
- package/dist/schemas/sessionConfig.mjs +2 -0
- package/dist/schemas/sessionConfig.mjs.map +1 -0
- package/dist/services/directTools.cjs +1 -1
- package/dist/services/directTools.cjs.map +1 -1
- package/dist/services/directTools.mjs +1 -1
- package/dist/services/directTools.mjs.map +1 -1
- package/dist/services/mcpCatalog.cjs +1 -1
- package/dist/services/mcpCatalog.cjs.map +1 -1
- package/dist/services/mcpCatalog.d.cts +5 -6
- package/dist/services/mcpCatalog.d.cts.map +1 -1
- package/dist/services/mcpCatalog.d.mts +5 -6
- package/dist/services/mcpCatalog.d.mts.map +1 -1
- package/dist/services/mcpCatalog.mjs +1 -1
- package/dist/services/mcpCatalog.mjs.map +1 -1
- package/dist/tui/mcpOverlay.cjs +1 -1
- package/dist/tui/mcpOverlay.cjs.map +1 -1
- package/dist/tui/mcpOverlay.mjs +1 -1
- package/dist/tui/mcpOverlay.mjs.map +1 -1
- package/dist/tui/mcpToolRender.cjs +3 -0
- package/dist/tui/mcpToolRender.cjs.map +1 -0
- package/dist/tui/mcpToolRender.mjs +3 -0
- package/dist/tui/mcpToolRender.mjs.map +1 -0
- package/dist/types/mcp.d.cts +1 -0
- package/dist/types/mcp.d.mts +1 -0
- package/dist/types/mcpCatalog.d.cts +1 -0
- package/dist/types/mcpCatalog.d.mts +1 -0
- package/dist/types/mcpConfig.d.cts +56 -0
- package/dist/types/mcpConfig.d.cts.map +1 -0
- package/dist/types/mcpConfig.d.mts +56 -0
- package/dist/types/mcpConfig.d.mts.map +1 -0
- package/dist/types/mcpRuntime.d.cts +12 -0
- package/dist/types/mcpRuntime.d.cts.map +1 -0
- package/dist/types/mcpRuntime.d.mts +12 -0
- package/dist/types/mcpRuntime.d.mts.map +1 -0
- package/dist/types/webMcp.cjs +2 -0
- package/dist/types/webMcp.cjs.map +1 -0
- package/dist/types/webMcp.mjs +2 -0
- package/dist/types/webMcp.mjs.map +1 -0
- package/dist/webHub.cjs +1 -0
- package/dist/webHub.d.cts +2 -0
- package/dist/webHub.d.mts +2 -0
- package/dist/webHub.mjs +1 -0
- package/llms.txt +9 -0
- package/package.json +70 -22
- package/src/exports/webClient.ts +1 -0
- package/src/prompts/doompi-use-mcp/SKILL.md +37 -0
- package/src/types/webMcp.ts +171 -0
- package/src/web/api/mcpSettingsApi.ts +83 -0
- package/src/web/components/McpRepositorySettingsPanel.tsx +248 -0
- package/src/web/components/McpSessionAuthSection.tsx +268 -0
- package/src/web/components/McpToolMessage.tsx +147 -0
- package/src/web/index.ts +30 -0
- package/src/web/lib/mcpToolMatch.ts +177 -0
- package/src/web/stores/mcpSettingsStore.ts +144 -0
- package/src/web/tsconfig.json +23 -0
- package/dist/constants.cjs +0 -2
- package/dist/constants.cjs.map +0 -1
- package/dist/constants.mjs +0 -2
- package/dist/constants.mjs.map +0 -1
- package/dist/extension.cjs +0 -2
- package/dist/extension.cjs.map +0 -1
- package/dist/extension.d.cts +0 -18
- package/dist/extension.d.cts.map +0 -1
- package/dist/extension.d.mts +0 -18
- package/dist/extension.d.mts.map +0 -1
- package/dist/extension.mjs +0 -2
- package/dist/extension.mjs.map +0 -1
- package/dist/extensions/doom.cjs +0 -2
- package/dist/extensions/doom.cjs.map +0 -1
- package/dist/extensions/doom.d.cts +0 -6
- package/dist/extensions/doom.d.cts.map +0 -1
- package/dist/extensions/doom.d.mts +0 -7
- package/dist/extensions/doom.d.mts.map +0 -1
- package/dist/extensions/doom.mjs +0 -2
- package/dist/extensions/doom.mjs.map +0 -1
- package/dist/extensions/pi.cjs.map +0 -1
- package/dist/extensions/pi.mjs.map +0 -1
- package/dist/leader.cjs +0 -2
- package/dist/leader.cjs.map +0 -1
- package/dist/leader.mjs +0 -2
- package/dist/leader.mjs.map +0 -1
- package/dist/services/configSources.cjs +0 -2
- package/dist/services/configSources.cjs.map +0 -1
- package/dist/services/configSources.d.cts +0 -48
- package/dist/services/configSources.d.cts.map +0 -1
- package/dist/services/configSources.d.mts +0 -48
- package/dist/services/configSources.d.mts.map +0 -1
- package/dist/services/configSources.mjs +0 -2
- package/dist/services/configSources.mjs.map +0 -1
- package/dist/services/directTools.d.cts +0 -14
- package/dist/services/directTools.d.cts.map +0 -1
- package/dist/services/directTools.d.mts +0 -14
- package/dist/services/directTools.d.mts.map +0 -1
- package/dist/services/keyringTokenStore.cjs.map +0 -1
- package/dist/services/keyringTokenStore.mjs.map +0 -1
- package/dist/services/mcpRuntime.cjs +0 -2
- package/dist/services/mcpRuntime.cjs.map +0 -1
- package/dist/services/mcpRuntime.d.cts +0 -26
- package/dist/services/mcpRuntime.d.cts.map +0 -1
- package/dist/services/mcpRuntime.d.mts +0 -26
- package/dist/services/mcpRuntime.d.mts.map +0 -1
- package/dist/services/mcpRuntime.mjs +0 -2
- package/dist/services/mcpRuntime.mjs.map +0 -1
- package/dist/services/mcpSession.cjs +0 -2
- package/dist/services/mcpSession.cjs.map +0 -1
- package/dist/services/mcpSession.mjs +0 -2
- package/dist/services/mcpSession.mjs.map +0 -1
- package/dist/services/sessionConfig.cjs +0 -2
- package/dist/services/sessionConfig.cjs.map +0 -1
- package/dist/services/sessionConfig.d.cts +0 -25
- package/dist/services/sessionConfig.d.cts.map +0 -1
- package/dist/services/sessionConfig.d.mts +0 -25
- package/dist/services/sessionConfig.d.mts.map +0 -1
- package/dist/services/sessionConfig.mjs +0 -2
- package/dist/services/sessionConfig.mjs.map +0 -1
- package/dist/tools/mcpTools.cjs +0 -3
- package/dist/tools/mcpTools.cjs.map +0 -1
- package/dist/tools/mcpTools.mjs +0 -3
- package/dist/tools/mcpTools.mjs.map +0 -1
- package/dist/types.d.cts +0 -2
- package/dist/types.d.mts +0 -2
- /package/dist/{services → adapters/node}/keyringTokenStore.cjs +0 -0
- /package/dist/{services → adapters/node}/keyringTokenStore.mjs +0 -0
package/README.md
CHANGED
|
@@ -1,34 +1,130 @@
|
|
|
1
1
|
# @agimon-ai/doompi-mcp
|
|
2
2
|
|
|
3
|
-
MCP
|
|
3
|
+
Domain-aware MCP selection for DoomPi and a standalone MCP adapter for Pi.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
a domain with an `mcp` allowlist reaches only the servers and proxy upstreams it names.
|
|
5
|
+
Part of the [DoomPi distribution](https://www.npmjs.com/package/@agimon-ai/doompi).
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
The package removes disallowed servers before any stdio process is spawned. A filtered server is
|
|
8
|
+
absent rather than merely hidden from the model.
|
|
9
|
+
|
|
10
|
+
> **Alpha:** configuration and adapter contracts may change between releases.
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
- Node.js 22.19.0 or newer
|
|
15
|
+
- Pi 0.85.0 and Pi TUI 0.85.0
|
|
11
16
|
|
|
12
17
|
## Install
|
|
13
18
|
|
|
19
|
+
`doompi init` and `dpi init` include this adapter in `default.packages` in `.doom/modes.yaml`.
|
|
20
|
+
Remove it or move it to a named layer to change which major modes load it. When loaded in DoomPi,
|
|
21
|
+
domain selection controls server access. For plain Pi:
|
|
22
|
+
|
|
14
23
|
```bash
|
|
15
|
-
|
|
24
|
+
pi install npm:@agimon-ai/doompi-mcp
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
| Entry | Purpose |
|
|
28
|
+
| ------------------------------------- | ---------------------------------------------------------- |
|
|
29
|
+
| `@agimon-ai/doompi-mcp/extensions/pi` | Standard Pi adapter using repository and domain MCP config |
|
|
30
|
+
| `@agimon-ai/doompi-mcp` | Library API |
|
|
31
|
+
| `@agimon-ai/doompi-mcp/projection` | Neutral projection adapter and Agent Plugin normalization |
|
|
32
|
+
|
|
33
|
+
## Configure direct servers
|
|
34
|
+
|
|
35
|
+
Repository `.mcp.json` entries describe direct MCP servers:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"mcpServers": {
|
|
40
|
+
"filesystem": {
|
|
41
|
+
"command": "npx",
|
|
42
|
+
"args": ["-y", "@modelcontextprotocol/server-filesystem", "."]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Selected legacy plugins may also provide root `.mcp.json`. A schema-gated Agent Plugin v1 instead
|
|
49
|
+
provides root `mcp.json`. Doom validates its portable contract, supplies private persistent
|
|
50
|
+
`PLUGIN_DATA`, and resolves plugin-relative stdio commands and working directories. It also
|
|
51
|
+
normalizes `streamable-http` for the embedded runtime. Invalid portable server entries are isolated
|
|
52
|
+
from the other entries in that plugin.
|
|
53
|
+
|
|
54
|
+
Proxy upstreams are read from `mcp-config.yaml`. Domains can select direct `servers` and proxy
|
|
55
|
+
upstreams separately in `.doom/domains.yaml`:
|
|
56
|
+
|
|
57
|
+
```yaml
|
|
58
|
+
# .doom/domains.yaml
|
|
59
|
+
domains:
|
|
60
|
+
development:
|
|
61
|
+
description: Repository development tools.
|
|
62
|
+
mcp:
|
|
63
|
+
servers: [filesystem]
|
|
64
|
+
proxy: [github]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
An absent or empty allowlist retains configured entries. Use `doompi --no-mcp` when no MCP server
|
|
68
|
+
should load. In plain Pi mode, no DoomPi domain allowlist is applied.
|
|
69
|
+
|
|
70
|
+
Within DoomPi, this package consumes the immutable `doomMcpProjection` service published on the
|
|
71
|
+
session Cordis root. Each Pi reload disposes the old injected runtime before the replacement binds.
|
|
72
|
+
Downstream clients run in-process through `@agimon-ai/mcp-proxy`; this adapter does not start its
|
|
73
|
+
Hono server.
|
|
74
|
+
|
|
75
|
+
## Commands
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
/mcp
|
|
79
|
+
/mcp status
|
|
80
|
+
/mcp auth <server>
|
|
81
|
+
/mcp reload
|
|
16
82
|
```
|
|
17
83
|
|
|
18
|
-
|
|
84
|
+
In a TUI, the bare `/mcp` command opens the interactive overlay. In the web cockpit, the focused
|
|
85
|
+
session's Context panel lists only servers currently waiting for authorization. Its `authorize`
|
|
86
|
+
action sends one `/mcp auth <server>` prompt frame to that session. The authorization URL then appears
|
|
87
|
+
as a clickable notice in the transcript. Headless hosts never open a desktop browser automatically.
|
|
88
|
+
|
|
89
|
+
The live browser status is intentionally compact: `doom-mcp-session-auth` contains JSON rows shaped
|
|
90
|
+
as `[{"name":"server","state":"needs-auth"}]` and is cleared when no server needs authorization.
|
|
91
|
+
It never includes authorization URLs, errors, credentials, or credential-store data. Repository
|
|
92
|
+
catalog and authorization APIs remain separate from this live-session view.
|
|
93
|
+
|
|
94
|
+
## Credentials and trust
|
|
95
|
+
|
|
96
|
+
Allowed stdio entries execute their configured commands with the Pi process environment and
|
|
97
|
+
operating system privileges. Review configuration as executable code.
|
|
19
98
|
|
|
20
|
-
|
|
21
|
-
|
|
99
|
+
OAuth credentials use the operating system keyring when available. The private-file fallback is
|
|
100
|
+
stored under `~/.mcp-proxy/oauth` with owner-only permissions. Credentials remain machine-wide and
|
|
101
|
+
keyed by server name, while their upstream URL binding prevents blind replay to another endpoint.
|
|
102
|
+
Treat both the configuration and credential store as sensitive.
|
|
22
103
|
|
|
23
|
-
|
|
104
|
+
The default OAuth callback is `http://127.0.0.1:19876/callback`. Authorization therefore requires the
|
|
105
|
+
browser and DoomPi runtime to share that loopback network namespace. A remote browser cannot complete
|
|
106
|
+
the callback when its `127.0.0.1` resolves to another machine or container.
|
|
24
107
|
|
|
25
|
-
##
|
|
108
|
+
## Public API
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { buildMcpConfigGroups, readCachedCatalog, registerMcpExtension, toPiToolName } from '@agimon-ai/doompi-mcp';
|
|
112
|
+
import type { McpAllowlist, McpSessionConfig } from '@agimon-ai/doompi-mcp';
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`doompi --emit-mcp <directory>` emits the resolved MCP configuration to the target directory
|
|
116
|
+
without launching a model.
|
|
117
|
+
|
|
118
|
+
## Development
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
pnpm build
|
|
122
|
+
pnpm typecheck
|
|
123
|
+
pnpm test
|
|
124
|
+
pnpm lint
|
|
125
|
+
```
|
|
26
126
|
|
|
27
|
-
|
|
28
|
-
| --------------------------------------- |
|
|
29
|
-
| `@agimon-ai/doompi-mcp` |
|
|
30
|
-
| `@agimon-ai/doompi-mcp/extensions/pi` |
|
|
31
|
-
| `@agimon-ai/doompi-mcp/extensions/doom` |
|
|
127
|
+
Maintained by [Agimon](https://agimon.ai/about).
|
|
32
128
|
|
|
33
129
|
## License
|
|
34
130
|
|
|
@@ -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"),a=require("@agimon-ai/doompi-config/agentPluginMcp");const o=`mcp-proxy`,s=new Set([o,`agiflow-proxy`]);function c(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function l(e){let t=r.default.extname(e).toLowerCase();return t===`.yaml`||t===`.yml`}function u(e){try{let t=n.default.readFileSync(e,`utf8`),r=l(e)?(0,i.parse)(t):JSON.parse(t);return c(r)?{config:r,contents:t}:void 0}catch{return}}function d(e){return u(e)?.config}function f(e){return c(e?.mcpServers)?e.mcpServers:{}}function p(e){return!c(e)||e.disabled!==!0}function m(e){return s.has(e)}function h(e,t){let i=f(d(t)),a=[...s].map(e=>i[e]).find(e=>c(e))?.args,o=Array.isArray(a)?a:[],l=o.indexOf(`--config`),u=o[l+1],p=l>=0&&typeof u==`string`?r.default.resolve(e,u):r.default.join(e,`mcp-config.yaml`);return n.default.existsSync(p)?p:void 0}function g(e){return h(e,r.default.join(e,`.mcp.json`))}function _(e,t){return!t||t.length===0||t.includes(e)}function v(e,n,i){let a=(0,t.createHash)(`sha256`).update(e).update(`\0`).update(n).digest(`hex`).slice(0,16),o=l(e)?`.yaml`:`.json`;return r.default.join(i,`mcp-${a}${o}`)}function y(e,t){return l(e)?(0,i.stringify)(t):`${JSON.stringify(t,null,2)}\n`}function b(e,r,i,a,o){return(0,t.createHash)(`sha256`).update(r).update(`\0`).update(n.default.realpathSync(e)).update(`\0`).update(o??`unprojected`).update(`\0`).update(i).update(`\0`).update(a).digest(`hex`)}function x(e,t,r,i){let a=u(e);if(!a)return;let{config:o,contents:s}=a,c=f(o),l={},d=[];for(let[e,n]of Object.entries(c))i.stripProxyWrappers&&m(e)||!_(e,t)?d.push(e):l[e]=n;let h=Object.entries(l).filter(([,e])=>p(e)).map(([e])=>e);if(d.length===0)return{source:{path:e,format:`claude`,cacheKey:b(e,`claude`,s,s,i.sourceIdentity)},kept:h,dropped:d};n.default.mkdirSync(r,{mode:448,recursive:!0});let g=y(e,{...o,mcpServers:l}),x=v(e,g,r);return n.default.writeFileSync(x,g,{mode:384}),{source:{path:x,format:`claude`,cacheKey:b(e,`claude`,s,g,i.sourceIdentity)},kept:h,dropped:d}}function S(e){try{return(0,t.createHash)(`sha256`).update(n.default.readFileSync(e.configPath)).digest(`hex`)===e.contentDigest}catch{return!1}}function C(e=[]){return{shared:{configSources:[],configPaths:[],serverNames:[]},sessionLocal:{configSources:[],configPaths:[],serverNames:[]},droppedServers:[],diagnostics:e}}function w(e){if(e.enabled===!1)return C();let t=e.sources,i=t?[]:[r.default.join(e.repoRoot,`.mcp.json`),...(e.pluginConfigPaths??[]).map(t=>r.default.resolve(e.repoRoot,t))].filter(e=>n.default.existsSync(e)),o=[],s=[],c=[],l=[];for(let t of i){let n=x(t,e.allowlist?.servers,e.stagingDirectory,{stripProxyWrappers:!0});n&&(o.push(n.source),s.push(...n.kept),c.push(...n.dropped))}for(let n of t??[]){if(n.format===`agent-plugin-v1`){let t=(0,a.normalizeAgentPluginMcpSource)(n,{stagingDirectory:e.stagingDirectory,allowedServers:e.allowlist?.servers});t.configSource&&o.push(t.configSource),s.push(...t.serverNames),c.push(...t.droppedServers),l.push(...t.diagnostics);continue}if(!S(n)){l.push(`Native MCP source "${n.sourceId}" was disabled: the file is missing or its content digest changed.`);continue}let t=x(n.configPath,e.allowlist?.servers,e.stagingDirectory,{stripProxyWrappers:!0,sourceIdentity:`${n.sourceId}:${n.contentDigest}`});if(!t){l.push(`Native MCP source "${n.sourceId}" was disabled: the config could not be parsed.`);continue}o.push(t.source),s.push(...t.kept),c.push(...t.dropped)}let u=t?.find(e=>e.format===`native`&&e.owner===`repository`),d=t?u&&S(u)?h(e.repoRoot,u.configPath):void 0:g(e.repoRoot),f=d?x(d,e.allowlist?.proxy,e.stagingDirectory,{stripProxyWrappers:!0}):void 0;return f&&c.push(...f.dropped),{shared:{configSources:f?[f.source]:[],configPaths:f?[f.source.path]:[],serverNames:f?f.kept:[]},sessionLocal:{configSources:o,configPaths:o.map(e=>e.path),serverNames:[...new Set(s)]},droppedServers:[...new Set(c)],diagnostics:l}}exports.PROXY_SERVER_NAME=o,exports.buildMcpConfigGroups=w,exports.resolveSharedConfigPath=g;
|
|
2
|
+
//# sourceMappingURL=configSources.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configSources.cjs","names":["path","fs","parseYaml","createHash","stringifyYaml","normalizeAgentPluginMcpSource"],"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 { normalizeAgentPluginMcpSource } from '@agimon-ai/doompi-config/agentPluginMcp';\nimport type { DoomMcpNativeProjectionSource } from '@agimon-ai/doompi-extension-contracts/mcp-projection';\nimport type { McpConfigGroups, McpConfigGroupsInput, McpConfigSource } 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 source: McpConfigSource;\n kept: string[];\n dropped: string[];\n}\n\ninterface ConfigDocument {\n config: JsonObject;\n contents: 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 readConfigDocument(filePath: string): ConfigDocument | undefined {\n try {\n const contents = fs.readFileSync(filePath, 'utf8');\n const parsed: unknown = isYamlPath(filePath) ? parseYaml(contents) : JSON.parse(contents);\n return isObject(parsed) ? { config: parsed, contents } : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction readConfigObject(filePath: string): JsonObject | undefined {\n return readConfigDocument(filePath)?.config;\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 */\nfunction resolveSharedConfigPathFrom(repoRoot: string, repositoryConfigPath: string): string | undefined {\n const servers = serverDefinitions(readConfigObject(repositoryConfigPath));\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\nexport function resolveSharedConfigPath(repoRoot: string): string | undefined {\n return resolveSharedConfigPathFrom(repoRoot, path.join(repoRoot, '.mcp.json'));\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\nfunction layerCacheKey(\n filePath: string,\n format: McpConfigSource['format'],\n originalContents: string,\n effectiveContents: string,\n sourceIdentity?: string,\n): string {\n return createHash('sha256')\n .update(format)\n .update('\\0')\n .update(fs.realpathSync(filePath))\n .update('\\0')\n .update(sourceIdentity ?? 'unprojected')\n .update('\\0')\n .update(originalContents)\n .update('\\0')\n .update(effectiveContents)\n .digest('hex');\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; sourceIdentity?: string },\n): FilteredLayer | undefined {\n const document = readConfigDocument(filePath);\n if (!document) return undefined;\n const { config, contents: originalContents } = document;\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) {\n return {\n source: {\n path: filePath,\n format: 'claude',\n cacheKey: layerCacheKey(filePath, 'claude', originalContents, originalContents, options.sourceIdentity),\n },\n kept: keptNames,\n dropped,\n };\n }\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 {\n source: {\n path: target,\n format: 'claude',\n cacheKey: layerCacheKey(filePath, 'claude', originalContents, contents, options.sourceIdentity),\n },\n kept: keptNames,\n dropped,\n };\n}\n\nfunction digestMatches(source: DoomMcpNativeProjectionSource): boolean {\n try {\n const digest = createHash('sha256').update(fs.readFileSync(source.configPath)).digest('hex');\n return digest === source.contentDigest;\n } catch {\n return false;\n }\n}\n\nfunction emptyGroups(diagnostics: string[] = []): McpConfigGroups {\n return {\n shared: { configSources: [], configPaths: [], serverNames: [] },\n sessionLocal: { configSources: [], configPaths: [], serverNames: [] },\n droppedServers: [],\n diagnostics,\n };\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 if (input.enabled === false) return emptyGroups();\n\n const projectedSources = input.sources;\n const legacyLayers = projectedSources\n ? []\n : [\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 configSources: McpConfigSource[] = [];\n const serverNames: string[] = [];\n const droppedServers: string[] = [];\n const diagnostics: string[] = [];\n for (const layer of legacyLayers) {\n const filtered = filterLayer(layer, input.allowlist?.servers, input.stagingDirectory, {\n stripProxyWrappers: true,\n });\n if (!filtered) continue;\n configSources.push(filtered.source);\n serverNames.push(...filtered.kept);\n droppedServers.push(...filtered.dropped);\n }\n\n for (const source of projectedSources ?? []) {\n if (source.format === 'agent-plugin-v1') {\n const normalized = normalizeAgentPluginMcpSource(source, {\n stagingDirectory: input.stagingDirectory,\n allowedServers: input.allowlist?.servers,\n });\n if (normalized.configSource) configSources.push(normalized.configSource);\n serverNames.push(...normalized.serverNames);\n droppedServers.push(...normalized.droppedServers);\n diagnostics.push(...normalized.diagnostics);\n continue;\n }\n\n if (!digestMatches(source)) {\n diagnostics.push(\n `Native MCP source \"${source.sourceId}\" was disabled: the file is missing or its content digest changed.`,\n );\n continue;\n }\n const filtered = filterLayer(source.configPath, input.allowlist?.servers, input.stagingDirectory, {\n stripProxyWrappers: true,\n sourceIdentity: `${source.sourceId}:${source.contentDigest}`,\n });\n if (!filtered) {\n diagnostics.push(`Native MCP source \"${source.sourceId}\" was disabled: the config could not be parsed.`);\n continue;\n }\n configSources.push(filtered.source);\n serverNames.push(...filtered.kept);\n droppedServers.push(...filtered.dropped);\n }\n\n const repositorySource = projectedSources?.find(\n (source): source is DoomMcpNativeProjectionSource => source.format === 'native' && source.owner === 'repository',\n );\n const upstreamPath = projectedSources\n ? repositorySource && digestMatches(repositorySource)\n ? resolveSharedConfigPathFrom(input.repoRoot, repositorySource.configPath)\n : undefined\n : 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 configSources: upstream ? [upstream.source] : [],\n configPaths: upstream ? [upstream.source.path] : [],\n serverNames: upstream ? upstream.kept : [],\n },\n // A later layer overriding an earlier one contributes the same name twice.\n sessionLocal: {\n configSources,\n configPaths: configSources.map((source) => source.path),\n serverNames: [...new Set(serverNames)],\n },\n droppedServers: [...new Set(droppedServers)],\n diagnostics,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
export 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
|
+
export declare function buildMcpConfigGroups(input: McpConfigGroupsInput): McpConfigGroups;
|
|
14
|
+
//#endregion
|
|
15
|
+
//# sourceMappingURL=configSources.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configSources.d.cts","names":[],"sources":["../../../src/adapters/node/configSources.ts"],"mappings":";;;qBASa;;;;;;;;;wBA2MG,qBAAqB,OAAO,uBAAuB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
export 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
|
+
export declare function buildMcpConfigGroups(input: McpConfigGroupsInput): McpConfigGroups;
|
|
14
|
+
//#endregion
|
|
15
|
+
//# sourceMappingURL=configSources.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configSources.d.mts","names":[],"sources":["../../../src/adapters/node/configSources.ts"],"mappings":";;;qBASa;;;;;;;;;wBA2MG,qBAAqB,OAAO,uBAAuB"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{createHash as e}from"node:crypto";import t from"node:fs";import n from"node:path";import{parse as r,stringify as i}from"yaml";import{normalizeAgentPluginMcpSource as a}from"@agimon-ai/doompi-config/agentPluginMcp";const o=`mcp-proxy`,s=new Set([o,`agiflow-proxy`]);function c(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function l(e){let t=n.extname(e).toLowerCase();return t===`.yaml`||t===`.yml`}function u(e){try{let n=t.readFileSync(e,`utf8`),i=l(e)?r(n):JSON.parse(n);return c(i)?{config:i,contents:n}:void 0}catch{return}}function d(e){return u(e)?.config}function f(e){return c(e?.mcpServers)?e.mcpServers:{}}function p(e){return!c(e)||e.disabled!==!0}function m(e){return s.has(e)}function h(e,r){let i=f(d(r)),a=[...s].map(e=>i[e]).find(e=>c(e))?.args,o=Array.isArray(a)?a:[],l=o.indexOf(`--config`),u=o[l+1],p=l>=0&&typeof u==`string`?n.resolve(e,u):n.join(e,`mcp-config.yaml`);return t.existsSync(p)?p:void 0}function g(e){return h(e,n.join(e,`.mcp.json`))}function _(e,t){return!t||t.length===0||t.includes(e)}function v(t,r,i){let a=e(`sha256`).update(t).update(`\0`).update(r).digest(`hex`).slice(0,16),o=l(t)?`.yaml`:`.json`;return n.join(i,`mcp-${a}${o}`)}function y(e,t){return l(e)?i(t):`${JSON.stringify(t,null,2)}\n`}function b(n,r,i,a,o){return e(`sha256`).update(r).update(`\0`).update(t.realpathSync(n)).update(`\0`).update(o??`unprojected`).update(`\0`).update(i).update(`\0`).update(a).digest(`hex`)}function x(e,n,r,i){let a=u(e);if(!a)return;let{config:o,contents:s}=a,c=f(o),l={},d=[];for(let[e,t]of Object.entries(c))i.stripProxyWrappers&&m(e)||!_(e,n)?d.push(e):l[e]=t;let h=Object.entries(l).filter(([,e])=>p(e)).map(([e])=>e);if(d.length===0)return{source:{path:e,format:`claude`,cacheKey:b(e,`claude`,s,s,i.sourceIdentity)},kept:h,dropped:d};t.mkdirSync(r,{mode:448,recursive:!0});let g=y(e,{...o,mcpServers:l}),x=v(e,g,r);return t.writeFileSync(x,g,{mode:384}),{source:{path:x,format:`claude`,cacheKey:b(e,`claude`,s,g,i.sourceIdentity)},kept:h,dropped:d}}function S(n){try{return e(`sha256`).update(t.readFileSync(n.configPath)).digest(`hex`)===n.contentDigest}catch{return!1}}function C(e=[]){return{shared:{configSources:[],configPaths:[],serverNames:[]},sessionLocal:{configSources:[],configPaths:[],serverNames:[]},droppedServers:[],diagnostics:e}}function w(e){if(e.enabled===!1)return C();let r=e.sources,i=r?[]:[n.join(e.repoRoot,`.mcp.json`),...(e.pluginConfigPaths??[]).map(t=>n.resolve(e.repoRoot,t))].filter(e=>t.existsSync(e)),o=[],s=[],c=[],l=[];for(let t of i){let n=x(t,e.allowlist?.servers,e.stagingDirectory,{stripProxyWrappers:!0});n&&(o.push(n.source),s.push(...n.kept),c.push(...n.dropped))}for(let t of r??[]){if(t.format===`agent-plugin-v1`){let n=a(t,{stagingDirectory:e.stagingDirectory,allowedServers:e.allowlist?.servers});n.configSource&&o.push(n.configSource),s.push(...n.serverNames),c.push(...n.droppedServers),l.push(...n.diagnostics);continue}if(!S(t)){l.push(`Native MCP source "${t.sourceId}" was disabled: the file is missing or its content digest changed.`);continue}let n=x(t.configPath,e.allowlist?.servers,e.stagingDirectory,{stripProxyWrappers:!0,sourceIdentity:`${t.sourceId}:${t.contentDigest}`});if(!n){l.push(`Native MCP source "${t.sourceId}" was disabled: the config could not be parsed.`);continue}o.push(n.source),s.push(...n.kept),c.push(...n.dropped)}let u=r?.find(e=>e.format===`native`&&e.owner===`repository`),d=r?u&&S(u)?h(e.repoRoot,u.configPath):void 0:g(e.repoRoot),f=d?x(d,e.allowlist?.proxy,e.stagingDirectory,{stripProxyWrappers:!0}):void 0;return f&&c.push(...f.dropped),{shared:{configSources:f?[f.source]:[],configPaths:f?[f.source.path]:[],serverNames:f?f.kept:[]},sessionLocal:{configSources:o,configPaths:o.map(e=>e.path),serverNames:[...new Set(s)]},droppedServers:[...new Set(c)],diagnostics:l}}export{o as PROXY_SERVER_NAME,w as buildMcpConfigGroups,g as resolveSharedConfigPath};
|
|
2
|
+
//# sourceMappingURL=configSources.mjs.map
|
|
@@ -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 { normalizeAgentPluginMcpSource } from '@agimon-ai/doompi-config/agentPluginMcp';\nimport type { DoomMcpNativeProjectionSource } from '@agimon-ai/doompi-extension-contracts/mcp-projection';\nimport type { McpConfigGroups, McpConfigGroupsInput, McpConfigSource } 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 source: McpConfigSource;\n kept: string[];\n dropped: string[];\n}\n\ninterface ConfigDocument {\n config: JsonObject;\n contents: 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 readConfigDocument(filePath: string): ConfigDocument | undefined {\n try {\n const contents = fs.readFileSync(filePath, 'utf8');\n const parsed: unknown = isYamlPath(filePath) ? parseYaml(contents) : JSON.parse(contents);\n return isObject(parsed) ? { config: parsed, contents } : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction readConfigObject(filePath: string): JsonObject | undefined {\n return readConfigDocument(filePath)?.config;\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 */\nfunction resolveSharedConfigPathFrom(repoRoot: string, repositoryConfigPath: string): string | undefined {\n const servers = serverDefinitions(readConfigObject(repositoryConfigPath));\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\nexport function resolveSharedConfigPath(repoRoot: string): string | undefined {\n return resolveSharedConfigPathFrom(repoRoot, path.join(repoRoot, '.mcp.json'));\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\nfunction layerCacheKey(\n filePath: string,\n format: McpConfigSource['format'],\n originalContents: string,\n effectiveContents: string,\n sourceIdentity?: string,\n): string {\n return createHash('sha256')\n .update(format)\n .update('\\0')\n .update(fs.realpathSync(filePath))\n .update('\\0')\n .update(sourceIdentity ?? 'unprojected')\n .update('\\0')\n .update(originalContents)\n .update('\\0')\n .update(effectiveContents)\n .digest('hex');\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; sourceIdentity?: string },\n): FilteredLayer | undefined {\n const document = readConfigDocument(filePath);\n if (!document) return undefined;\n const { config, contents: originalContents } = document;\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) {\n return {\n source: {\n path: filePath,\n format: 'claude',\n cacheKey: layerCacheKey(filePath, 'claude', originalContents, originalContents, options.sourceIdentity),\n },\n kept: keptNames,\n dropped,\n };\n }\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 {\n source: {\n path: target,\n format: 'claude',\n cacheKey: layerCacheKey(filePath, 'claude', originalContents, contents, options.sourceIdentity),\n },\n kept: keptNames,\n dropped,\n };\n}\n\nfunction digestMatches(source: DoomMcpNativeProjectionSource): boolean {\n try {\n const digest = createHash('sha256').update(fs.readFileSync(source.configPath)).digest('hex');\n return digest === source.contentDigest;\n } catch {\n return false;\n }\n}\n\nfunction emptyGroups(diagnostics: string[] = []): McpConfigGroups {\n return {\n shared: { configSources: [], configPaths: [], serverNames: [] },\n sessionLocal: { configSources: [], configPaths: [], serverNames: [] },\n droppedServers: [],\n diagnostics,\n };\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 if (input.enabled === false) return emptyGroups();\n\n const projectedSources = input.sources;\n const legacyLayers = projectedSources\n ? []\n : [\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 configSources: McpConfigSource[] = [];\n const serverNames: string[] = [];\n const droppedServers: string[] = [];\n const diagnostics: string[] = [];\n for (const layer of legacyLayers) {\n const filtered = filterLayer(layer, input.allowlist?.servers, input.stagingDirectory, {\n stripProxyWrappers: true,\n });\n if (!filtered) continue;\n configSources.push(filtered.source);\n serverNames.push(...filtered.kept);\n droppedServers.push(...filtered.dropped);\n }\n\n for (const source of projectedSources ?? []) {\n if (source.format === 'agent-plugin-v1') {\n const normalized = normalizeAgentPluginMcpSource(source, {\n stagingDirectory: input.stagingDirectory,\n allowedServers: input.allowlist?.servers,\n });\n if (normalized.configSource) configSources.push(normalized.configSource);\n serverNames.push(...normalized.serverNames);\n droppedServers.push(...normalized.droppedServers);\n diagnostics.push(...normalized.diagnostics);\n continue;\n }\n\n if (!digestMatches(source)) {\n diagnostics.push(\n `Native MCP source \"${source.sourceId}\" was disabled: the file is missing or its content digest changed.`,\n );\n continue;\n }\n const filtered = filterLayer(source.configPath, input.allowlist?.servers, input.stagingDirectory, {\n stripProxyWrappers: true,\n sourceIdentity: `${source.sourceId}:${source.contentDigest}`,\n });\n if (!filtered) {\n diagnostics.push(`Native MCP source \"${source.sourceId}\" was disabled: the config could not be parsed.`);\n continue;\n }\n configSources.push(filtered.source);\n serverNames.push(...filtered.kept);\n droppedServers.push(...filtered.dropped);\n }\n\n const repositorySource = projectedSources?.find(\n (source): source is DoomMcpNativeProjectionSource => source.format === 'native' && source.owner === 'repository',\n );\n const upstreamPath = projectedSources\n ? repositorySource && digestMatches(repositorySource)\n ? resolveSharedConfigPathFrom(input.repoRoot, repositorySource.configPath)\n : undefined\n : 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 configSources: upstream ? [upstream.source] : [],\n configPaths: upstream ? [upstream.source.path] : [],\n serverNames: upstream ? upstream.kept : [],\n },\n // A later layer overriding an earlier one contributes the same name twice.\n sessionLocal: {\n configSources,\n configPaths: configSources.map((source) => source.path),\n serverNames: [...new Set(serverNames)],\n },\n droppedServers: [...new Set(droppedServers)],\n diagnostics,\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,2 @@
|
|
|
1
|
+
const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:crypto"),n=require("node:fs"),r=require("node:path"),i=require("node:os"),a=require("@agimon-ai/mcp-proxy/package.json");a=e.__toESM(a,1);function o(e){return{redirectUri:()=>e.redirectUri,getPort:()=>void 0,listen:e=>Promise.resolve(e),reserve:(t,n)=>e.reserve(t,n),waitForCallback:(t,n)=>e.wait(t,n),cancel:t=>e.cancel(t)}}function s(e){if(e.length===0)throw Error(`definitionsCachePath requires at least one config source`);let n=(0,t.createHash)(`sha256`).update(JSON.stringify(e.map(e=>({cacheKey:e.cacheKey,format:e.format,optional:e.optional??!1,mergeStrategy:e.mergeStrategy??`remote-priority`})))).update(`\0`).update(a.default.version).digest(`hex`);return(0,r.join)((0,i.homedir)(),`.mcp-proxy`,`doompi-${n}.definitions-cache.json`)}function c(e,t=s){if(e.length===0)return{servers:[]};try{let r=t(e),i=JSON.parse((0,n.readFileSync)(r,`utf8`));return i.oneMcpVersion===a.default.version?{servers:Object.values(i.servers??{}).map(e=>({name:e.serverName,tools:e.tools??[]}))}:{servers:[]}}catch{return{servers:[]}}}function l(e){return{path:e.path,format:e.format,...e.optional===void 0?{}:{optional:e.optional},...e.mergeStrategy===void 0?{}:{mergeStrategy:e.mergeStrategy}}}var u=class{generation=0;active;unsubscribe;get currentGeneration(){return this.generation}getServices(){return this.active}isCurrent(e){return e!==void 0&&e===this.active}async start(e){let t=++this.generation;if(await this.retire(),e.configSources.length===0)return;let{createProxyContainer:n}=await import(`@agimon-ai/mcp-proxy`),r=s(e.configSources),i=await n({configSources:e.configSources.map(l),definitionsCachePath:r,startupMode:`background`,auth:{tokenStore:e.tokenStore,onAuthorizationUrl:e.onAuthorizationUrl,...e.callbackServer?{callbackServer:e.callbackServer}:{}}});if(t!==this.generation){await i.dispose().catch(()=>void 0);return}this.active=i;let a=e.onServerStateChange;return a&&(this.unsubscribe=i.clientManager.onServerStateChange(e=>{t===this.generation&&a(e)})),{generation:t,services:i,connectionsSettled:i.connectionsSettled}}async dispose(){++this.generation,await this.retire()}async retire(){this.unsubscribe?.(),this.unsubscribe=void 0;let e=this.active;this.active=void 0,e&&await e.dispose().catch(()=>void 0)}};exports.McpRuntimeOwner=u,exports.definitionsCachePath=s,exports.hubCallbackSink=o,exports.readCachedCatalog=c;
|
|
2
|
+
//# sourceMappingURL=mcpRuntime.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcpRuntime.cjs","names":["createHash","mcpProxyPackage","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 } from 'node:path';\nimport type {\n ConfigSource,\n McpServerStateChange,\n McpToolInfo,\n OAuthCallbackSink,\n SharedServices,\n TokenStore,\n} from '@agimon-ai/mcp-proxy';\nimport type { DoomOAuthRedirect } from '@agimon-ai/doompi-extension-contracts/package-api';\nimport mcpProxyPackage from '@agimon-ai/mcp-proxy/package.json' with { type: 'json' };\nimport type { McpConfigSource } from '../../types/mcpConfig.ts';\nimport type { CachedCatalog } from '../../types/mcpRuntime.ts';\n\n/**\n * Presents the hub's redirect surface as the proxy's callback collaborator.\n *\n * `listen` binds nothing: the hub already serves the address this advertises,\n * so opening a loopback port would only create one nobody redirects to.\n */\nexport function hubCallbackSink(redirect: DoomOAuthRedirect): OAuthCallbackSink {\n return {\n redirectUri: () => redirect.redirectUri,\n getPort: () => undefined,\n listen: (preferredPort) => Promise.resolve(preferredPort),\n reserve: (state, timeoutMs) => redirect.reserve(state, timeoutMs),\n waitForCallback: (state, timeoutMs) => redirect.wait(state, timeoutMs),\n cancel: (state) => redirect.cancel(state),\n };\n}\n\nexport interface McpRuntimeOptions {\n /** Ordered config layers. Later layers override earlier ones. */\n configSources: McpConfigSource[];\n tokenStore?: TokenStore;\n /**\n * Where OAuth redirects are received. Omitted, the proxy binds its own\n * loopback listener, which only a browser on this machine can reach.\n */\n callbackServer?: OAuthCallbackSink;\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 oneMcpVersion?: string;\n servers?: Record<string, { serverName: string; tools?: McpToolInfo[] }>;\n}\n\n/**\n * Definitions cache path shared by Doom's synchronous warm reader and mcp-proxy.\n *\n * Generated staging paths are deliberately absent: projected source content must\n * invalidate a reused path, while identical portable content must survive a new\n * private staging directory. Stable source origins are carried inside each cache\n * key; format and merge behavior remain part of the ordered identity.\n */\nexport function definitionsCachePath(configSources: readonly McpConfigSource[]): string {\n if (configSources.length === 0) throw new Error('definitionsCachePath requires at least one config source');\n const digest = createHash('sha256')\n .update(\n JSON.stringify(\n configSources.map((source) => ({\n cacheKey: source.cacheKey,\n format: source.format,\n optional: source.optional ?? false,\n mergeStrategy: source.mergeStrategy ?? 'remote-priority',\n })),\n ),\n )\n .update('\\0')\n .update(mcpProxyPackage.version)\n .digest('hex');\n return join(homedir(), '.mcp-proxy', `doompi-${digest}.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 file path is content-addressed from the current ordered layers, and its proxy\n * version is checked before any schema is registered. Servers filtered out of the\n * current configuration are also excluded later by `McpCatalog.seed`.\n */\nexport function readCachedCatalog(\n configSources: readonly McpConfigSource[],\n cachePathFor: (sources: readonly McpConfigSource[]) => string = definitionsCachePath,\n): CachedCatalog {\n if (configSources.length === 0) return { servers: [] };\n try {\n const cachePath = cachePathFor(configSources);\n const cache = JSON.parse(readFileSync(cachePath, 'utf8')) as Partial<DefinitionsCacheFile>;\n if (cache.oneMcpVersion !== mcpProxyPackage.version) return { servers: [] };\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\nfunction toProxyConfigSource(source: McpConfigSource): ConfigSource {\n return {\n path: source.path,\n format: source.format,\n ...(source.optional === undefined ? {} : { optional: source.optional }),\n ...(source.mergeStrategy === undefined ? {} : { mergeStrategy: source.mergeStrategy }),\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.configSources.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 cachePath = definitionsCachePath(options.configSources);\n const services = await createProxyContainer({\n configSources: options.configSources.map(toProxyConfigSource),\n definitionsCachePath: cachePath,\n // Never blocking: an unreachable server must not delay Pi's first prompt.\n startupMode: 'background',\n auth: {\n tokenStore: options.tokenStore,\n onAuthorizationUrl: options.onAuthorizationUrl,\n ...(options.callbackServer ? { callbackServer: options.callbackServer } : {}),\n },\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":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { McpConfigSource } from "../../types/mcpConfig.cjs";
|
|
2
|
+
import { CachedCatalog } from "../../types/mcpRuntime.cjs";
|
|
3
|
+
import "@agimon-ai/mcp-proxy";
|
|
4
|
+
import "@agimon-ai/doompi-extension-contracts/package-api";
|
|
5
|
+
//#region src/adapters/node/mcpRuntime.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Definitions cache path shared by Doom's synchronous warm reader and mcp-proxy.
|
|
8
|
+
*
|
|
9
|
+
* Generated staging paths are deliberately absent: projected source content must
|
|
10
|
+
* invalidate a reused path, while identical portable content must survive a new
|
|
11
|
+
* private staging directory. Stable source origins are carried inside each cache
|
|
12
|
+
* key; format and merge behavior remain part of the ordered identity.
|
|
13
|
+
*/
|
|
14
|
+
export declare function definitionsCachePath(configSources: readonly McpConfigSource[]): string;
|
|
15
|
+
/**
|
|
16
|
+
* Reads the catalog the previous run left behind.
|
|
17
|
+
*
|
|
18
|
+
* Synchronous on purpose: Pi has to be able to register these tools during
|
|
19
|
+
* extension install, before it takes its first turn, and awaiting a file read
|
|
20
|
+
* would let that turn start with an empty tool set. A miss is normal on a first
|
|
21
|
+
* run and resolves to an empty catalog rather than an error.
|
|
22
|
+
*
|
|
23
|
+
* The file path is content-addressed from the current ordered layers, and its proxy
|
|
24
|
+
* version is checked before any schema is registered. Servers filtered out of the
|
|
25
|
+
* current configuration are also excluded later by `McpCatalog.seed`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function readCachedCatalog(configSources: readonly McpConfigSource[], cachePathFor?: (sources: readonly McpConfigSource[]) => string): CachedCatalog;
|
|
28
|
+
//#endregion
|
|
29
|
+
//# sourceMappingURL=mcpRuntime.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcpRuntime.d.cts","names":[],"sources":["../../../src/adapters/node/mcpRuntime.ts"],"mappings":";;;;;;;;;;;;;wBAmEgB,qBAAqB,wBAAwB;;;;;;;;;;;;;wBA+B7C,kBACd,wBAAwB,mBACxB,gBAAe,kBAAkB,+BAChC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { McpConfigSource } from "../../types/mcpConfig.mjs";
|
|
2
|
+
import { CachedCatalog } from "../../types/mcpRuntime.mjs";
|
|
3
|
+
import "@agimon-ai/mcp-proxy";
|
|
4
|
+
import "@agimon-ai/doompi-extension-contracts/package-api";
|
|
5
|
+
//#region src/adapters/node/mcpRuntime.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Definitions cache path shared by Doom's synchronous warm reader and mcp-proxy.
|
|
8
|
+
*
|
|
9
|
+
* Generated staging paths are deliberately absent: projected source content must
|
|
10
|
+
* invalidate a reused path, while identical portable content must survive a new
|
|
11
|
+
* private staging directory. Stable source origins are carried inside each cache
|
|
12
|
+
* key; format and merge behavior remain part of the ordered identity.
|
|
13
|
+
*/
|
|
14
|
+
export declare function definitionsCachePath(configSources: readonly McpConfigSource[]): string;
|
|
15
|
+
/**
|
|
16
|
+
* Reads the catalog the previous run left behind.
|
|
17
|
+
*
|
|
18
|
+
* Synchronous on purpose: Pi has to be able to register these tools during
|
|
19
|
+
* extension install, before it takes its first turn, and awaiting a file read
|
|
20
|
+
* would let that turn start with an empty tool set. A miss is normal on a first
|
|
21
|
+
* run and resolves to an empty catalog rather than an error.
|
|
22
|
+
*
|
|
23
|
+
* The file path is content-addressed from the current ordered layers, and its proxy
|
|
24
|
+
* version is checked before any schema is registered. Servers filtered out of the
|
|
25
|
+
* current configuration are also excluded later by `McpCatalog.seed`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function readCachedCatalog(configSources: readonly McpConfigSource[], cachePathFor?: (sources: readonly McpConfigSource[]) => string): CachedCatalog;
|
|
28
|
+
//#endregion
|
|
29
|
+
//# sourceMappingURL=mcpRuntime.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcpRuntime.d.mts","names":[],"sources":["../../../src/adapters/node/mcpRuntime.ts"],"mappings":";;;;;;;;;;;;;wBAmEgB,qBAAqB,wBAAwB;;;;;;;;;;;;;wBA+B7C,kBACd,wBAAwB,mBACxB,gBAAe,kBAAkB,+BAChC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{createHash as e}from"node:crypto";import{readFileSync as t}from"node:fs";import{join as n}from"node:path";import{homedir as r}from"node:os";import i from"@agimon-ai/mcp-proxy/package.json"with{type:"json"};function a(e){return{redirectUri:()=>e.redirectUri,getPort:()=>void 0,listen:e=>Promise.resolve(e),reserve:(t,n)=>e.reserve(t,n),waitForCallback:(t,n)=>e.wait(t,n),cancel:t=>e.cancel(t)}}function o(t){if(t.length===0)throw Error(`definitionsCachePath requires at least one config source`);let a=e(`sha256`).update(JSON.stringify(t.map(e=>({cacheKey:e.cacheKey,format:e.format,optional:e.optional??!1,mergeStrategy:e.mergeStrategy??`remote-priority`})))).update(`\0`).update(i.version).digest(`hex`);return n(r(),`.mcp-proxy`,`doompi-${a}.definitions-cache.json`)}function s(e,n=o){if(e.length===0)return{servers:[]};try{let r=n(e),a=JSON.parse(t(r,`utf8`));return a.oneMcpVersion===i.version?{servers:Object.values(a.servers??{}).map(e=>({name:e.serverName,tools:e.tools??[]}))}:{servers:[]}}catch{return{servers:[]}}}function c(e){return{path:e.path,format:e.format,...e.optional===void 0?{}:{optional:e.optional},...e.mergeStrategy===void 0?{}:{mergeStrategy:e.mergeStrategy}}}var l=class{generation=0;active;unsubscribe;get currentGeneration(){return this.generation}getServices(){return this.active}isCurrent(e){return e!==void 0&&e===this.active}async start(e){let t=++this.generation;if(await this.retire(),e.configSources.length===0)return;let{createProxyContainer:n}=await import(`@agimon-ai/mcp-proxy`),r=o(e.configSources),i=await n({configSources:e.configSources.map(c),definitionsCachePath:r,startupMode:`background`,auth:{tokenStore:e.tokenStore,onAuthorizationUrl:e.onAuthorizationUrl,...e.callbackServer?{callbackServer:e.callbackServer}:{}}});if(t!==this.generation){await i.dispose().catch(()=>void 0);return}this.active=i;let a=e.onServerStateChange;return a&&(this.unsubscribe=i.clientManager.onServerStateChange(e=>{t===this.generation&&a(e)})),{generation:t,services:i,connectionsSettled:i.connectionsSettled}}async dispose(){++this.generation,await this.retire()}async retire(){this.unsubscribe?.(),this.unsubscribe=void 0;let e=this.active;this.active=void 0,e&&await e.dispose().catch(()=>void 0)}};export{l as McpRuntimeOwner,o as definitionsCachePath,a as hubCallbackSink,s as readCachedCatalog};
|
|
2
|
+
//# sourceMappingURL=mcpRuntime.mjs.map
|
|
@@ -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 } from 'node:path';\nimport type {\n ConfigSource,\n McpServerStateChange,\n McpToolInfo,\n OAuthCallbackSink,\n SharedServices,\n TokenStore,\n} from '@agimon-ai/mcp-proxy';\nimport type { DoomOAuthRedirect } from '@agimon-ai/doompi-extension-contracts/package-api';\nimport mcpProxyPackage from '@agimon-ai/mcp-proxy/package.json' with { type: 'json' };\nimport type { McpConfigSource } from '../../types/mcpConfig.ts';\nimport type { CachedCatalog } from '../../types/mcpRuntime.ts';\n\n/**\n * Presents the hub's redirect surface as the proxy's callback collaborator.\n *\n * `listen` binds nothing: the hub already serves the address this advertises,\n * so opening a loopback port would only create one nobody redirects to.\n */\nexport function hubCallbackSink(redirect: DoomOAuthRedirect): OAuthCallbackSink {\n return {\n redirectUri: () => redirect.redirectUri,\n getPort: () => undefined,\n listen: (preferredPort) => Promise.resolve(preferredPort),\n reserve: (state, timeoutMs) => redirect.reserve(state, timeoutMs),\n waitForCallback: (state, timeoutMs) => redirect.wait(state, timeoutMs),\n cancel: (state) => redirect.cancel(state),\n };\n}\n\nexport interface McpRuntimeOptions {\n /** Ordered config layers. Later layers override earlier ones. */\n configSources: McpConfigSource[];\n tokenStore?: TokenStore;\n /**\n * Where OAuth redirects are received. Omitted, the proxy binds its own\n * loopback listener, which only a browser on this machine can reach.\n */\n callbackServer?: OAuthCallbackSink;\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 oneMcpVersion?: string;\n servers?: Record<string, { serverName: string; tools?: McpToolInfo[] }>;\n}\n\n/**\n * Definitions cache path shared by Doom's synchronous warm reader and mcp-proxy.\n *\n * Generated staging paths are deliberately absent: projected source content must\n * invalidate a reused path, while identical portable content must survive a new\n * private staging directory. Stable source origins are carried inside each cache\n * key; format and merge behavior remain part of the ordered identity.\n */\nexport function definitionsCachePath(configSources: readonly McpConfigSource[]): string {\n if (configSources.length === 0) throw new Error('definitionsCachePath requires at least one config source');\n const digest = createHash('sha256')\n .update(\n JSON.stringify(\n configSources.map((source) => ({\n cacheKey: source.cacheKey,\n format: source.format,\n optional: source.optional ?? false,\n mergeStrategy: source.mergeStrategy ?? 'remote-priority',\n })),\n ),\n )\n .update('\\0')\n .update(mcpProxyPackage.version)\n .digest('hex');\n return join(homedir(), '.mcp-proxy', `doompi-${digest}.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 file path is content-addressed from the current ordered layers, and its proxy\n * version is checked before any schema is registered. Servers filtered out of the\n * current configuration are also excluded later by `McpCatalog.seed`.\n */\nexport function readCachedCatalog(\n configSources: readonly McpConfigSource[],\n cachePathFor: (sources: readonly McpConfigSource[]) => string = definitionsCachePath,\n): CachedCatalog {\n if (configSources.length === 0) return { servers: [] };\n try {\n const cachePath = cachePathFor(configSources);\n const cache = JSON.parse(readFileSync(cachePath, 'utf8')) as Partial<DefinitionsCacheFile>;\n if (cache.oneMcpVersion !== mcpProxyPackage.version) return { servers: [] };\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\nfunction toProxyConfigSource(source: McpConfigSource): ConfigSource {\n return {\n path: source.path,\n format: source.format,\n ...(source.optional === undefined ? {} : { optional: source.optional }),\n ...(source.mergeStrategy === undefined ? {} : { mergeStrategy: source.mergeStrategy }),\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.configSources.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 cachePath = definitionsCachePath(options.configSources);\n const services = await createProxyContainer({\n configSources: options.configSources.map(toProxyConfigSource),\n definitionsCachePath: cachePath,\n // Never blocking: an unreachable server must not delay Pi's first prompt.\n startupMode: 'background',\n auth: {\n tokenStore: options.tokenStore,\n onAuthorizationUrl: options.onAuthorizationUrl,\n ...(options.callbackServer ? { callbackServer: options.callbackServer } : {}),\n },\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":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./configSources.cjs"),n=require("./mcpRuntime.cjs"),r=require("./projection.cjs"),i=require("../../services/mcpCatalog.cjs"),a=require("./keyringTokenStore.cjs");let o=require("node:crypto"),s=require("node:fs");s=e.__toESM(s,1);let c=require("node:path");c=e.__toESM(c,1);let l=require("node:os");l=e.__toESM(l,1);const u={fresh:!1,reasons:[`never-synced`]};function d(e){return[...new Set([...e.sessionLocal.serverNames,...e.shared.serverNames])].sort((e,t)=>e.localeCompare(t))}function f(e){return[...e.sessionLocal.configSources,...e.shared.configSources]}function p(e,t){return{name:t.name,piName:i.toPiToolName(e,t.name),...t.description?{description:t.description}:{}}}function m(e,t,n){let r=e;for(let e of[n,t,l.default.homedir()])e&&(r=r.split(e).join(e===l.default.homedir()?`~`:`<repository>`));return r.replace(/[A-Za-z]:\\[^\s,;)'"`]+/gu,`<path>`).replace(/(^|\s)\/(?!\/)[^\s,;)'"`]+/gu,`$1<path>`)}function h(e){return{id:e.id,repositoryId:e.repositoryId,serverName:e.serverName,status:e.status,...e.authorizationUrl?{authorizationUrl:e.authorizationUrl}:{},...e.error?{error:e.error}:{},expiresAt:e.expiresAt}}function g(e){return e===`completed`||e===`failed`||e===`cancelled`||e===`expired`}function _(e){if(!(e.protocol!==`https:`&&e.protocol!==`http:`||e.toString().length>4096))return e.toString()}var v=class{discoveryTimeoutMs;authorizationTimeoutMs;injectedTokenStore;busyRepositories=new Set;flows=new Map;constructor(e={}){this.discoveryTimeoutMs=e.discoveryTimeoutMs??3e4,this.authorizationTimeoutMs=e.authorizationTimeoutMs??3e5,this.injectedTokenStore=e.tokenStore}async tokenStore(){return this.injectedTokenStore??await a.createTokenStore()}async credentialPresence(e){let t=new Map;try{let n=await this.tokenStore();await Promise.all(e.map(async e=>{try{t.set(e,await n.read(e)!==void 0)}catch{t.set(e,!1)}}))}catch{for(let n of e)t.set(n,!1)}return t}prepare(e,i,a,o){if(!a?.mcpProjection){let t=a??u;return{catalog:{repositoryId:e,sync:{fresh:t.fresh,reasons:[...t.reasons]},servers:[],droppedServers:[],diagnostics:[]},configSources:[],groups:{shared:{configSources:[],configPaths:[],serverNames:[]},sessionLocal:{configSources:[],configPaths:[],serverNames:[]},droppedServers:[],diagnostics:[]}}}let h=s.default.mkdtempSync(c.default.join(l.default.tmpdir(),`doompi-mcp-settings-`));s.default.chmodSync(h,448);try{let s={...r.mcpSessionConfigFromProjection(a.mcpProjection),repoRoot:i,stagingDirectory:h,...o?{allowlist:{servers:[o],proxy:[]}}:{}},c=t.buildMcpConfigGroups(s),l=f(c),u=d(c),g=new Map(n.readCachedCatalog(l).servers.map(e=>[e.name,e.tools])),_=u.map(e=>{let t=g.get(e)??[];return{name:e,state:`not-connected`,source:t.length===0?`configured`:`cached`,credentialPresent:!1,tools:t.map(t=>p(e,t))}});return{catalog:{repositoryId:e,sync:{fresh:a.fresh,reasons:[...a.reasons]},servers:_,droppedServers:[...c.droppedServers],diagnostics:c.diagnostics.map(e=>m(e,i,h))},configSources:l,groups:c,stagingDirectory:h}}catch(e){throw s.default.rmSync(h,{recursive:!0,force:!0}),e}}cleanupPrepared(e){e.stagingDirectory&&s.default.rmSync(e.stagingDirectory,{recursive:!0,force:!0})}async readCatalog(e,t,n){let r=this.prepare(e,t,n);try{let e=await this.credentialPresence(r.catalog.servers.map(e=>e.name));return{...r.catalog,servers:r.catalog.servers.map(t=>({...t,credentialPresent:e.get(t.name)??!1}))}}finally{this.cleanupPrepared(r)}}async discover(e,t,r){if(this.busyRepositories.has(e))throw Error(`MCP management is already active for this repository.`);let i=this.prepare(e,t,r);if(!i.catalog.sync.fresh)throw this.cleanupPrepared(i),Error(`Sync this repository before discovering MCP capabilities.`);if(i.configSources.length===0)return this.cleanupPrepared(i),i.catalog;this.busyRepositories.add(e);let a=new n.McpRuntimeOwner,o=new Map;try{let e=await a.start({configSources:i.configSources,tokenStore:await this.tokenStore(),onAuthorizationUrl:(e,t)=>{o.set(t,{serverName:t,state:`needs-auth`})},onServerStateChange:e=>o.set(e.serverName,e)});e&&await Promise.race([e.connectionsSettled,new Promise(e=>setTimeout(e,this.discoveryTimeoutMs))]);let t=e?.services.clientManager,r=new Map(n.readCachedCatalog(i.configSources).servers.map(e=>[e.name,e.tools])),s=await this.credentialPresence(i.catalog.servers.map(e=>e.name)),c=await Promise.all(i.catalog.servers.map(async e=>{let n=o.get(e.name),i=n?.state??t?.getServerState(e.name)??e.state,a=r.get(e.name)??[],c=a.length===0?`configured`:`cached`;if(i===`connected`&&t)try{a=await t.ensureConnected(e.name).then(async e=>await e.listTools()),c=`live`}catch{}return{name:e.name,state:i,source:c,credentialPresent:s.get(e.name)??!1,tools:a.map(t=>p(e.name,t)),...n?.error?{error:`The server reported a connection error.`}:{}}}));return{...i.catalog,servers:c}}finally{await a.dispose(),this.cleanupPrepared(i),this.busyRepositories.delete(e)}}async authorize(e,t,n,r,i){if(this.sweepFlows(),this.busyRepositories.has(e))throw Error(`MCP management is already active for this repository.`);let a=this.prepare(e,t,n),s=a.catalog.servers.some(e=>e.name===r),c=a.catalog.sync.fresh;if(this.cleanupPrepared(a),!c)throw Error(`Sync this repository before authorizing an MCP server.`);if(!s)throw Error(`That MCP server is not enabled for this repository.`);let l=this.prepare(e,t,n,r),u={id:(0,o.randomUUID)(),repositoryId:e,serverName:r,status:`starting`,expiresAt:Date.now()+this.authorizationTimeoutMs,stagingDirectory:l.stagingDirectory};return this.flows.set(u.id,u),this.busyRepositories.add(e),this.runAuthorization(u,l.configSources,i),h(u)}getAuthorization(e,t){this.sweepFlows();let n=this.flows.get(e);return n?.repositoryId===t?h(n):void 0}async cancelAuthorization(e,t){let n=this.flows.get(e);if(n&&n.repositoryId===t)return g(n.status)||(n.status=`cancelled`,await this.finishFlow(n)),h(n)}async runAuthorization(e,t,r){let i=new n.McpRuntimeOwner;e.owner=i,e.timeout=setTimeout(()=>{g(e.status)||(e.status=`expired`,e.error=`Authorization timed out.`,this.finishFlow(e))},this.authorizationTimeoutMs);try{let a=await i.start({configSources:t,tokenStore:await this.tokenStore(),...r?{callbackServer:n.hubCallbackSink(r)}:{},onAuthorizationUrl:(t,n)=>{if(n!==e.serverName||g(e.status))return;let r=_(t);if(!r){e.status=`failed`,e.error=`The server returned an invalid authorization URL.`,this.finishFlow(e);return}e.authorizationUrl=r,e.status=`waiting`},onServerStateChange:t=>this.applyAuthorizationState(e,t)});if(!a||g(e.status))return;let o=a.services.clientManager;await o.disconnectServer(e.serverName),await o.ensureConnected(e.serverName),g(e.status)||(e.status=`completed`)}catch{g(e.status)||(e.status=`failed`,e.error=`The MCP server could not complete authorization.`)}finally{g(e.status)||(e.status=`completed`),await this.finishFlow(e)}}applyAuthorizationState(e,t){t.serverName!==e.serverName||g(e.status)||(t.state===`connected`?e.status=`completed`:t.state===`needs-auth`&&e.status===`starting`?e.status=`waiting`:t.state===`failed`&&(e.status=`failed`,e.error=`The MCP server rejected the authorization attempt.`))}async finishFlow(e){e.timeout&&clearTimeout(e.timeout),e.timeout=void 0;let t=e.owner;e.owner=void 0,await t?.dispose(),e.stagingDirectory&&s.default.rmSync(e.stagingDirectory,{recursive:!0,force:!0}),e.stagingDirectory=void 0,this.busyRepositories.delete(e.repositoryId),e.retainedUntil=Date.now()+3e5}sweepFlows(){let e=Date.now();for(let[t,n]of this.flows)n.retainedUntil!==void 0&&n.retainedUntil<=e&&this.flows.delete(t)}async dispose(){await Promise.all([...this.flows.values()].map(async e=>{g(e.status)||(e.status=`cancelled`),await this.finishFlow(e)})),this.flows.clear(),this.busyRepositories.clear()}};exports.McpSettingsManager=v;
|
|
2
|
+
//# sourceMappingURL=mcpSettingsManager.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcpSettingsManager.cjs","names":["toPiToolName","os","createTokenStore","fs","path","mcpSessionConfigFromProjection","buildMcpConfigGroups","readCachedCatalog","McpRuntimeOwner","randomUUID","hubCallbackSink"],"sources":["../../../src/adapters/node/mcpSettingsManager.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport type { DoomOAuthRedirect, DoomRepositorySyncView } from '@agimon-ai/doompi-extension-contracts/package-api';\nimport type { McpServerStateChange, McpToolInfo, TokenStore } from '@agimon-ai/mcp-proxy';\nimport type {\n McpAuthorizationFlow,\n McpRepositoryCatalog,\n McpRepositoryServer,\n McpRepositoryServerState,\n McpRepositoryTool,\n} from '../../types/webMcp.ts';\nimport type { McpConfigGroups, McpConfigSource, McpSessionConfig } from '../../types/mcpConfig.ts';\nimport { toPiToolName } from '../../services/mcpCatalog.ts';\nimport { buildMcpConfigGroups } from './configSources.ts';\nimport { createTokenStore } from './keyringTokenStore.ts';\nimport { hubCallbackSink, McpRuntimeOwner, readCachedCatalog } from './mcpRuntime.ts';\nimport { mcpSessionConfigFromProjection } from './projection.ts';\n\nconst DISCOVERY_TIMEOUT_MS = 30_000;\nconst AUTHORIZATION_TIMEOUT_MS = 5 * 60_000;\nconst TERMINAL_FLOW_RETENTION_MS = 5 * 60_000;\nconst EMPTY_SYNC = { fresh: false, reasons: ['never-synced'] } as const;\n\ninterface PreparedMcpRepository {\n catalog: McpRepositoryCatalog;\n configSources: McpConfigSource[];\n groups: McpConfigGroups;\n stagingDirectory?: string;\n}\n\ninterface InternalAuthorizationFlow extends McpAuthorizationFlow {\n owner?: McpRuntimeOwner;\n stagingDirectory?: string;\n timeout?: ReturnType<typeof setTimeout>;\n retainedUntil?: number;\n}\n\nexport interface McpSettingsManagerOptions {\n discoveryTimeoutMs?: number;\n authorizationTimeoutMs?: number;\n tokenStore?: TokenStore;\n}\n\nfunction configuredServerNames(groups: McpConfigGroups): string[] {\n return [...new Set([...groups.sessionLocal.serverNames, ...groups.shared.serverNames])].sort((left, right) =>\n left.localeCompare(right),\n );\n}\n\nfunction configSources(groups: McpConfigGroups): McpConfigSource[] {\n return [...groups.sessionLocal.configSources, ...groups.shared.configSources];\n}\n\nfunction toolView(serverName: string, tool: McpToolInfo): McpRepositoryTool {\n return {\n name: tool.name,\n piName: toPiToolName(serverName, tool.name),\n ...(tool.description ? { description: tool.description } : {}),\n };\n}\n\nfunction sanitizeDiagnostic(message: string, repositoryRoot: string, stagingDirectory: string | undefined): string {\n let safe = message;\n for (const value of [stagingDirectory, repositoryRoot, os.homedir()]) {\n if (value) safe = safe.split(value).join(value === os.homedir() ? '~' : '<repository>');\n }\n return safe.replace(/[A-Za-z]:\\\\[^\\s,;)'\"`]+/gu, '<path>').replace(/(^|\\s)\\/(?!\\/)[^\\s,;)'\"`]+/gu, '$1<path>');\n}\n\nfunction publicFlow(flow: InternalAuthorizationFlow): McpAuthorizationFlow {\n return {\n id: flow.id,\n repositoryId: flow.repositoryId,\n serverName: flow.serverName,\n status: flow.status,\n ...(flow.authorizationUrl ? { authorizationUrl: flow.authorizationUrl } : {}),\n ...(flow.error ? { error: flow.error } : {}),\n expiresAt: flow.expiresAt,\n };\n}\n\nfunction isTerminal(status: McpAuthorizationFlow['status']): boolean {\n return status === 'completed' || status === 'failed' || status === 'cancelled' || status === 'expired';\n}\n\nfunction safeAuthorizationUrl(url: URL): string | undefined {\n if ((url.protocol !== 'https:' && url.protocol !== 'http:') || url.toString().length > 4096) return undefined;\n return url.toString();\n}\n\nexport class McpSettingsManager {\n private readonly discoveryTimeoutMs: number;\n private readonly authorizationTimeoutMs: number;\n private readonly injectedTokenStore?: TokenStore;\n private readonly busyRepositories = new Set<string>();\n private readonly flows = new Map<string, InternalAuthorizationFlow>();\n\n constructor(options: McpSettingsManagerOptions = {}) {\n this.discoveryTimeoutMs = options.discoveryTimeoutMs ?? DISCOVERY_TIMEOUT_MS;\n this.authorizationTimeoutMs = options.authorizationTimeoutMs ?? AUTHORIZATION_TIMEOUT_MS;\n this.injectedTokenStore = options.tokenStore;\n }\n\n private async tokenStore(): Promise<TokenStore> {\n return this.injectedTokenStore ?? (await createTokenStore());\n }\n\n private async credentialPresence(serverNames: readonly string[]): Promise<Map<string, boolean>> {\n const presence = new Map<string, boolean>();\n try {\n const tokenStore = await this.tokenStore();\n await Promise.all(\n serverNames.map(async (serverName) => {\n try {\n presence.set(serverName, (await tokenStore.read(serverName)) !== undefined);\n } catch {\n presence.set(serverName, false);\n }\n }),\n );\n } catch {\n for (const serverName of serverNames) presence.set(serverName, false);\n }\n return presence;\n }\n\n private prepare(\n repositoryId: string,\n repositoryRoot: string,\n sync: DoomRepositorySyncView | undefined,\n onlyServer?: string,\n ): PreparedMcpRepository {\n if (!sync?.mcpProjection) {\n const readiness = sync ?? EMPTY_SYNC;\n return {\n catalog: {\n repositoryId,\n sync: { fresh: readiness.fresh, reasons: [...readiness.reasons] },\n servers: [],\n droppedServers: [],\n diagnostics: [],\n },\n configSources: [],\n groups: {\n shared: { configSources: [], configPaths: [], serverNames: [] },\n sessionLocal: { configSources: [], configPaths: [], serverNames: [] },\n droppedServers: [],\n diagnostics: [],\n },\n };\n }\n\n const stagingDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'doompi-mcp-settings-'));\n fs.chmodSync(stagingDirectory, 0o700);\n try {\n const projected = mcpSessionConfigFromProjection(sync.mcpProjection);\n const configuration: McpSessionConfig = {\n ...projected,\n repoRoot: repositoryRoot,\n stagingDirectory,\n ...(onlyServer ? { allowlist: { servers: [onlyServer], proxy: [] } } : {}),\n };\n const groups = buildMcpConfigGroups(configuration);\n const sources = configSources(groups);\n const names = configuredServerNames(groups);\n const cachedByServer = new Map(readCachedCatalog(sources).servers.map((server) => [server.name, server.tools]));\n const servers: McpRepositoryServer[] = names.map((name) => {\n const tools = cachedByServer.get(name) ?? [];\n return {\n name,\n state: 'not-connected',\n source: tools.length === 0 ? 'configured' : 'cached',\n credentialPresent: false,\n tools: tools.map((tool) => toolView(name, tool)),\n };\n });\n return {\n catalog: {\n repositoryId,\n sync: { fresh: sync.fresh, reasons: [...sync.reasons] },\n servers,\n droppedServers: [...groups.droppedServers],\n diagnostics: groups.diagnostics.map((diagnostic) =>\n sanitizeDiagnostic(diagnostic, repositoryRoot, stagingDirectory),\n ),\n },\n configSources: sources,\n groups,\n stagingDirectory,\n };\n } catch (error) {\n fs.rmSync(stagingDirectory, { recursive: true, force: true });\n throw error;\n }\n }\n\n private cleanupPrepared(prepared: PreparedMcpRepository): void {\n if (prepared.stagingDirectory) fs.rmSync(prepared.stagingDirectory, { recursive: true, force: true });\n }\n\n async readCatalog(\n repositoryId: string,\n repositoryRoot: string,\n sync: DoomRepositorySyncView | undefined,\n ): Promise<McpRepositoryCatalog> {\n const prepared = this.prepare(repositoryId, repositoryRoot, sync);\n try {\n const presence = await this.credentialPresence(prepared.catalog.servers.map((server) => server.name));\n return {\n ...prepared.catalog,\n servers: prepared.catalog.servers.map((server) => ({\n ...server,\n credentialPresent: presence.get(server.name) ?? false,\n })),\n };\n } finally {\n this.cleanupPrepared(prepared);\n }\n }\n\n async discover(\n repositoryId: string,\n repositoryRoot: string,\n sync: DoomRepositorySyncView | undefined,\n ): Promise<McpRepositoryCatalog> {\n if (this.busyRepositories.has(repositoryId))\n throw new Error('MCP management is already active for this repository.');\n const prepared = this.prepare(repositoryId, repositoryRoot, sync);\n if (!prepared.catalog.sync.fresh) {\n this.cleanupPrepared(prepared);\n throw new Error('Sync this repository before discovering MCP capabilities.');\n }\n if (prepared.configSources.length === 0) {\n this.cleanupPrepared(prepared);\n return prepared.catalog;\n }\n\n this.busyRepositories.add(repositoryId);\n const owner = new McpRuntimeOwner();\n const liveStates = new Map<string, McpServerStateChange>();\n try {\n const handle = await owner.start({\n configSources: prepared.configSources,\n tokenStore: await this.tokenStore(),\n onAuthorizationUrl: (_url, serverName) => {\n liveStates.set(serverName, { serverName, state: 'needs-auth' });\n },\n onServerStateChange: (change) => liveStates.set(change.serverName, change),\n });\n if (handle) {\n await Promise.race([\n handle.connectionsSettled,\n new Promise<void>((resolve) => setTimeout(resolve, this.discoveryTimeoutMs)),\n ]);\n }\n const manager = handle?.services.clientManager;\n const cachedByServer = new Map(\n readCachedCatalog(prepared.configSources).servers.map((server) => [server.name, server.tools]),\n );\n const presence = await this.credentialPresence(prepared.catalog.servers.map((server) => server.name));\n const servers = await Promise.all(\n prepared.catalog.servers.map(async (server): Promise<McpRepositoryServer> => {\n const change = liveStates.get(server.name);\n const state = (change?.state ??\n manager?.getServerState(server.name) ??\n server.state) as McpRepositoryServerState;\n let tools = cachedByServer.get(server.name) ?? [];\n let source: McpRepositoryServer['source'] = tools.length === 0 ? 'configured' : 'cached';\n if (state === 'connected' && manager) {\n try {\n tools = await manager\n .ensureConnected(server.name)\n .then(async (connection) => await connection.listTools());\n source = 'live';\n } catch {\n // Preserve the cache when a server moved between state inspection and listing.\n }\n }\n return {\n name: server.name,\n state,\n source,\n credentialPresent: presence.get(server.name) ?? false,\n tools: tools.map((tool) => toolView(server.name, tool)),\n ...(change?.error ? { error: 'The server reported a connection error.' } : {}),\n };\n }),\n );\n return { ...prepared.catalog, servers };\n } finally {\n await owner.dispose();\n this.cleanupPrepared(prepared);\n this.busyRepositories.delete(repositoryId);\n }\n }\n\n async authorize(\n repositoryId: string,\n repositoryRoot: string,\n sync: DoomRepositorySyncView | undefined,\n serverName: string,\n /**\n * The hub's redirect surface. Present, the authorization server sends the\n * browser back to the hub, which is reachable whether the operator is at\n * this machine or on a tunnel. Absent, the proxy keeps its loopback\n * listener and behaviour is unchanged.\n */\n redirect?: DoomOAuthRedirect,\n ): Promise<McpAuthorizationFlow> {\n this.sweepFlows();\n if (this.busyRepositories.has(repositoryId))\n throw new Error('MCP management is already active for this repository.');\n\n const full = this.prepare(repositoryId, repositoryRoot, sync);\n const configured = full.catalog.servers.some((server) => server.name === serverName);\n const fresh = full.catalog.sync.fresh;\n this.cleanupPrepared(full);\n if (!fresh) throw new Error('Sync this repository before authorizing an MCP server.');\n if (!configured) throw new Error('That MCP server is not enabled for this repository.');\n\n const prepared = this.prepare(repositoryId, repositoryRoot, sync, serverName);\n const flow: InternalAuthorizationFlow = {\n id: randomUUID(),\n repositoryId,\n serverName,\n status: 'starting',\n expiresAt: Date.now() + this.authorizationTimeoutMs,\n stagingDirectory: prepared.stagingDirectory,\n };\n this.flows.set(flow.id, flow);\n this.busyRepositories.add(repositoryId);\n void this.runAuthorization(flow, prepared.configSources, redirect);\n return publicFlow(flow);\n }\n\n getAuthorization(flowId: string, repositoryId: string): McpAuthorizationFlow | undefined {\n this.sweepFlows();\n const flow = this.flows.get(flowId);\n return flow?.repositoryId === repositoryId ? publicFlow(flow) : undefined;\n }\n\n async cancelAuthorization(flowId: string, repositoryId: string): Promise<McpAuthorizationFlow | undefined> {\n const flow = this.flows.get(flowId);\n if (!flow || flow.repositoryId !== repositoryId) return undefined;\n if (!isTerminal(flow.status)) {\n flow.status = 'cancelled';\n await this.finishFlow(flow);\n }\n return publicFlow(flow);\n }\n\n private async runAuthorization(\n flow: InternalAuthorizationFlow,\n sources: McpConfigSource[],\n redirect?: DoomOAuthRedirect,\n ): Promise<void> {\n const owner = new McpRuntimeOwner();\n flow.owner = owner;\n flow.timeout = setTimeout(() => {\n if (isTerminal(flow.status)) return;\n flow.status = 'expired';\n flow.error = 'Authorization timed out.';\n void this.finishFlow(flow);\n }, this.authorizationTimeoutMs);\n\n try {\n const handle = await owner.start({\n configSources: sources,\n tokenStore: await this.tokenStore(),\n ...(redirect ? { callbackServer: hubCallbackSink(redirect) } : {}),\n onAuthorizationUrl: (url, serverName) => {\n if (serverName !== flow.serverName || isTerminal(flow.status)) return;\n const safeUrl = safeAuthorizationUrl(url);\n if (!safeUrl) {\n flow.status = 'failed';\n flow.error = 'The server returned an invalid authorization URL.';\n void this.finishFlow(flow);\n return;\n }\n flow.authorizationUrl = safeUrl;\n flow.status = 'waiting';\n },\n onServerStateChange: (change) => this.applyAuthorizationState(flow, change),\n });\n if (!handle || isTerminal(flow.status)) return;\n const manager = handle.services.clientManager;\n await manager.disconnectServer(flow.serverName);\n await manager.ensureConnected(flow.serverName);\n if (!isTerminal(flow.status)) flow.status = 'completed';\n } catch {\n if (!isTerminal(flow.status)) {\n flow.status = 'failed';\n flow.error = 'The MCP server could not complete authorization.';\n }\n } finally {\n if (!isTerminal(flow.status)) flow.status = 'completed';\n await this.finishFlow(flow);\n }\n }\n\n private applyAuthorizationState(flow: InternalAuthorizationFlow, change: McpServerStateChange): void {\n if (change.serverName !== flow.serverName || isTerminal(flow.status)) return;\n if (change.state === 'connected') flow.status = 'completed';\n else if (change.state === 'needs-auth' && flow.status === 'starting') flow.status = 'waiting';\n else if (change.state === 'failed') {\n flow.status = 'failed';\n flow.error = 'The MCP server rejected the authorization attempt.';\n }\n }\n\n private async finishFlow(flow: InternalAuthorizationFlow): Promise<void> {\n if (flow.timeout) clearTimeout(flow.timeout);\n flow.timeout = undefined;\n const owner = flow.owner;\n flow.owner = undefined;\n await owner?.dispose();\n if (flow.stagingDirectory) fs.rmSync(flow.stagingDirectory, { recursive: true, force: true });\n flow.stagingDirectory = undefined;\n this.busyRepositories.delete(flow.repositoryId);\n flow.retainedUntil = Date.now() + TERMINAL_FLOW_RETENTION_MS;\n }\n\n private sweepFlows(): void {\n const now = Date.now();\n for (const [id, flow] of this.flows) {\n if (flow.retainedUntil !== undefined && flow.retainedUntil <= now) this.flows.delete(id);\n }\n }\n\n async dispose(): Promise<void> {\n await Promise.all(\n [...this.flows.values()].map(async (flow) => {\n if (!isTerminal(flow.status)) flow.status = 'cancelled';\n await this.finishFlow(flow);\n }),\n );\n this.flows.clear();\n this.busyRepositories.clear();\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{buildMcpConfigGroups as e}from"./configSources.mjs";import{McpRuntimeOwner as t,hubCallbackSink as n,readCachedCatalog as r}from"./mcpRuntime.mjs";import{mcpSessionConfigFromProjection as i}from"./projection.mjs";import{toPiToolName as a}from"../../services/mcpCatalog.mjs";import{createTokenStore as o}from"./keyringTokenStore.mjs";import{randomUUID as s}from"node:crypto";import c from"node:fs";import l from"node:path";import u from"node:os";const d={fresh:!1,reasons:[`never-synced`]};function f(e){return[...new Set([...e.sessionLocal.serverNames,...e.shared.serverNames])].sort((e,t)=>e.localeCompare(t))}function p(e){return[...e.sessionLocal.configSources,...e.shared.configSources]}function m(e,t){return{name:t.name,piName:a(e,t.name),...t.description?{description:t.description}:{}}}function h(e,t,n){let r=e;for(let e of[n,t,u.homedir()])e&&(r=r.split(e).join(e===u.homedir()?`~`:`<repository>`));return r.replace(/[A-Za-z]:\\[^\s,;)'"`]+/gu,`<path>`).replace(/(^|\s)\/(?!\/)[^\s,;)'"`]+/gu,`$1<path>`)}function g(e){return{id:e.id,repositoryId:e.repositoryId,serverName:e.serverName,status:e.status,...e.authorizationUrl?{authorizationUrl:e.authorizationUrl}:{},...e.error?{error:e.error}:{},expiresAt:e.expiresAt}}function _(e){return e===`completed`||e===`failed`||e===`cancelled`||e===`expired`}function v(e){if(!(e.protocol!==`https:`&&e.protocol!==`http:`||e.toString().length>4096))return e.toString()}var y=class{discoveryTimeoutMs;authorizationTimeoutMs;injectedTokenStore;busyRepositories=new Set;flows=new Map;constructor(e={}){this.discoveryTimeoutMs=e.discoveryTimeoutMs??3e4,this.authorizationTimeoutMs=e.authorizationTimeoutMs??3e5,this.injectedTokenStore=e.tokenStore}async tokenStore(){return this.injectedTokenStore??await o()}async credentialPresence(e){let t=new Map;try{let n=await this.tokenStore();await Promise.all(e.map(async e=>{try{t.set(e,await n.read(e)!==void 0)}catch{t.set(e,!1)}}))}catch{for(let n of e)t.set(n,!1)}return t}prepare(t,n,a,o){if(!a?.mcpProjection){let e=a??d;return{catalog:{repositoryId:t,sync:{fresh:e.fresh,reasons:[...e.reasons]},servers:[],droppedServers:[],diagnostics:[]},configSources:[],groups:{shared:{configSources:[],configPaths:[],serverNames:[]},sessionLocal:{configSources:[],configPaths:[],serverNames:[]},droppedServers:[],diagnostics:[]}}}let s=c.mkdtempSync(l.join(u.tmpdir(),`doompi-mcp-settings-`));c.chmodSync(s,448);try{let c={...i(a.mcpProjection),repoRoot:n,stagingDirectory:s,...o?{allowlist:{servers:[o],proxy:[]}}:{}},l=e(c),u=p(l),d=f(l),g=new Map(r(u).servers.map(e=>[e.name,e.tools])),_=d.map(e=>{let t=g.get(e)??[];return{name:e,state:`not-connected`,source:t.length===0?`configured`:`cached`,credentialPresent:!1,tools:t.map(t=>m(e,t))}});return{catalog:{repositoryId:t,sync:{fresh:a.fresh,reasons:[...a.reasons]},servers:_,droppedServers:[...l.droppedServers],diagnostics:l.diagnostics.map(e=>h(e,n,s))},configSources:u,groups:l,stagingDirectory:s}}catch(e){throw c.rmSync(s,{recursive:!0,force:!0}),e}}cleanupPrepared(e){e.stagingDirectory&&c.rmSync(e.stagingDirectory,{recursive:!0,force:!0})}async readCatalog(e,t,n){let r=this.prepare(e,t,n);try{let e=await this.credentialPresence(r.catalog.servers.map(e=>e.name));return{...r.catalog,servers:r.catalog.servers.map(t=>({...t,credentialPresent:e.get(t.name)??!1}))}}finally{this.cleanupPrepared(r)}}async discover(e,n,i){if(this.busyRepositories.has(e))throw Error(`MCP management is already active for this repository.`);let a=this.prepare(e,n,i);if(!a.catalog.sync.fresh)throw this.cleanupPrepared(a),Error(`Sync this repository before discovering MCP capabilities.`);if(a.configSources.length===0)return this.cleanupPrepared(a),a.catalog;this.busyRepositories.add(e);let o=new t,s=new Map;try{let e=await o.start({configSources:a.configSources,tokenStore:await this.tokenStore(),onAuthorizationUrl:(e,t)=>{s.set(t,{serverName:t,state:`needs-auth`})},onServerStateChange:e=>s.set(e.serverName,e)});e&&await Promise.race([e.connectionsSettled,new Promise(e=>setTimeout(e,this.discoveryTimeoutMs))]);let t=e?.services.clientManager,n=new Map(r(a.configSources).servers.map(e=>[e.name,e.tools])),i=await this.credentialPresence(a.catalog.servers.map(e=>e.name)),c=await Promise.all(a.catalog.servers.map(async e=>{let r=s.get(e.name),a=r?.state??t?.getServerState(e.name)??e.state,o=n.get(e.name)??[],c=o.length===0?`configured`:`cached`;if(a===`connected`&&t)try{o=await t.ensureConnected(e.name).then(async e=>await e.listTools()),c=`live`}catch{}return{name:e.name,state:a,source:c,credentialPresent:i.get(e.name)??!1,tools:o.map(t=>m(e.name,t)),...r?.error?{error:`The server reported a connection error.`}:{}}}));return{...a.catalog,servers:c}}finally{await o.dispose(),this.cleanupPrepared(a),this.busyRepositories.delete(e)}}async authorize(e,t,n,r,i){if(this.sweepFlows(),this.busyRepositories.has(e))throw Error(`MCP management is already active for this repository.`);let a=this.prepare(e,t,n),o=a.catalog.servers.some(e=>e.name===r),c=a.catalog.sync.fresh;if(this.cleanupPrepared(a),!c)throw Error(`Sync this repository before authorizing an MCP server.`);if(!o)throw Error(`That MCP server is not enabled for this repository.`);let l=this.prepare(e,t,n,r),u={id:s(),repositoryId:e,serverName:r,status:`starting`,expiresAt:Date.now()+this.authorizationTimeoutMs,stagingDirectory:l.stagingDirectory};return this.flows.set(u.id,u),this.busyRepositories.add(e),this.runAuthorization(u,l.configSources,i),g(u)}getAuthorization(e,t){this.sweepFlows();let n=this.flows.get(e);return n?.repositoryId===t?g(n):void 0}async cancelAuthorization(e,t){let n=this.flows.get(e);if(n&&n.repositoryId===t)return _(n.status)||(n.status=`cancelled`,await this.finishFlow(n)),g(n)}async runAuthorization(e,r,i){let a=new t;e.owner=a,e.timeout=setTimeout(()=>{_(e.status)||(e.status=`expired`,e.error=`Authorization timed out.`,this.finishFlow(e))},this.authorizationTimeoutMs);try{let t=await a.start({configSources:r,tokenStore:await this.tokenStore(),...i?{callbackServer:n(i)}:{},onAuthorizationUrl:(t,n)=>{if(n!==e.serverName||_(e.status))return;let r=v(t);if(!r){e.status=`failed`,e.error=`The server returned an invalid authorization URL.`,this.finishFlow(e);return}e.authorizationUrl=r,e.status=`waiting`},onServerStateChange:t=>this.applyAuthorizationState(e,t)});if(!t||_(e.status))return;let o=t.services.clientManager;await o.disconnectServer(e.serverName),await o.ensureConnected(e.serverName),_(e.status)||(e.status=`completed`)}catch{_(e.status)||(e.status=`failed`,e.error=`The MCP server could not complete authorization.`)}finally{_(e.status)||(e.status=`completed`),await this.finishFlow(e)}}applyAuthorizationState(e,t){t.serverName!==e.serverName||_(e.status)||(t.state===`connected`?e.status=`completed`:t.state===`needs-auth`&&e.status===`starting`?e.status=`waiting`:t.state===`failed`&&(e.status=`failed`,e.error=`The MCP server rejected the authorization attempt.`))}async finishFlow(e){e.timeout&&clearTimeout(e.timeout),e.timeout=void 0;let t=e.owner;e.owner=void 0,await t?.dispose(),e.stagingDirectory&&c.rmSync(e.stagingDirectory,{recursive:!0,force:!0}),e.stagingDirectory=void 0,this.busyRepositories.delete(e.repositoryId),e.retainedUntil=Date.now()+3e5}sweepFlows(){let e=Date.now();for(let[t,n]of this.flows)n.retainedUntil!==void 0&&n.retainedUntil<=e&&this.flows.delete(t)}async dispose(){await Promise.all([...this.flows.values()].map(async e=>{_(e.status)||(e.status=`cancelled`),await this.finishFlow(e)})),this.flows.clear(),this.busyRepositories.clear()}};export{y as McpSettingsManager};
|
|
2
|
+
//# sourceMappingURL=mcpSettingsManager.mjs.map
|