@akon-labs/nexus-cli 0.1.8 → 0.1.10
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/CHANGELOG.md +27 -0
- package/LICENSE +1 -0
- package/README.md +3 -2
- package/dist/app-bridge.bundle.js +67 -0
- package/dist/cli.js +19934 -1869
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +1 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auto-model.d.ts +1 -1
- package/dist/core/auto-model.d.ts.map +1 -1
- package/dist/core/auto-model.js +1 -1
- package/dist/core/auto-model.js.map +1 -1
- package/dist/core/model-resolver.d.ts +2 -2
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +3 -3
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/package-manager.d.ts +15 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +50 -0
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +3 -3
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +5 -5
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/websearch.d.ts.map +1 -1
- package/dist/core/tools/websearch.js +3 -3
- package/dist/core/tools/websearch.js.map +1 -1
- package/dist/extensions/gitnexus/gateway.d.ts +6 -6
- package/dist/extensions/gitnexus/gateway.d.ts.map +1 -1
- package/dist/extensions/gitnexus/gateway.js +4 -4
- package/dist/extensions/gitnexus/gateway.js.map +1 -1
- package/dist/extensions/gitnexus/index.d.ts.map +1 -1
- package/dist/extensions/gitnexus/index.js +4 -2
- package/dist/extensions/gitnexus/index.js.map +1 -1
- package/dist/extensions/mcp/abort.d.ts +3 -0
- package/dist/extensions/mcp/abort.d.ts.map +1 -0
- package/dist/extensions/mcp/abort.js +41 -0
- package/dist/extensions/mcp/abort.js.map +1 -0
- package/dist/extensions/mcp/agent-dir.d.ts +6 -0
- package/dist/extensions/mcp/agent-dir.d.ts.map +1 -0
- package/dist/extensions/mcp/agent-dir.js +18 -0
- package/dist/extensions/mcp/agent-dir.js.map +1 -0
- package/dist/extensions/mcp/agent-plugin-loader.d.ts +9 -0
- package/dist/extensions/mcp/agent-plugin-loader.d.ts.map +1 -0
- package/dist/extensions/mcp/agent-plugin-loader.js +384 -0
- package/dist/extensions/mcp/agent-plugin-loader.js.map +1 -0
- package/dist/extensions/mcp/app-bridge.bundle.js +67 -0
- package/dist/extensions/mcp/commands.d.ts +27 -0
- package/dist/extensions/mcp/commands.d.ts.map +1 -0
- package/dist/extensions/mcp/commands.js +629 -0
- package/dist/extensions/mcp/commands.js.map +1 -0
- package/dist/extensions/mcp/config.d.ts +121 -38
- package/dist/extensions/mcp/config.d.ts.map +1 -1
- package/dist/extensions/mcp/config.js +1030 -70
- package/dist/extensions/mcp/config.js.map +1 -1
- package/dist/extensions/mcp/consent-manager.d.ts +14 -0
- package/dist/extensions/mcp/consent-manager.d.ts.map +1 -0
- package/dist/extensions/mcp/consent-manager.js +60 -0
- package/dist/extensions/mcp/consent-manager.js.map +1 -0
- package/dist/extensions/mcp/direct-tools.d.ts +11 -0
- package/dist/extensions/mcp/direct-tools.d.ts.map +1 -0
- package/dist/extensions/mcp/direct-tools.js +562 -0
- package/dist/extensions/mcp/direct-tools.js.map +1 -0
- package/dist/extensions/mcp/elicitation-handler.d.ts +18 -0
- package/dist/extensions/mcp/elicitation-handler.d.ts.map +1 -0
- package/dist/extensions/mcp/elicitation-handler.js +320 -0
- package/dist/extensions/mcp/elicitation-handler.js.map +1 -0
- package/dist/extensions/mcp/error-signal.d.ts +16 -0
- package/dist/extensions/mcp/error-signal.d.ts.map +1 -0
- package/dist/extensions/mcp/error-signal.js +22 -0
- package/dist/extensions/mcp/error-signal.js.map +1 -0
- package/dist/extensions/mcp/errors.d.ts +104 -0
- package/dist/extensions/mcp/errors.d.ts.map +1 -0
- package/dist/extensions/mcp/errors.js +171 -0
- package/dist/extensions/mcp/errors.js.map +1 -0
- package/dist/extensions/mcp/glimpse-ui.d.ts +10 -0
- package/dist/extensions/mcp/glimpse-ui.d.ts.map +1 -0
- package/dist/extensions/mcp/glimpse-ui.js +67 -0
- package/dist/extensions/mcp/glimpse-ui.js.map +1 -0
- package/dist/extensions/mcp/host-html-template.d.ts +17 -0
- package/dist/extensions/mcp/host-html-template.d.ts.map +1 -0
- package/dist/extensions/mcp/host-html-template.js +442 -0
- package/dist/extensions/mcp/host-html-template.js.map +1 -0
- package/dist/extensions/mcp/index.d.ts +20 -15
- package/dist/extensions/mcp/index.d.ts.map +1 -1
- package/dist/extensions/mcp/index.js +1064 -82
- package/dist/extensions/mcp/index.js.map +1 -1
- package/dist/extensions/mcp/init.d.ts +28 -0
- package/dist/extensions/mcp/init.d.ts.map +1 -0
- package/dist/extensions/mcp/init.js +631 -0
- package/dist/extensions/mcp/init.js.map +1 -0
- package/dist/extensions/mcp/json-schema-validator.d.ts +3 -0
- package/dist/extensions/mcp/json-schema-validator.d.ts.map +1 -0
- package/dist/extensions/mcp/json-schema-validator.js +53 -0
- package/dist/extensions/mcp/json-schema-validator.js.map +1 -0
- package/dist/extensions/mcp/lifecycle.d.ts +56 -0
- package/dist/extensions/mcp/lifecycle.d.ts.map +1 -0
- package/dist/extensions/mcp/lifecycle.js +395 -0
- package/dist/extensions/mcp/lifecycle.js.map +1 -0
- package/dist/extensions/mcp/logger.d.ts +52 -0
- package/dist/extensions/mcp/logger.d.ts.map +1 -0
- package/dist/extensions/mcp/logger.js +134 -0
- package/dist/extensions/mcp/logger.js.map +1 -0
- package/dist/extensions/mcp/mcp-auth-flow.d.ts +117 -0
- package/dist/extensions/mcp/mcp-auth-flow.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-auth-flow.js +869 -0
- package/dist/extensions/mcp/mcp-auth-flow.js.map +1 -0
- package/dist/extensions/mcp/mcp-auth.d.ts +162 -0
- package/dist/extensions/mcp/mcp-auth.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-auth.js +897 -0
- package/dist/extensions/mcp/mcp-auth.js.map +1 -0
- package/dist/extensions/mcp/mcp-bearer-store.d.ts +25 -0
- package/dist/extensions/mcp/mcp-bearer-store.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-bearer-store.js +350 -0
- package/dist/extensions/mcp/mcp-bearer-store.js.map +1 -0
- package/dist/extensions/mcp/mcp-callback-server.d.ts +54 -0
- package/dist/extensions/mcp/mcp-callback-server.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-callback-server.js +454 -0
- package/dist/extensions/mcp/mcp-callback-server.js.map +1 -0
- package/dist/extensions/mcp/mcp-code.d.ts +48 -0
- package/dist/extensions/mcp/mcp-code.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-code.js +358 -0
- package/dist/extensions/mcp/mcp-code.js.map +1 -0
- package/dist/extensions/mcp/mcp-keyring-helper.cjs +89 -0
- package/dist/extensions/mcp/mcp-oauth-provider.d.ts +135 -0
- package/dist/extensions/mcp/mcp-oauth-provider.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-oauth-provider.js +491 -0
- package/dist/extensions/mcp/mcp-oauth-provider.js.map +1 -0
- package/dist/extensions/mcp/mcp-output-guard.d.ts +59 -0
- package/dist/extensions/mcp/mcp-output-guard.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-output-guard.js +343 -0
- package/dist/extensions/mcp/mcp-output-guard.js.map +1 -0
- package/dist/extensions/mcp/mcp-panel.d.ts +71 -0
- package/dist/extensions/mcp/mcp-panel.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-panel.js +973 -0
- package/dist/extensions/mcp/mcp-panel.js.map +1 -0
- package/dist/extensions/mcp/mcp-probe.d.ts +7 -0
- package/dist/extensions/mcp/mcp-probe.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-probe.js +155 -0
- package/dist/extensions/mcp/mcp-probe.js.map +1 -0
- package/dist/extensions/mcp/mcp-script-worker.mjs +130 -0
- package/dist/extensions/mcp/mcp-setup-panel.d.ts +85 -0
- package/dist/extensions/mcp/mcp-setup-panel.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-setup-panel.js +618 -0
- package/dist/extensions/mcp/mcp-setup-panel.js.map +1 -0
- package/dist/extensions/mcp/mcp-status.d.ts +12 -0
- package/dist/extensions/mcp/mcp-status.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-status.js +98 -0
- package/dist/extensions/mcp/mcp-status.js.map +1 -0
- package/dist/extensions/mcp/mcp-trace.d.ts +95 -0
- package/dist/extensions/mcp/mcp-trace.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-trace.js +252 -0
- package/dist/extensions/mcp/mcp-trace.js.map +1 -0
- package/dist/extensions/mcp/metadata-cache.d.ts +20 -0
- package/dist/extensions/mcp/metadata-cache.d.ts.map +1 -0
- package/dist/extensions/mcp/metadata-cache.js +318 -0
- package/dist/extensions/mcp/metadata-cache.js.map +1 -0
- package/dist/extensions/mcp/npx-resolver.d.ts +7 -0
- package/dist/extensions/mcp/npx-resolver.d.ts.map +1 -0
- package/dist/extensions/mcp/npx-resolver.js +505 -0
- package/dist/extensions/mcp/npx-resolver.js.map +1 -0
- package/dist/extensions/mcp/oauth.d.ts +21 -0
- package/dist/extensions/mcp/oauth.d.ts.map +1 -0
- package/dist/extensions/mcp/oauth.js +15 -0
- package/dist/extensions/mcp/oauth.js.map +1 -0
- package/dist/extensions/mcp/onboarding-state.d.ts +13 -0
- package/dist/extensions/mcp/onboarding-state.d.ts.map +1 -0
- package/dist/extensions/mcp/onboarding-state.js +65 -0
- package/dist/extensions/mcp/onboarding-state.js.map +1 -0
- package/dist/extensions/mcp/package-mcp-loader.d.ts +3 -0
- package/dist/extensions/mcp/package-mcp-loader.d.ts.map +1 -0
- package/dist/extensions/mcp/package-mcp-loader.js +170 -0
- package/dist/extensions/mcp/package-mcp-loader.js.map +1 -0
- package/dist/extensions/mcp/panel-keys.d.ts +21 -0
- package/dist/extensions/mcp/panel-keys.d.ts.map +1 -0
- package/dist/extensions/mcp/panel-keys.js +29 -0
- package/dist/extensions/mcp/panel-keys.js.map +1 -0
- package/dist/extensions/mcp/prompts.d.ts +63 -0
- package/dist/extensions/mcp/prompts.d.ts.map +1 -0
- package/dist/extensions/mcp/prompts.js +297 -0
- package/dist/extensions/mcp/prompts.js.map +1 -0
- package/dist/extensions/mcp/proxy-modes.d.ts +15 -0
- package/dist/extensions/mcp/proxy-modes.d.ts.map +1 -0
- package/dist/extensions/mcp/proxy-modes.js +1421 -0
- package/dist/extensions/mcp/proxy-modes.js.map +1 -0
- package/dist/extensions/mcp/request-headers-command.d.ts +11 -0
- package/dist/extensions/mcp/request-headers-command.d.ts.map +1 -0
- package/dist/extensions/mcp/request-headers-command.js +334 -0
- package/dist/extensions/mcp/request-headers-command.js.map +1 -0
- package/dist/extensions/mcp/resource-tools.d.ts +2 -0
- package/dist/extensions/mcp/resource-tools.d.ts.map +1 -0
- package/dist/extensions/mcp/resource-tools.js +15 -0
- package/dist/extensions/mcp/resource-tools.js.map +1 -0
- package/dist/extensions/mcp/runtime-owner.d.ts +14 -0
- package/dist/extensions/mcp/runtime-owner.d.ts.map +1 -0
- package/dist/extensions/mcp/runtime-owner.js +84 -0
- package/dist/extensions/mcp/runtime-owner.js.map +1 -0
- package/dist/extensions/mcp/sampling-handler.d.ts +18 -0
- package/dist/extensions/mcp/sampling-handler.d.ts.map +1 -0
- package/dist/extensions/mcp/sampling-handler.js +229 -0
- package/dist/extensions/mcp/sampling-handler.js.map +1 -0
- package/dist/extensions/mcp/search-ranking.d.ts +25 -0
- package/dist/extensions/mcp/search-ranking.d.ts.map +1 -0
- package/dist/extensions/mcp/search-ranking.js +234 -0
- package/dist/extensions/mcp/search-ranking.js.map +1 -0
- package/dist/extensions/mcp/server-manager.d.ts +116 -0
- package/dist/extensions/mcp/server-manager.d.ts.map +1 -0
- package/dist/extensions/mcp/server-manager.js +1052 -0
- package/dist/extensions/mcp/server-manager.js.map +1 -0
- package/dist/extensions/mcp/session-recovery.d.ts +27 -0
- package/dist/extensions/mcp/session-recovery.d.ts.map +1 -0
- package/dist/extensions/mcp/session-recovery.js +140 -0
- package/dist/extensions/mcp/session-recovery.js.map +1 -0
- package/dist/extensions/mcp/skills/mcp-scripting/SKILL.md +38 -0
- package/dist/extensions/mcp/state.d.ts +62 -0
- package/dist/extensions/mcp/state.d.ts.map +1 -0
- package/dist/extensions/mcp/state.js +2 -0
- package/dist/extensions/mcp/state.js.map +1 -0
- package/dist/extensions/mcp/tool-approval.d.ts +11 -0
- package/dist/extensions/mcp/tool-approval.d.ts.map +1 -0
- package/dist/extensions/mcp/tool-approval.js +156 -0
- package/dist/extensions/mcp/tool-approval.js.map +1 -0
- package/dist/extensions/mcp/tool-metadata.d.ts +11 -0
- package/dist/extensions/mcp/tool-metadata.d.ts.map +1 -0
- package/dist/extensions/mcp/tool-metadata.js +264 -0
- package/dist/extensions/mcp/tool-metadata.js.map +1 -0
- package/dist/extensions/mcp/tool-registrar.d.ts +21 -0
- package/dist/extensions/mcp/tool-registrar.d.ts.map +1 -0
- package/dist/extensions/mcp/tool-registrar.js +229 -0
- package/dist/extensions/mcp/tool-registrar.js.map +1 -0
- package/dist/extensions/mcp/tool-result-renderer.d.ts +83 -0
- package/dist/extensions/mcp/tool-result-renderer.d.ts.map +1 -0
- package/dist/extensions/mcp/tool-result-renderer.js +345 -0
- package/dist/extensions/mcp/tool-result-renderer.js.map +1 -0
- package/dist/extensions/mcp/ts-shape.d.ts +3 -0
- package/dist/extensions/mcp/ts-shape.d.ts.map +1 -0
- package/dist/extensions/mcp/ts-shape.js +160 -0
- package/dist/extensions/mcp/ts-shape.js.map +1 -0
- package/dist/extensions/mcp/types.d.ts +521 -0
- package/dist/extensions/mcp/types.d.ts.map +1 -0
- package/dist/extensions/mcp/types.js +294 -0
- package/dist/extensions/mcp/types.js.map +1 -0
- package/dist/extensions/mcp/ui-app-bridge-helpers.d.ts +7 -0
- package/dist/extensions/mcp/ui-app-bridge-helpers.d.ts.map +1 -0
- package/dist/extensions/mcp/ui-app-bridge-helpers.js +37 -0
- package/dist/extensions/mcp/ui-app-bridge-helpers.js.map +1 -0
- package/dist/extensions/mcp/ui-resource-handler.d.ts +18 -0
- package/dist/extensions/mcp/ui-resource-handler.d.ts.map +1 -0
- package/dist/extensions/mcp/ui-resource-handler.js +210 -0
- package/dist/extensions/mcp/ui-resource-handler.js.map +1 -0
- package/dist/extensions/mcp/ui-server.d.ts +52 -0
- package/dist/extensions/mcp/ui-server.d.ts.map +1 -0
- package/dist/extensions/mcp/ui-server.js +740 -0
- package/dist/extensions/mcp/ui-server.js.map +1 -0
- package/dist/extensions/mcp/ui-session.d.ts +40 -0
- package/dist/extensions/mcp/ui-session.d.ts.map +1 -0
- package/dist/extensions/mcp/ui-session.js +477 -0
- package/dist/extensions/mcp/ui-session.js.map +1 -0
- package/dist/extensions/mcp/ui-stream-types.d.ts +105 -0
- package/dist/extensions/mcp/ui-stream-types.d.ts.map +1 -0
- package/dist/extensions/mcp/ui-stream-types.js +61 -0
- package/dist/extensions/mcp/ui-stream-types.js.map +1 -0
- package/dist/extensions/mcp/ui-tool-visibility.d.ts +5 -0
- package/dist/extensions/mcp/ui-tool-visibility.d.ts.map +1 -0
- package/dist/extensions/mcp/ui-tool-visibility.js +27 -0
- package/dist/extensions/mcp/ui-tool-visibility.js.map +1 -0
- package/dist/extensions/mcp/unix-socket-transport.d.ts +15 -0
- package/dist/extensions/mcp/unix-socket-transport.d.ts.map +1 -0
- package/dist/extensions/mcp/unix-socket-transport.js +85 -0
- package/dist/extensions/mcp/unix-socket-transport.js.map +1 -0
- package/dist/extensions/mcp/utils.d.ts +32 -0
- package/dist/extensions/mcp/utils.d.ts.map +1 -0
- package/dist/extensions/mcp/utils.js +336 -0
- package/dist/extensions/mcp/utils.js.map +1 -0
- package/dist/extensions/subagent/index.d.ts +62 -3
- package/dist/extensions/subagent/index.d.ts.map +1 -1
- package/dist/extensions/subagent/index.js +193 -58
- package/dist/extensions/subagent/index.js.map +1 -1
- package/dist/extensions/web-access.d.ts.map +1 -1
- package/dist/extensions/web-access.js +1 -1
- package/dist/extensions/web-access.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20074 -2004
- package/dist/index.js.map +1 -1
- package/dist/mcp-keyring-helper.cjs +89 -0
- package/dist/mcp-script-worker.mjs +130 -0
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +1 -1
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/response-separator.d.ts +1 -1
- package/dist/modes/interactive/components/response-separator.d.ts.map +1 -1
- package/dist/modes/interactive/components/response-separator.js +1 -1
- package/dist/modes/interactive/components/response-separator.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +7 -5
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/rpc-entry.js +19934 -1869
- package/dist/skills/mcp-scripting/SKILL.md +38 -0
- package/dist/vendor/ai.js +282 -207
- package/docs/mcp.md +178 -0
- package/docs/pi-subagents.md +75 -0
- package/docs/quickstart.md +1 -1
- package/docs/usage.md +2 -1
- package/examples/extensions/pi-subagents-worker-policy.ts +86 -0
- package/npm-shrinkwrap.json +763 -6
- package/package.json +15 -4
- package/dist/extensions/mcp/client.d.ts +0 -86
- package/dist/extensions/mcp/client.d.ts.map +0 -1
- package/dist/extensions/mcp/client.js +0 -275
- package/dist/extensions/mcp/client.js.map +0 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decide the `isError` override for a finished tool result in the `tool_result` hook.
|
|
3
|
+
*
|
|
4
|
+
* A failed MCP tool call is *returned* (not thrown), tagged `details.error: "tool_error"` (the server
|
|
5
|
+
* returned an error result) or `"call_failed"` (the call itself threw and was caught). nexus never reads a
|
|
6
|
+
* result-level `isError`, so without this such a call is recorded as a success. Returning
|
|
7
|
+
* `{ isError: true }` (and nothing else) flips the flag; nexus's field-by-field merge keeps the original
|
|
8
|
+
* `content` and `details` intact.
|
|
9
|
+
*
|
|
10
|
+
* Limited to those two codes: the adapter's other `details.error` values (`auth_required`, connection
|
|
11
|
+
* states, search/validation feedback, ...) are not failed tool calls, so they get no override.
|
|
12
|
+
*/
|
|
13
|
+
export function toolErrorOverride(details) {
|
|
14
|
+
if (details && typeof details === "object" && "error" in details) {
|
|
15
|
+
const code = details.error;
|
|
16
|
+
if (code === "tool_error" || code === "call_failed") {
|
|
17
|
+
return { isError: true };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=error-signal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-signal.js","sourceRoot":"","sources":["../../../src/extensions/mcp/error-signal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAgB,EAAiC;IAClF,IAAI,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,IAAI,OAAO,EAAE,CAAC;QAClE,MAAM,IAAI,GAAI,OAA+B,CAAC,KAAK,CAAC;QACpD,IAAI,IAAI,KAAK,YAAY,IAAI,IAAI,KAAK,aAAa,EAAE,CAAC;YACrD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC1B,CAAC;IACF,CAAC;IACD,OAAO,SAAS,CAAC;AAAA,CACjB","sourcesContent":["/**\n * Decide the `isError` override for a finished tool result in the `tool_result` hook.\n *\n * A failed MCP tool call is *returned* (not thrown), tagged `details.error: \"tool_error\"` (the server\n * returned an error result) or `\"call_failed\"` (the call itself threw and was caught). nexus never reads a\n * result-level `isError`, so without this such a call is recorded as a success. Returning\n * `{ isError: true }` (and nothing else) flips the flag; nexus's field-by-field merge keeps the original\n * `content` and `details` intact.\n *\n * Limited to those two codes: the adapter's other `details.error` values (`auth_required`, connection\n * states, search/validation feedback, ...) are not failed tool calls, so they get no override.\n */\nexport function toolErrorOverride(details: unknown): { isError: true } | undefined {\n\tif (details && typeof details === \"object\" && \"error\" in details) {\n\t\tconst code = (details as { error?: unknown }).error;\n\t\tif (code === \"tool_error\" || code === \"call_failed\") {\n\t\t\treturn { isError: true };\n\t\t}\n\t}\n\treturn undefined;\n}\n"]}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error types for MCP UI operations.
|
|
3
|
+
* Provides structured errors with context and recovery hints.
|
|
4
|
+
*/
|
|
5
|
+
export interface McpUiErrorContext {
|
|
6
|
+
server?: string;
|
|
7
|
+
tool?: string;
|
|
8
|
+
uri?: string;
|
|
9
|
+
session?: string;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Base error class for MCP UI errors.
|
|
14
|
+
*/
|
|
15
|
+
export declare class McpUiError extends Error {
|
|
16
|
+
readonly code: string;
|
|
17
|
+
readonly context: McpUiErrorContext;
|
|
18
|
+
readonly recoveryHint: string | undefined;
|
|
19
|
+
readonly cause: Error | undefined;
|
|
20
|
+
constructor(message: string, options: {
|
|
21
|
+
code: string;
|
|
22
|
+
context?: McpUiErrorContext;
|
|
23
|
+
recoveryHint?: string;
|
|
24
|
+
cause?: Error;
|
|
25
|
+
});
|
|
26
|
+
toJSON(): Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Error fetching a UI resource from the MCP server.
|
|
30
|
+
*/
|
|
31
|
+
export declare class ResourceFetchError extends McpUiError {
|
|
32
|
+
constructor(uri: string, reason: string, options?: {
|
|
33
|
+
server?: string;
|
|
34
|
+
cause?: Error;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Error parsing or validating UI resource content.
|
|
39
|
+
*/
|
|
40
|
+
export declare class ResourceParseError extends McpUiError {
|
|
41
|
+
constructor(uri: string, reason: string, options?: {
|
|
42
|
+
server?: string;
|
|
43
|
+
mimeType?: string;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Error connecting to the AppBridge.
|
|
48
|
+
*/
|
|
49
|
+
export declare class BridgeConnectionError extends McpUiError {
|
|
50
|
+
constructor(reason: string, options?: {
|
|
51
|
+
session?: string;
|
|
52
|
+
cause?: Error;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Error related to user consent for tool calls.
|
|
57
|
+
*/
|
|
58
|
+
export declare class ConsentError extends McpUiError {
|
|
59
|
+
readonly denied: boolean;
|
|
60
|
+
readonly requiresApproval: boolean;
|
|
61
|
+
constructor(server: string, options: {
|
|
62
|
+
denied: true;
|
|
63
|
+
requiresApproval?: never;
|
|
64
|
+
} | {
|
|
65
|
+
requiresApproval: true;
|
|
66
|
+
denied?: never;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Error with UI server session management.
|
|
71
|
+
*/
|
|
72
|
+
export declare class SessionError extends McpUiError {
|
|
73
|
+
constructor(reason: string, options?: {
|
|
74
|
+
session?: string;
|
|
75
|
+
cause?: Error;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Error starting or operating the UI server.
|
|
80
|
+
*/
|
|
81
|
+
export declare class ServerError extends McpUiError {
|
|
82
|
+
constructor(reason: string, options?: {
|
|
83
|
+
port?: number;
|
|
84
|
+
cause?: Error;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Error communicating with the MCP server.
|
|
89
|
+
*/
|
|
90
|
+
export declare class McpServerError extends McpUiError {
|
|
91
|
+
constructor(server: string, reason: string, options?: {
|
|
92
|
+
tool?: string;
|
|
93
|
+
cause?: Error;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Wrap an unknown error into an McpUiError.
|
|
98
|
+
*/
|
|
99
|
+
export declare function wrapError(error: unknown, context?: McpUiErrorContext): McpUiError;
|
|
100
|
+
/**
|
|
101
|
+
* Check if an error is a specific MCP UI error type.
|
|
102
|
+
*/
|
|
103
|
+
export declare function isErrorCode(error: unknown, code: string): boolean;
|
|
104
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/extensions/mcp/errors.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Custom error types for MCP UI operations.\n * Provides structured errors with context and recovery hints.\n */\n\nexport interface McpUiErrorContext {\n\tserver?: string;\n\ttool?: string;\n\turi?: string;\n\tsession?: string;\n\t[key: string]: unknown;\n}\n\n/**\n * Base error class for MCP UI errors.\n */\nexport class McpUiError extends Error {\n\treadonly code: string;\n\treadonly context: McpUiErrorContext;\n\treadonly recoveryHint: string | undefined;\n\treadonly cause: Error | undefined;\n\n\tconstructor(\n\t\tmessage: string,\n\t\toptions: {\n\t\t\tcode: string;\n\t\t\tcontext?: McpUiErrorContext;\n\t\t\trecoveryHint?: string;\n\t\t\tcause?: Error;\n\t\t},\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"McpUiError\";\n\t\tthis.code = options.code;\n\t\tthis.context = options.context ?? {};\n\t\tthis.recoveryHint = options.recoveryHint;\n\t\tthis.cause = options.cause;\n\n\t\t// Maintain proper stack trace\n\t\tif (Error.captureStackTrace) {\n\t\t\tError.captureStackTrace(this, this.constructor);\n\t\t}\n\t}\n\n\ttoJSON(): Record<string, unknown> {\n\t\treturn {\n\t\t\tname: this.name,\n\t\t\tcode: this.code,\n\t\t\tmessage: this.message,\n\t\t\tcontext: this.context,\n\t\t\trecoveryHint: this.recoveryHint,\n\t\t\tstack: this.stack,\n\t\t};\n\t}\n}\n\n/**\n * Error fetching a UI resource from the MCP server.\n */\nexport class ResourceFetchError extends McpUiError {\n\tconstructor(uri: string, reason: string, options?: { server?: string; cause?: Error }) {\n\t\tsuper(`Failed to fetch UI resource \"${uri}\": ${reason}`, {\n\t\t\tcode: \"RESOURCE_FETCH_ERROR\",\n\t\t\tcontext: { uri, ...(options?.server !== undefined ? { server: options.server } : {}) },\n\t\t\trecoveryHint: \"Check that the MCP server is connected and the resource URI is valid.\",\n\t\t\t...(options?.cause !== undefined ? { cause: options.cause } : {}),\n\t\t});\n\t\tthis.name = \"ResourceFetchError\";\n\t}\n}\n\n/**\n * Error parsing or validating UI resource content.\n */\nexport class ResourceParseError extends McpUiError {\n\tconstructor(uri: string, reason: string, options?: { server?: string; mimeType?: string }) {\n\t\tsuper(`Invalid UI resource \"${uri}\": ${reason}`, {\n\t\t\tcode: \"RESOURCE_PARSE_ERROR\",\n\t\t\tcontext: {\n\t\t\t\turi,\n\t\t\t\t...(options?.server !== undefined ? { server: options.server } : {}),\n\t\t\t\t...(options?.mimeType !== undefined ? { mimeType: options.mimeType } : {}),\n\t\t\t},\n\t\t\trecoveryHint: \"Ensure the resource returns valid HTML with the correct MIME type.\",\n\t\t});\n\t\tthis.name = \"ResourceParseError\";\n\t}\n}\n\n/**\n * Error connecting to the AppBridge.\n */\nexport class BridgeConnectionError extends McpUiError {\n\tconstructor(reason: string, options?: { session?: string; cause?: Error }) {\n\t\tsuper(`AppBridge connection failed: ${reason}`, {\n\t\t\tcode: \"BRIDGE_CONNECTION_ERROR\",\n\t\t\tcontext: options?.session !== undefined ? { session: options.session } : {},\n\t\t\trecoveryHint: \"Check browser console for detailed errors. The iframe may have failed to load.\",\n\t\t\t...(options?.cause !== undefined ? { cause: options.cause } : {}),\n\t\t});\n\t\tthis.name = \"BridgeConnectionError\";\n\t}\n}\n\n/**\n * Error related to user consent for tool calls.\n */\nexport class ConsentError extends McpUiError {\n\treadonly denied: boolean;\n\treadonly requiresApproval: boolean;\n\n\tconstructor(\n\t\tserver: string,\n\t\toptions: { denied: true; requiresApproval?: never } | { requiresApproval: true; denied?: never },\n\t) {\n\t\tconst denied = options.denied === true;\n\t\tconst message = denied\n\t\t\t? `Tool calls for \"${server}\" were denied for this session`\n\t\t\t: `Tool call approval required for \"${server}\"`;\n\n\t\tsuper(message, {\n\t\t\tcode: denied ? \"CONSENT_DENIED\" : \"CONSENT_REQUIRED\",\n\t\t\tcontext: { server },\n\t\t\trecoveryHint: denied\n\t\t\t\t? \"The user denied tool access. Start a new session to try again.\"\n\t\t\t\t: \"Prompt the user for consent before calling tools.\",\n\t\t});\n\t\tthis.name = \"ConsentError\";\n\t\tthis.denied = denied;\n\t\tthis.requiresApproval = options.requiresApproval === true;\n\t}\n}\n\n/**\n * Error with UI server session management.\n */\nexport class SessionError extends McpUiError {\n\tconstructor(reason: string, options?: { session?: string; cause?: Error }) {\n\t\tsuper(`Session error: ${reason}`, {\n\t\t\tcode: \"SESSION_ERROR\",\n\t\t\tcontext: options?.session !== undefined ? { session: options.session } : {},\n\t\t\trecoveryHint: \"The session may have expired or been closed. Try opening the UI again.\",\n\t\t\t...(options?.cause !== undefined ? { cause: options.cause } : {}),\n\t\t});\n\t\tthis.name = \"SessionError\";\n\t}\n}\n\n/**\n * Error starting or operating the UI server.\n */\nexport class ServerError extends McpUiError {\n\tconstructor(reason: string, options?: { port?: number; cause?: Error }) {\n\t\tsuper(`UI server error: ${reason}`, {\n\t\t\tcode: \"SERVER_ERROR\",\n\t\t\tcontext: options?.port !== undefined ? { port: options.port } : {},\n\t\t\trecoveryHint: \"Check if the port is available. Another process may be using it.\",\n\t\t\t...(options?.cause !== undefined ? { cause: options.cause } : {}),\n\t\t});\n\t\tthis.name = \"ServerError\";\n\t}\n}\n\n/**\n * Error communicating with the MCP server.\n */\nexport class McpServerError extends McpUiError {\n\tconstructor(server: string, reason: string, options?: { tool?: string; cause?: Error }) {\n\t\tsuper(`MCP server \"${server}\" error: ${reason}`, {\n\t\t\tcode: \"MCP_SERVER_ERROR\",\n\t\t\tcontext: { server, ...(options?.tool !== undefined ? { tool: options.tool } : {}) },\n\t\t\trecoveryHint: \"Check that the MCP server is running and responsive.\",\n\t\t\t...(options?.cause !== undefined ? { cause: options.cause } : {}),\n\t\t});\n\t\tthis.name = \"McpServerError\";\n\t}\n}\n\n/**\n * Wrap an unknown error into an McpUiError.\n */\nexport function wrapError(error: unknown, context?: McpUiErrorContext): McpUiError {\n\tif (error instanceof McpUiError) {\n\t\t// Preserve the original subtype and subtype-specific fields (for example\n\t\t// ConsentError.denied) while enriching its mutable context record.\n\t\tif (context) Object.assign(error.context, context);\n\t\treturn error;\n\t}\n\n\tconst cause = error instanceof Error ? error : undefined;\n\tconst message = error instanceof Error ? error.message : String(error);\n\n\treturn new McpUiError(message, {\n\t\tcode: \"UNKNOWN_ERROR\",\n\t\t...(context !== undefined ? { context } : {}),\n\t\t...(cause !== undefined ? { cause } : {}),\n\t});\n}\n\n/**\n * Check if an error is a specific MCP UI error type.\n */\nexport function isErrorCode(error: unknown, code: string): boolean {\n\treturn error instanceof McpUiError && error.code === code;\n}\n"]}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error types for MCP UI operations.
|
|
3
|
+
* Provides structured errors with context and recovery hints.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Base error class for MCP UI errors.
|
|
7
|
+
*/
|
|
8
|
+
export class McpUiError extends Error {
|
|
9
|
+
code;
|
|
10
|
+
context;
|
|
11
|
+
recoveryHint;
|
|
12
|
+
cause;
|
|
13
|
+
constructor(message, options) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "McpUiError";
|
|
16
|
+
this.code = options.code;
|
|
17
|
+
this.context = options.context ?? {};
|
|
18
|
+
this.recoveryHint = options.recoveryHint;
|
|
19
|
+
this.cause = options.cause;
|
|
20
|
+
// Maintain proper stack trace
|
|
21
|
+
if (Error.captureStackTrace) {
|
|
22
|
+
Error.captureStackTrace(this, this.constructor);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
toJSON() {
|
|
26
|
+
return {
|
|
27
|
+
name: this.name,
|
|
28
|
+
code: this.code,
|
|
29
|
+
message: this.message,
|
|
30
|
+
context: this.context,
|
|
31
|
+
recoveryHint: this.recoveryHint,
|
|
32
|
+
stack: this.stack,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Error fetching a UI resource from the MCP server.
|
|
38
|
+
*/
|
|
39
|
+
export class ResourceFetchError extends McpUiError {
|
|
40
|
+
constructor(uri, reason, options) {
|
|
41
|
+
super(`Failed to fetch UI resource "${uri}": ${reason}`, {
|
|
42
|
+
code: "RESOURCE_FETCH_ERROR",
|
|
43
|
+
context: { uri, ...(options?.server !== undefined ? { server: options.server } : {}) },
|
|
44
|
+
recoveryHint: "Check that the MCP server is connected and the resource URI is valid.",
|
|
45
|
+
...(options?.cause !== undefined ? { cause: options.cause } : {}),
|
|
46
|
+
});
|
|
47
|
+
this.name = "ResourceFetchError";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Error parsing or validating UI resource content.
|
|
52
|
+
*/
|
|
53
|
+
export class ResourceParseError extends McpUiError {
|
|
54
|
+
constructor(uri, reason, options) {
|
|
55
|
+
super(`Invalid UI resource "${uri}": ${reason}`, {
|
|
56
|
+
code: "RESOURCE_PARSE_ERROR",
|
|
57
|
+
context: {
|
|
58
|
+
uri,
|
|
59
|
+
...(options?.server !== undefined ? { server: options.server } : {}),
|
|
60
|
+
...(options?.mimeType !== undefined ? { mimeType: options.mimeType } : {}),
|
|
61
|
+
},
|
|
62
|
+
recoveryHint: "Ensure the resource returns valid HTML with the correct MIME type.",
|
|
63
|
+
});
|
|
64
|
+
this.name = "ResourceParseError";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Error connecting to the AppBridge.
|
|
69
|
+
*/
|
|
70
|
+
export class BridgeConnectionError extends McpUiError {
|
|
71
|
+
constructor(reason, options) {
|
|
72
|
+
super(`AppBridge connection failed: ${reason}`, {
|
|
73
|
+
code: "BRIDGE_CONNECTION_ERROR",
|
|
74
|
+
context: options?.session !== undefined ? { session: options.session } : {},
|
|
75
|
+
recoveryHint: "Check browser console for detailed errors. The iframe may have failed to load.",
|
|
76
|
+
...(options?.cause !== undefined ? { cause: options.cause } : {}),
|
|
77
|
+
});
|
|
78
|
+
this.name = "BridgeConnectionError";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Error related to user consent for tool calls.
|
|
83
|
+
*/
|
|
84
|
+
export class ConsentError extends McpUiError {
|
|
85
|
+
denied;
|
|
86
|
+
requiresApproval;
|
|
87
|
+
constructor(server, options) {
|
|
88
|
+
const denied = options.denied === true;
|
|
89
|
+
const message = denied
|
|
90
|
+
? `Tool calls for "${server}" were denied for this session`
|
|
91
|
+
: `Tool call approval required for "${server}"`;
|
|
92
|
+
super(message, {
|
|
93
|
+
code: denied ? "CONSENT_DENIED" : "CONSENT_REQUIRED",
|
|
94
|
+
context: { server },
|
|
95
|
+
recoveryHint: denied
|
|
96
|
+
? "The user denied tool access. Start a new session to try again."
|
|
97
|
+
: "Prompt the user for consent before calling tools.",
|
|
98
|
+
});
|
|
99
|
+
this.name = "ConsentError";
|
|
100
|
+
this.denied = denied;
|
|
101
|
+
this.requiresApproval = options.requiresApproval === true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Error with UI server session management.
|
|
106
|
+
*/
|
|
107
|
+
export class SessionError extends McpUiError {
|
|
108
|
+
constructor(reason, options) {
|
|
109
|
+
super(`Session error: ${reason}`, {
|
|
110
|
+
code: "SESSION_ERROR",
|
|
111
|
+
context: options?.session !== undefined ? { session: options.session } : {},
|
|
112
|
+
recoveryHint: "The session may have expired or been closed. Try opening the UI again.",
|
|
113
|
+
...(options?.cause !== undefined ? { cause: options.cause } : {}),
|
|
114
|
+
});
|
|
115
|
+
this.name = "SessionError";
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Error starting or operating the UI server.
|
|
120
|
+
*/
|
|
121
|
+
export class ServerError extends McpUiError {
|
|
122
|
+
constructor(reason, options) {
|
|
123
|
+
super(`UI server error: ${reason}`, {
|
|
124
|
+
code: "SERVER_ERROR",
|
|
125
|
+
context: options?.port !== undefined ? { port: options.port } : {},
|
|
126
|
+
recoveryHint: "Check if the port is available. Another process may be using it.",
|
|
127
|
+
...(options?.cause !== undefined ? { cause: options.cause } : {}),
|
|
128
|
+
});
|
|
129
|
+
this.name = "ServerError";
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Error communicating with the MCP server.
|
|
134
|
+
*/
|
|
135
|
+
export class McpServerError extends McpUiError {
|
|
136
|
+
constructor(server, reason, options) {
|
|
137
|
+
super(`MCP server "${server}" error: ${reason}`, {
|
|
138
|
+
code: "MCP_SERVER_ERROR",
|
|
139
|
+
context: { server, ...(options?.tool !== undefined ? { tool: options.tool } : {}) },
|
|
140
|
+
recoveryHint: "Check that the MCP server is running and responsive.",
|
|
141
|
+
...(options?.cause !== undefined ? { cause: options.cause } : {}),
|
|
142
|
+
});
|
|
143
|
+
this.name = "McpServerError";
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Wrap an unknown error into an McpUiError.
|
|
148
|
+
*/
|
|
149
|
+
export function wrapError(error, context) {
|
|
150
|
+
if (error instanceof McpUiError) {
|
|
151
|
+
// Preserve the original subtype and subtype-specific fields (for example
|
|
152
|
+
// ConsentError.denied) while enriching its mutable context record.
|
|
153
|
+
if (context)
|
|
154
|
+
Object.assign(error.context, context);
|
|
155
|
+
return error;
|
|
156
|
+
}
|
|
157
|
+
const cause = error instanceof Error ? error : undefined;
|
|
158
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
159
|
+
return new McpUiError(message, {
|
|
160
|
+
code: "UNKNOWN_ERROR",
|
|
161
|
+
...(context !== undefined ? { context } : {}),
|
|
162
|
+
...(cause !== undefined ? { cause } : {}),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Check if an error is a specific MCP UI error type.
|
|
167
|
+
*/
|
|
168
|
+
export function isErrorCode(error, code) {
|
|
169
|
+
return error instanceof McpUiError && error.code === code;
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/extensions/mcp/errors.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Custom error types for MCP UI operations.\n * Provides structured errors with context and recovery hints.\n */\n\nexport interface McpUiErrorContext {\n\tserver?: string;\n\ttool?: string;\n\turi?: string;\n\tsession?: string;\n\t[key: string]: unknown;\n}\n\n/**\n * Base error class for MCP UI errors.\n */\nexport class McpUiError extends Error {\n\treadonly code: string;\n\treadonly context: McpUiErrorContext;\n\treadonly recoveryHint: string | undefined;\n\treadonly cause: Error | undefined;\n\n\tconstructor(\n\t\tmessage: string,\n\t\toptions: {\n\t\t\tcode: string;\n\t\t\tcontext?: McpUiErrorContext;\n\t\t\trecoveryHint?: string;\n\t\t\tcause?: Error;\n\t\t},\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"McpUiError\";\n\t\tthis.code = options.code;\n\t\tthis.context = options.context ?? {};\n\t\tthis.recoveryHint = options.recoveryHint;\n\t\tthis.cause = options.cause;\n\n\t\t// Maintain proper stack trace\n\t\tif (Error.captureStackTrace) {\n\t\t\tError.captureStackTrace(this, this.constructor);\n\t\t}\n\t}\n\n\ttoJSON(): Record<string, unknown> {\n\t\treturn {\n\t\t\tname: this.name,\n\t\t\tcode: this.code,\n\t\t\tmessage: this.message,\n\t\t\tcontext: this.context,\n\t\t\trecoveryHint: this.recoveryHint,\n\t\t\tstack: this.stack,\n\t\t};\n\t}\n}\n\n/**\n * Error fetching a UI resource from the MCP server.\n */\nexport class ResourceFetchError extends McpUiError {\n\tconstructor(uri: string, reason: string, options?: { server?: string; cause?: Error }) {\n\t\tsuper(`Failed to fetch UI resource \"${uri}\": ${reason}`, {\n\t\t\tcode: \"RESOURCE_FETCH_ERROR\",\n\t\t\tcontext: { uri, ...(options?.server !== undefined ? { server: options.server } : {}) },\n\t\t\trecoveryHint: \"Check that the MCP server is connected and the resource URI is valid.\",\n\t\t\t...(options?.cause !== undefined ? { cause: options.cause } : {}),\n\t\t});\n\t\tthis.name = \"ResourceFetchError\";\n\t}\n}\n\n/**\n * Error parsing or validating UI resource content.\n */\nexport class ResourceParseError extends McpUiError {\n\tconstructor(uri: string, reason: string, options?: { server?: string; mimeType?: string }) {\n\t\tsuper(`Invalid UI resource \"${uri}\": ${reason}`, {\n\t\t\tcode: \"RESOURCE_PARSE_ERROR\",\n\t\t\tcontext: {\n\t\t\t\turi,\n\t\t\t\t...(options?.server !== undefined ? { server: options.server } : {}),\n\t\t\t\t...(options?.mimeType !== undefined ? { mimeType: options.mimeType } : {}),\n\t\t\t},\n\t\t\trecoveryHint: \"Ensure the resource returns valid HTML with the correct MIME type.\",\n\t\t});\n\t\tthis.name = \"ResourceParseError\";\n\t}\n}\n\n/**\n * Error connecting to the AppBridge.\n */\nexport class BridgeConnectionError extends McpUiError {\n\tconstructor(reason: string, options?: { session?: string; cause?: Error }) {\n\t\tsuper(`AppBridge connection failed: ${reason}`, {\n\t\t\tcode: \"BRIDGE_CONNECTION_ERROR\",\n\t\t\tcontext: options?.session !== undefined ? { session: options.session } : {},\n\t\t\trecoveryHint: \"Check browser console for detailed errors. The iframe may have failed to load.\",\n\t\t\t...(options?.cause !== undefined ? { cause: options.cause } : {}),\n\t\t});\n\t\tthis.name = \"BridgeConnectionError\";\n\t}\n}\n\n/**\n * Error related to user consent for tool calls.\n */\nexport class ConsentError extends McpUiError {\n\treadonly denied: boolean;\n\treadonly requiresApproval: boolean;\n\n\tconstructor(\n\t\tserver: string,\n\t\toptions: { denied: true; requiresApproval?: never } | { requiresApproval: true; denied?: never },\n\t) {\n\t\tconst denied = options.denied === true;\n\t\tconst message = denied\n\t\t\t? `Tool calls for \"${server}\" were denied for this session`\n\t\t\t: `Tool call approval required for \"${server}\"`;\n\n\t\tsuper(message, {\n\t\t\tcode: denied ? \"CONSENT_DENIED\" : \"CONSENT_REQUIRED\",\n\t\t\tcontext: { server },\n\t\t\trecoveryHint: denied\n\t\t\t\t? \"The user denied tool access. Start a new session to try again.\"\n\t\t\t\t: \"Prompt the user for consent before calling tools.\",\n\t\t});\n\t\tthis.name = \"ConsentError\";\n\t\tthis.denied = denied;\n\t\tthis.requiresApproval = options.requiresApproval === true;\n\t}\n}\n\n/**\n * Error with UI server session management.\n */\nexport class SessionError extends McpUiError {\n\tconstructor(reason: string, options?: { session?: string; cause?: Error }) {\n\t\tsuper(`Session error: ${reason}`, {\n\t\t\tcode: \"SESSION_ERROR\",\n\t\t\tcontext: options?.session !== undefined ? { session: options.session } : {},\n\t\t\trecoveryHint: \"The session may have expired or been closed. Try opening the UI again.\",\n\t\t\t...(options?.cause !== undefined ? { cause: options.cause } : {}),\n\t\t});\n\t\tthis.name = \"SessionError\";\n\t}\n}\n\n/**\n * Error starting or operating the UI server.\n */\nexport class ServerError extends McpUiError {\n\tconstructor(reason: string, options?: { port?: number; cause?: Error }) {\n\t\tsuper(`UI server error: ${reason}`, {\n\t\t\tcode: \"SERVER_ERROR\",\n\t\t\tcontext: options?.port !== undefined ? { port: options.port } : {},\n\t\t\trecoveryHint: \"Check if the port is available. Another process may be using it.\",\n\t\t\t...(options?.cause !== undefined ? { cause: options.cause } : {}),\n\t\t});\n\t\tthis.name = \"ServerError\";\n\t}\n}\n\n/**\n * Error communicating with the MCP server.\n */\nexport class McpServerError extends McpUiError {\n\tconstructor(server: string, reason: string, options?: { tool?: string; cause?: Error }) {\n\t\tsuper(`MCP server \"${server}\" error: ${reason}`, {\n\t\t\tcode: \"MCP_SERVER_ERROR\",\n\t\t\tcontext: { server, ...(options?.tool !== undefined ? { tool: options.tool } : {}) },\n\t\t\trecoveryHint: \"Check that the MCP server is running and responsive.\",\n\t\t\t...(options?.cause !== undefined ? { cause: options.cause } : {}),\n\t\t});\n\t\tthis.name = \"McpServerError\";\n\t}\n}\n\n/**\n * Wrap an unknown error into an McpUiError.\n */\nexport function wrapError(error: unknown, context?: McpUiErrorContext): McpUiError {\n\tif (error instanceof McpUiError) {\n\t\t// Preserve the original subtype and subtype-specific fields (for example\n\t\t// ConsentError.denied) while enriching its mutable context record.\n\t\tif (context) Object.assign(error.context, context);\n\t\treturn error;\n\t}\n\n\tconst cause = error instanceof Error ? error : undefined;\n\tconst message = error instanceof Error ? error.message : String(error);\n\n\treturn new McpUiError(message, {\n\t\tcode: \"UNKNOWN_ERROR\",\n\t\t...(context !== undefined ? { context } : {}),\n\t\t...(cause !== undefined ? { cause } : {}),\n\t});\n}\n\n/**\n * Check if an error is a specific MCP UI error type.\n */\nexport function isErrorCode(error: unknown, code: string): boolean {\n\treturn error instanceof McpUiError && error.code === code;\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function isGlimpseAvailable(): boolean;
|
|
2
|
+
export declare function openGlimpseWindow(html: string, options: {
|
|
3
|
+
title: string;
|
|
4
|
+
width?: number;
|
|
5
|
+
height?: number;
|
|
6
|
+
onClosed: () => void;
|
|
7
|
+
}): Promise<{
|
|
8
|
+
close: () => void;
|
|
9
|
+
}>;
|
|
10
|
+
//# sourceMappingURL=glimpse-ui.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glimpse-ui.d.ts","sourceRoot":"","sources":["../../../src/extensions/mcp/glimpse-ui.ts"],"names":[],"mappings":"AAmBA,wBAAgB,kBAAkB,IAAI,OAAO,CAW5C;AAyBD,wBAAsB,iBAAiB,CACtC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;IACR,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,IAAI,CAAC;CACrB;;GAyBD","sourcesContent":["import { execFileSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { platform } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\ninterface GlimpseWindow {\n\ton(event: \"closed\", callback: () => void): void;\n\tclose(): void;\n}\n\ninterface GlimpseModule {\n\topen(html: string, options: { width: number; height: number; title: string }): GlimpseWindow;\n}\n\nconst require = createRequire(import.meta.url);\nlet glimpseAvailable: boolean | null = null;\nlet resolvedBinaryPath: string | null = null;\n\nexport function isGlimpseAvailable(): boolean {\n\tif (glimpseAvailable !== null) return glimpseAvailable;\n\n\tif (platform() !== \"darwin\") {\n\t\tglimpseAvailable = false;\n\t\treturn false;\n\t}\n\n\tresolvedBinaryPath = getGlimpseBinaryPath();\n\tglimpseAvailable = resolvedBinaryPath !== null;\n\treturn glimpseAvailable;\n}\n\nfunction getGlimpseBinaryPath(): string | null {\n\tif (process.env.GLIMPSE_BINARY && existsSync(process.env.GLIMPSE_BINARY)) {\n\t\treturn process.env.GLIMPSE_BINARY;\n\t}\n\n\t// Local node_modules\n\ttry {\n\t\tconst require = createRequire(import.meta.url);\n\t\tconst glimpseuiPath = require.resolve(\"glimpseui\");\n\t\tconst binaryPath = join(dirname(glimpseuiPath), \"glimpse\");\n\t\tif (existsSync(binaryPath)) return binaryPath;\n\t} catch {}\n\n\t// Global npm install\n\ttry {\n\t\tconst globalRoot = execFileSync(\"npm\", [\"root\", \"-g\"], { encoding: \"utf-8\" }).trim();\n\t\tconst binaryPath = join(globalRoot, \"glimpseui\", \"src\", \"glimpse\");\n\t\tif (existsSync(binaryPath)) return binaryPath;\n\t} catch {}\n\n\treturn null;\n}\n\nexport async function openGlimpseWindow(\n\thtml: string,\n\toptions: {\n\t\ttitle: string;\n\t\twidth?: number;\n\t\theight?: number;\n\t\tonClosed: () => void;\n\t},\n) {\n\tconst modulePath = resolvedBinaryPath ? join(dirname(resolvedBinaryPath), \"glimpse.mjs\") : \"glimpseui\";\n\tconst glimpse = require(modulePath) as GlimpseModule;\n\n\tlet active = true;\n\tconst win = glimpse.open(html, {\n\t\twidth: options.width ?? 900,\n\t\theight: options.height ?? 700,\n\t\ttitle: options.title,\n\t});\n\n\twin.on(\"closed\", () => {\n\t\tif (!active) return;\n\t\tactive = false;\n\t\toptions.onClosed();\n\t});\n\n\treturn {\n\t\tclose: () => {\n\t\t\tif (!active) return;\n\t\t\tactive = false;\n\t\t\twin.close();\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { platform } from "node:os";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
let glimpseAvailable = null;
|
|
8
|
+
let resolvedBinaryPath = null;
|
|
9
|
+
export function isGlimpseAvailable() {
|
|
10
|
+
if (glimpseAvailable !== null)
|
|
11
|
+
return glimpseAvailable;
|
|
12
|
+
if (platform() !== "darwin") {
|
|
13
|
+
glimpseAvailable = false;
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
resolvedBinaryPath = getGlimpseBinaryPath();
|
|
17
|
+
glimpseAvailable = resolvedBinaryPath !== null;
|
|
18
|
+
return glimpseAvailable;
|
|
19
|
+
}
|
|
20
|
+
function getGlimpseBinaryPath() {
|
|
21
|
+
if (process.env.GLIMPSE_BINARY && existsSync(process.env.GLIMPSE_BINARY)) {
|
|
22
|
+
return process.env.GLIMPSE_BINARY;
|
|
23
|
+
}
|
|
24
|
+
// Local node_modules
|
|
25
|
+
try {
|
|
26
|
+
const require = createRequire(import.meta.url);
|
|
27
|
+
const glimpseuiPath = require.resolve("glimpseui");
|
|
28
|
+
const binaryPath = join(dirname(glimpseuiPath), "glimpse");
|
|
29
|
+
if (existsSync(binaryPath))
|
|
30
|
+
return binaryPath;
|
|
31
|
+
}
|
|
32
|
+
catch { }
|
|
33
|
+
// Global npm install
|
|
34
|
+
try {
|
|
35
|
+
const globalRoot = execFileSync("npm", ["root", "-g"], { encoding: "utf-8" }).trim();
|
|
36
|
+
const binaryPath = join(globalRoot, "glimpseui", "src", "glimpse");
|
|
37
|
+
if (existsSync(binaryPath))
|
|
38
|
+
return binaryPath;
|
|
39
|
+
}
|
|
40
|
+
catch { }
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
export async function openGlimpseWindow(html, options) {
|
|
44
|
+
const modulePath = resolvedBinaryPath ? join(dirname(resolvedBinaryPath), "glimpse.mjs") : "glimpseui";
|
|
45
|
+
const glimpse = require(modulePath);
|
|
46
|
+
let active = true;
|
|
47
|
+
const win = glimpse.open(html, {
|
|
48
|
+
width: options.width ?? 900,
|
|
49
|
+
height: options.height ?? 700,
|
|
50
|
+
title: options.title,
|
|
51
|
+
});
|
|
52
|
+
win.on("closed", () => {
|
|
53
|
+
if (!active)
|
|
54
|
+
return;
|
|
55
|
+
active = false;
|
|
56
|
+
options.onClosed();
|
|
57
|
+
});
|
|
58
|
+
return {
|
|
59
|
+
close: () => {
|
|
60
|
+
if (!active)
|
|
61
|
+
return;
|
|
62
|
+
active = false;
|
|
63
|
+
win.close();
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=glimpse-ui.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glimpse-ui.js","sourceRoot":"","sources":["../../../src/extensions/mcp/glimpse-ui.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFileSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { platform } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\ninterface GlimpseWindow {\n\ton(event: \"closed\", callback: () => void): void;\n\tclose(): void;\n}\n\ninterface GlimpseModule {\n\topen(html: string, options: { width: number; height: number; title: string }): GlimpseWindow;\n}\n\nconst require = createRequire(import.meta.url);\nlet glimpseAvailable: boolean | null = null;\nlet resolvedBinaryPath: string | null = null;\n\nexport function isGlimpseAvailable(): boolean {\n\tif (glimpseAvailable !== null) return glimpseAvailable;\n\n\tif (platform() !== \"darwin\") {\n\t\tglimpseAvailable = false;\n\t\treturn false;\n\t}\n\n\tresolvedBinaryPath = getGlimpseBinaryPath();\n\tglimpseAvailable = resolvedBinaryPath !== null;\n\treturn glimpseAvailable;\n}\n\nfunction getGlimpseBinaryPath(): string | null {\n\tif (process.env.GLIMPSE_BINARY && existsSync(process.env.GLIMPSE_BINARY)) {\n\t\treturn process.env.GLIMPSE_BINARY;\n\t}\n\n\t// Local node_modules\n\ttry {\n\t\tconst require = createRequire(import.meta.url);\n\t\tconst glimpseuiPath = require.resolve(\"glimpseui\");\n\t\tconst binaryPath = join(dirname(glimpseuiPath), \"glimpse\");\n\t\tif (existsSync(binaryPath)) return binaryPath;\n\t} catch {}\n\n\t// Global npm install\n\ttry {\n\t\tconst globalRoot = execFileSync(\"npm\", [\"root\", \"-g\"], { encoding: \"utf-8\" }).trim();\n\t\tconst binaryPath = join(globalRoot, \"glimpseui\", \"src\", \"glimpse\");\n\t\tif (existsSync(binaryPath)) return binaryPath;\n\t} catch {}\n\n\treturn null;\n}\n\nexport async function openGlimpseWindow(\n\thtml: string,\n\toptions: {\n\t\ttitle: string;\n\t\twidth?: number;\n\t\theight?: number;\n\t\tonClosed: () => void;\n\t},\n) {\n\tconst modulePath = resolvedBinaryPath ? join(dirname(resolvedBinaryPath), \"glimpse.mjs\") : \"glimpseui\";\n\tconst glimpse = require(modulePath) as GlimpseModule;\n\n\tlet active = true;\n\tconst win = glimpse.open(html, {\n\t\twidth: options.width ?? 900,\n\t\theight: options.height ?? 700,\n\t\ttitle: options.title,\n\t});\n\n\twin.on(\"closed\", () => {\n\t\tif (!active) return;\n\t\tactive = false;\n\t\toptions.onClosed();\n\t});\n\n\treturn {\n\t\tclose: () => {\n\t\t\tif (!active) return;\n\t\t\tactive = false;\n\t\t\twin.close();\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { UiHostContext, UiResourceContent, UiResourceCsp } from "./types.ts";
|
|
2
|
+
export interface HostHtmlTemplateInput {
|
|
3
|
+
sessionToken: string;
|
|
4
|
+
uiResourceToken: string;
|
|
5
|
+
serverName: string;
|
|
6
|
+
toolName: string;
|
|
7
|
+
toolArgs: Record<string, unknown>;
|
|
8
|
+
resource: UiResourceContent;
|
|
9
|
+
allowAttribute: string;
|
|
10
|
+
requireToolConsent: boolean;
|
|
11
|
+
cacheToolConsent: boolean;
|
|
12
|
+
hostContext?: UiHostContext;
|
|
13
|
+
appBridgeModuleUrl?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function buildHostHtmlTemplate(input: HostHtmlTemplateInput): string;
|
|
16
|
+
export declare function buildCspMetaContent(csp: UiResourceCsp | undefined): string;
|
|
17
|
+
//# sourceMappingURL=host-html-template.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-html-template.d.ts","sourceRoot":"","sources":["../../../src/extensions/mcp/host-html-template.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAMlF,MAAM,WAAW,qBAAqB;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,WAAW,CAAC,EAAE,aAAa,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,qBAAqB,GAAG,MAAM,CAmY1E;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,aAAa,GAAG,SAAS,GAAG,MAAM,CAoB1E","sourcesContent":["import { VERSION } from \"../../config.ts\";\nimport type { UiHostContext, UiResourceContent, UiResourceCsp } from \"./types.ts\";\n\n// Use locally bundled AppBridge to avoid CDN Zod bundling issues\nconst DEFAULT_APP_BRIDGE_MODULE_URL = \"/app-bridge.bundle.js\";\nconst APP_SANDBOX = \"allow-scripts allow-forms allow-modals allow-popups allow-downloads\";\n\nexport interface HostHtmlTemplateInput {\n\tsessionToken: string;\n\tuiResourceToken: string;\n\tserverName: string;\n\ttoolName: string;\n\ttoolArgs: Record<string, unknown>;\n\tresource: UiResourceContent;\n\tallowAttribute: string;\n\trequireToolConsent: boolean;\n\tcacheToolConsent: boolean;\n\thostContext?: UiHostContext;\n\tappBridgeModuleUrl?: string;\n}\n\nexport function buildHostHtmlTemplate(input: HostHtmlTemplateInput): string {\n\tconst hostContext = input.hostContext ?? {};\n\n\tconst sessionToken = safeInlineJSON(input.sessionToken);\n\tconst uiResourceToken = safeInlineJSON(input.uiResourceToken);\n\tconst toolArgs = safeInlineJSON(input.toolArgs);\n\tconst serverName = safeInlineJSON(input.serverName);\n\tconst toolName = safeInlineJSON(input.toolName);\n\tconst hostContextJson = safeInlineJSON(hostContext);\n\tconst allowAttribute = safeInlineJSON(input.allowAttribute);\n\tconst requireToolConsent = safeInlineJSON(input.requireToolConsent);\n\tconst cacheToolConsent = safeInlineJSON(input.cacheToolConsent);\n\tconst moduleUrl = safeInlineJSON(input.appBridgeModuleUrl ?? DEFAULT_APP_BRIDGE_MODULE_URL);\n\tconst nexusVersion = safeInlineJSON(VERSION);\n\n\treturn `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>MCP UI - ${escapeHtml(input.serverName)} / ${escapeHtml(input.toolName)}</title>\n <style>\n :root {\n color-scheme: light dark;\n --bg: #0f1115;\n --surface: #181c22;\n --text: #ecf0f5;\n --muted: #a9b2bf;\n --accent: #43c0ff;\n --border: rgba(255, 255, 255, 0.12);\n --good: #34d399;\n --warn: #fbbf24;\n --bad: #f87171;\n }\n @media (prefers-color-scheme: light) {\n :root {\n --bg: #f6f7fb;\n --surface: #ffffff;\n --text: #1d2939;\n --muted: #667085;\n --accent: #0ea5e9;\n --border: rgba(15, 23, 42, 0.14);\n --good: #059669;\n --warn: #b45309;\n --bad: #b91c1c;\n }\n }\n * { box-sizing: border-box; }\n html, body { margin: 0; padding: 0; height: 100%; font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; background: var(--bg); color: var(--text); }\n body { display: flex; flex-direction: column; min-height: 100vh; min-height: 100dvh; }\n header { background: var(--surface); border-bottom: 1px solid var(--border); padding: calc(10px + env(safe-area-inset-top, 0px)) calc(14px + env(safe-area-inset-right, 0px)) 10px calc(14px + env(safe-area-inset-left, 0px)); display: flex; align-items: center; justify-content: space-between; gap: 10px; }\n .title { display: flex; gap: 8px; align-items: baseline; min-width: 0; }\n .server { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em; white-space: nowrap; }\n .tool { font-size: 14px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .badge { border: 1px solid var(--border); border-radius: 999px; padding: 2px 8px; font-size: 11px; color: var(--muted); white-space: nowrap; }\n .controls { display: flex; gap: 8px; align-items: center; }\n .status { font-size: 12px; color: var(--muted); white-space: nowrap; }\n button { border: 1px solid var(--border); background: transparent; color: var(--text); border-radius: 8px; padding: 6px 10px; cursor: pointer; font-size: 12px; }\n button.primary { border-color: color-mix(in srgb, var(--good) 40%, var(--border) 60%); color: var(--good); }\n button.danger { border-color: color-mix(in srgb, var(--bad) 40%, var(--border) 60%); color: var(--bad); }\n button:hover { background: color-mix(in srgb, var(--surface) 75%, var(--accent) 25%); }\n main { flex: 1; min-height: 0; padding: 10px; padding-inline: calc(10px + env(safe-area-inset-left, 0px)) calc(10px + env(safe-area-inset-right, 0px)); padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px)); display: flex; }\n iframe { width: 100%; height: 100%; border: 1px solid var(--border); border-radius: 10px; background: white; }\n .overlay { position: fixed; inset: 0; background: color-mix(in srgb, var(--bg) 90%, black 10%); display: none; align-items: center; justify-content: center; z-index: 2; padding: 16px; }\n .overlay.visible { display: flex; }\n .panel { width: min(680px, calc(100vw - 40px)); background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 18px; }\n .panel h2 { margin: 0 0 8px; font-size: 16px; }\n .panel p { margin: 0; color: var(--muted); line-height: 1.4; font-size: 14px; white-space: pre-wrap; }\n @media (max-width: 640px) {\n header { align-items: stretch; flex-direction: column; gap: 8px; }\n .title { flex-wrap: wrap; row-gap: 4px; }\n .server { flex-basis: 100%; }\n .controls { width: 100%; }\n .status { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }\n button { min-height: 44px; padding: 10px 14px; }\n main { padding: 6px; padding-inline: calc(6px + env(safe-area-inset-left, 0px)) calc(6px + env(safe-area-inset-right, 0px)); padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px)); }\n iframe { border-radius: 6px; }\n .panel { width: 100%; }\n }\n </style>\n</head>\n<body>\n <header>\n <div class=\"title\">\n <span class=\"server\">MCP · <span id=\"server-name\"></span></span>\n <span class=\"tool\" id=\"tool-name\"></span>\n <span class=\"badge\">Sandboxed</span>\n </div>\n <div class=\"controls\">\n <span class=\"status\" id=\"status\">Loading UI...</span>\n <button class=\"primary\" id=\"done-btn\" title=\"Cmd/Ctrl+Enter\">Done</button>\n <button class=\"danger\" id=\"cancel-btn\" title=\"Escape\">Cancel</button>\n </div>\n </header>\n <main>\n <iframe id=\"mcp-app\" sandbox=\"${APP_SANDBOX}\" referrerpolicy=\"no-referrer\"></iframe>\n </main>\n <div class=\"overlay\" id=\"error-overlay\">\n <div class=\"panel\">\n <h2>UI Error</h2>\n <p id=\"error-message\"></p>\n </div>\n </div>\n <div class=\"overlay\" id=\"completion-overlay\">\n <div class=\"panel\">\n <h2>Done</h2>\n <p>MCP UI session finished. You can close this page and return to Nexus.</p>\n </div>\n </div>\n <script type=\"module\">\n import { AppBridge, PostMessageTransport } from ${moduleUrl};\n\n const SESSION_TOKEN = ${sessionToken};\n const UI_RESOURCE_TOKEN = ${uiResourceToken};\n const SERVER_NAME = ${serverName};\n const TOOL_NAME = ${toolName};\n const TOOL_ARGS = ${toolArgs};\n const HOST_CONTEXT = ${hostContextJson};\n const ALLOW_ATTRIBUTE = ${allowAttribute};\n const REQUIRE_TOOL_CONSENT = ${requireToolConsent};\n const CACHE_TOOL_CONSENT = ${cacheToolConsent};\n const STREAM_CONTEXT_KEY = \"nexus-mcp/stream\";\n const STREAM_PATCH_METHOD = \"notifications/nexus-mcp/ui-result-patch\";\n\n const iframe = document.getElementById(\"mcp-app\");\n const statusNode = document.getElementById(\"status\");\n const doneBtn = document.getElementById(\"done-btn\");\n const cancelBtn = document.getElementById(\"cancel-btn\");\n const errorOverlay = document.getElementById(\"error-overlay\");\n const completionOverlay = document.getElementById(\"completion-overlay\");\n const errorMessage = document.getElementById(\"error-message\");\n\n document.getElementById(\"server-name\").textContent = SERVER_NAME;\n document.getElementById(\"tool-name\").textContent = TOOL_NAME;\n\n const setStatus = (text, isError = false) => {\n statusNode.textContent = text;\n statusNode.style.color = isError ? \"var(--bad)\" : \"var(--muted)\";\n };\n\n const showError = (message) => {\n errorMessage.textContent = message;\n errorOverlay.classList.add(\"visible\");\n setStatus(\"Error\", true);\n };\n\n let completionPending = false;\n const showCompletion = () => {\n completionOverlay.classList.add(\"visible\");\n setStatus(\"Complete\");\n };\n const closeOrShowDone = () => {\n completionPending = true;\n window.close();\n setTimeout(() => {\n if (!document.hidden) {\n showCompletion();\n }\n }, 1000);\n };\n document.addEventListener(\"visibilitychange\", () => {\n if (completionPending && !document.hidden) {\n showCompletion();\n }\n });\n\n const post = async (endpoint, params) => {\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ token: SESSION_TOKEN, params }),\n });\n\n const body = await response.json().catch(() => ({ ok: false, error: \"Invalid JSON response\" }));\n if (!response.ok || !body.ok) {\n const message = body.error || (\"HTTP \" + response.status);\n throw new Error(message);\n }\n return body.result ?? {};\n };\n\n let consentGranted = !REQUIRE_TOOL_CONSENT;\n const initialStreamContext = HOST_CONTEXT?.[STREAM_CONTEXT_KEY];\n const streamMode = initialStreamContext?.mode === \"stream-first\" ? \"stream-first\" : \"eager\";\n\n const bridge = new AppBridge(\n null,\n { name: \"nexus\", version: ${nexusVersion} },\n { serverTools: {}, openLinks: {}, logging: {}, updateModelContext: {}, message: {} },\n { hostContext: HOST_CONTEXT }\n );\n\n bridge.oncalltool = async (params) => {\n if (!consentGranted) {\n const accepted = window.confirm(\"Allow this UI to call server tools for this session?\");\n if (!accepted) {\n await post(\"/proxy/ui/consent\", { approved: false }).catch(() => {});\n return {\n isError: true,\n content: [{ type: \"text\", text: \"Tool call denied by user.\" }],\n };\n }\n await post(\"/proxy/ui/consent\", { approved: true });\n if (CACHE_TOOL_CONSENT) {\n consentGranted = true;\n }\n }\n const result = await post(\"/proxy/tools/call\", params);\n // Notify agent about the tool call\n await post(\"/proxy/ui/generated-tool-call-intent\", {\n tool: params.name,\n arguments: params.arguments,\n isError: result.isError\n }).catch(() => {});\n return result;\n };\n\n bridge.onmessage = async (params) => post(\"/proxy/ui/message\", params);\n bridge.onupdatemodelcontext = async (params) => post(\"/proxy/ui/context\", params);\n\n // Also listen for raw postMessage events with custom types (notify, prompt, intent, etc.)\n // These bypass the AppBridge protocol but are used by some MCP UI implementations\n window.addEventListener(\"message\", async (event) => {\n if (event.source !== iframe.contentWindow) return;\n const data = event.data;\n if (!data || typeof data !== \"object\") return;\n\n // Skip AppBridge protocol messages (handled by bridge)\n if (data.jsonrpc || (typeof data.method === \"string\" && (data.method.startsWith(\"app/\") || data.method.startsWith(\"host/\")))) return;\n\n // Handle raw UI action messages\n const msgType = data.type;\n if (typeof msgType !== \"string\") return;\n\n if (msgType === \"notify\" || msgType === \"prompt\" || msgType === \"intent\" || msgType === \"message\") {\n // Standard MCP-UI types - preserve their semantics\n // Support both { type, payload: {...} } and { type, field: value } formats\n const { type: _, payload, ...directFields } = data;\n await post(\"/proxy/ui/message\", { type: msgType, ...directFields, ...(payload || {}) }).catch(() => {});\n } else if (!msgType.startsWith(\"ui-lifecycle-\") && !msgType.startsWith(\"ui-message-\")) {\n // Any other custom type - forward as intent with type as intent name\n // (Skip internal lifecycle/ack messages)\n const payload = data.payload || {};\n await post(\"/proxy/ui/message\", {\n type: \"intent\",\n intent: msgType,\n params: payload,\n }).catch(() => {});\n }\n });\n bridge.ondownloadfile = async (params) => post(\"/proxy/ui/download-file\", params);\n bridge.onrequestdisplaymode = async (params) => post(\"/proxy/ui/request-display-mode\", params);\n bridge.onopenlink = async (params) => {\n\t let externalUrl;\n\t try {\n\t\texternalUrl = new URL(params.url);\n\t } catch {\n\t\treturn { isError: true };\n\t }\n\t if ((externalUrl.protocol !== \"http:\" && externalUrl.protocol !== \"https:\") || externalUrl.username || externalUrl.password) {\n\t\treturn { isError: true };\n\t }\n const result = await post(\"/proxy/ui/open-link\", params);\n if (!result.isError) {\n\t\twindow.open(externalUrl.href, \"_blank\", \"noopener,noreferrer\");\n // Notify agent about the link open\n await post(\"/proxy/ui/message\", {\n type: \"intent\",\n intent: \"open_link\",\n params: { url: params.url }\n }).catch(() => {});\n }\n return result;\n };\n\n bridge.oninitialized = () => {\n if (streamMode !== \"stream-first\") {\n bridge.sendToolInput({ arguments: TOOL_ARGS });\n }\n setStatus(streamMode === \"stream-first\" ? \"Streaming…\" : \"Connected\");\n };\n\n bridge.onsizechange = ({ width, height }) => {\n if (typeof width === \"number\" && width > 0) {\n iframe.style.minWidth = Math.min(width, window.innerWidth - 24) + \"px\";\n }\n if (typeof height === \"number\" && height > 0) {\n iframe.style.height = Math.max(height, 320) + \"px\";\n }\n };\n\n if (ALLOW_ATTRIBUTE) {\n iframe.setAttribute(\"allow\", ALLOW_ATTRIBUTE);\n }\n\n // Connect bridge BEFORE loading iframe to ensure we're listening when the app sends ui/initialize\n try {\n const transport = new PostMessageTransport(iframe.contentWindow, window);\n await bridge.connect(transport);\n } catch (error) {\n console.error(\"[host] Bridge connection failed:\", error);\n showError(\"Failed to initialize AppBridge: \" + String(error));\n }\n\n const iframeLoaded = new Promise((resolve) => {\n iframe.onload = resolve;\n });\n iframe.src = \"/ui-app?resource=\" + encodeURIComponent(UI_RESOURCE_TOKEN);\n await iframeLoaded;\n\n const eventSource = new EventSource(\"/events?session=\" + encodeURIComponent(SESSION_TOKEN));\n eventSource.addEventListener(\"tool-input\", (event) => {\n try {\n bridge.sendToolInput(JSON.parse(event.data));\n } catch (error) {\n showError(\"Failed to forward tool input: \" + String(error));\n }\n });\n eventSource.addEventListener(\"tool-result\", (event) => {\n try {\n bridge.sendToolResult(JSON.parse(event.data));\n } catch (error) {\n showError(\"Failed to forward tool result: \" + String(error));\n }\n });\n eventSource.addEventListener(\"tool-cancelled\", (event) => {\n try {\n bridge.sendToolCancelled(JSON.parse(event.data));\n } catch (error) {\n showError(\"Failed to forward cancellation: \" + String(error));\n }\n });\n eventSource.addEventListener(\"result-patch\", async (event) => {\n try {\n await bridge.notification({\n method: STREAM_PATCH_METHOD,\n params: JSON.parse(event.data),\n });\n } catch (error) {\n showError(\"Failed to forward stream patch: \" + String(error));\n }\n });\n eventSource.addEventListener(\"host-context\", (event) => {\n try {\n bridge.setHostContext(JSON.parse(event.data));\n } catch {}\n });\n eventSource.addEventListener(\"session-complete\", async () => {\n await bridge.teardownResource({}).catch(() => {});\n clearInterval(heartbeat);\n eventSource.close();\n closeOrShowDone();\n });\n eventSource.onerror = () => {\n setStatus(\"Connection lost\", true);\n };\n\n const heartbeat = setInterval(() => {\n post(\"/proxy/ui/heartbeat\", {}).catch(() => {});\n }, 10000);\n\n const complete = async (reason) => {\n try {\n await post(\"/proxy/ui/complete\", { reason });\n } catch {}\n try {\n await bridge.teardownResource({});\n } catch {}\n clearInterval(heartbeat);\n eventSource.close();\n closeOrShowDone();\n };\n\n doneBtn.addEventListener(\"click\", () => complete(\"done\"));\n cancelBtn.addEventListener(\"click\", () => complete(\"cancel\"));\n window.addEventListener(\"keydown\", (event) => {\n if (event.key === \"Escape\") {\n event.preventDefault();\n complete(\"cancel\");\n } else if ((event.metaKey || event.ctrlKey) && event.key === \"Enter\") {\n event.preventDefault();\n complete(\"done\");\n }\n });\n </script>\n</body>\n</html>`;\n}\n\nexport function buildCspMetaContent(csp: UiResourceCsp | undefined): string {\n\tconst resourceDomains = sanitizeCspDomains(csp?.resourceDomains);\n\tconst connectDomains = sanitizeCspDomains(csp?.connectDomains);\n\tconst frameDomains = sanitizeCspDomains(csp?.frameDomains);\n\tconst baseUriDomains = sanitizeCspDomains(csp?.baseUriDomains);\n\n\treturn [\n\t\t\"default-src 'none'\",\n\t\t`sandbox ${APP_SANDBOX}`,\n\t\ttoDirective(\"script-src\", [\"'self'\", \"'unsafe-inline'\"], resourceDomains),\n\t\ttoDirective(\"style-src\", [\"'self'\", \"'unsafe-inline'\"], resourceDomains),\n\t\ttoDirective(\"font-src\", [\"'self'\"], resourceDomains),\n\t\ttoDirective(\"img-src\", [\"'self'\", \"data:\"], resourceDomains),\n\t\ttoDirective(\"media-src\", [\"'self'\", \"data:\"], resourceDomains),\n\t\tconnectDomains.length > 0 ? `connect-src ${connectDomains.join(\" \")}` : \"connect-src 'none'\",\n\t\tframeDomains.length > 0 ? `frame-src ${frameDomains.join(\" \")}` : \"frame-src 'none'\",\n\t\t\"worker-src 'none'\",\n\t\t\"object-src 'none'\",\n\t\tbaseUriDomains.length > 0 ? `base-uri ${baseUriDomains.join(\" \")}` : \"base-uri 'self'\",\n\t].join(\"; \");\n}\n\nfunction toDirective(name: string, trustedSources: string[], domains: string[]): string {\n\treturn `${name} ${[...new Set([...trustedSources, ...domains])].join(\" \")}`;\n}\n\nfunction sanitizeCspDomains(domains: unknown): string[] {\n\tif (!Array.isArray(domains)) return [];\n\n\treturn [\n\t\t...new Set(\n\t\t\tdomains.filter(\n\t\t\t\t(domain): domain is string =>\n\t\t\t\t\ttypeof domain === \"string\" &&\n\t\t\t\t\tdomain.length > 0 &&\n\t\t\t\t\t// HTTP headers must be printable ASCII; rejecting all other code points also\n\t\t\t\t\t// excludes every C0/C1 control character before Node serializes the policy.\n\t\t\t\t\t/^[\\x21-\\x7E]+$/.test(domain) &&\n\t\t\t\t\t!/[;'\"]/.test(domain),\n\t\t\t),\n\t\t),\n\t];\n}\n\nfunction safeInlineJSON(value: unknown): string {\n\treturn JSON.stringify(value)\n\t\t.replace(/</g, \"\\\\u003c\")\n\t\t.replace(/>/g, \"\\\\u003e\")\n\t\t.replace(/&/g, \"\\\\u0026\")\n\t\t.replace(/\\u2028/g, \"\\\\u2028\")\n\t\t.replace(/\\u2029/g, \"\\\\u2029\");\n}\n\nfunction escapeHtml(value: string): string {\n\treturn value\n\t\t.replace(/&/g, \"&\")\n\t\t.replace(/</g, \"<\")\n\t\t.replace(/>/g, \">\")\n\t\t.replace(/\"/g, \""\")\n\t\t.replace(/'/g, \"'\");\n}\n"]}
|