@gajae-code/coding-agent 0.18.7 → 0.18.9
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 +377 -1
- package/dist/types/async/endpoint-id.d.ts +16 -0
- package/dist/types/async/job-manager.d.ts +1 -1
- package/dist/types/async/support.d.ts +0 -14
- package/dist/types/capability/mcp.d.ts +2 -0
- package/dist/types/capability/skill.d.ts +2 -0
- package/dist/types/capability/types.d.ts +2 -0
- package/dist/types/cli/public-command-errors.d.ts +3 -1
- package/dist/types/cli/setup-cli.d.ts +1 -1
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/commands/mcp-serve.d.ts +4 -0
- package/dist/types/config/autorouting-tier-map.d.ts +348 -9
- package/dist/types/config/file-lock.d.ts +1 -0
- package/dist/types/config/model-bindings-applier.d.ts +2 -0
- package/dist/types/config/model-profile-activation.d.ts +11 -3
- package/dist/types/config/model-registry.d.ts +4 -9
- package/dist/types/config/skill-settings-defaults.d.ts +2 -0
- package/dist/types/cursor-delete-path.d.ts +1 -0
- package/dist/types/cursor-mcp-name.d.ts +8 -0
- package/dist/types/customization/inventory.d.ts +2 -0
- package/dist/types/debug/report-bundle-parent.d.ts +2 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +1 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.d.ts +31 -0
- package/dist/types/defaults/gjc-skills.generated.d.ts +5 -6
- package/dist/types/discovery/helpers.d.ts +6 -1
- package/dist/types/discovery/mcp-json.d.ts +1 -0
- package/dist/types/eval/py/kernel.d.ts +18 -0
- package/dist/types/export/html/css-color.d.ts +2 -0
- package/dist/types/export/html/html-export-destination.d.ts +2 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +5 -0
- package/dist/types/extensibility/extensions/types.d.ts +7 -0
- package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +14 -1
- package/dist/types/extensibility/plugins/marketplace/cache.d.ts +2 -0
- package/dist/types/extensibility/plugins/plugin-link-path.d.ts +2 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +3 -1
- package/dist/types/extensibility/skill-management.d.ts +4 -1
- package/dist/types/extensibility/skills.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-crystallize.d.ts +74 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +11 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +5 -0
- package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -3
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +0 -5
- package/dist/types/gjc-runtime/sidecar-bootstrap-env.d.ts +6 -0
- package/dist/types/gjc-runtime/state-argv.d.ts +1 -1
- package/dist/types/gjc-runtime/state-migrations.d.ts +8 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +95 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +14 -1
- package/dist/types/gjc-runtime/tmux-common.d.ts +8 -5
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -0
- package/dist/types/internal-urls/skill-path.d.ts +1 -0
- package/dist/types/lsp/config.d.ts +8 -5
- package/dist/types/lsp/workspace-path.d.ts +1 -0
- package/dist/types/modes/acp/acp-agent.d.ts +7 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -0
- package/dist/types/modes/components/customization/customization-dashboard.d.ts +1 -0
- package/dist/types/modes/components/extensions/state-manager.d.ts +4 -3
- package/dist/types/modes/controllers/input-controller.d.ts +9 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/utils/interrupt-ownership.d.ts +3 -0
- package/dist/types/modes/utils/keybinding-matchers.d.ts +1 -0
- package/dist/types/modes/utils/macos-option-forwarding.d.ts +17 -0
- package/dist/types/progress/collect-project-progress.d.ts +38 -0
- package/dist/types/progress/progress-contract.d.ts +154 -0
- package/dist/types/progress/progress-snapshot.d.ts +11 -0
- package/dist/types/progress/project-progress.d.ts +141 -0
- package/dist/types/progress/render-progress.d.ts +28 -0
- package/dist/types/runtime-mcp/manager.d.ts +2 -1
- package/dist/types/runtime-mcp/mcp-redirect.d.ts +9 -0
- package/dist/types/runtime-mcp/oauth-public-url.d.ts +3 -0
- package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +1 -0
- package/dist/types/runtime-mcp/project-host-values.d.ts +27 -0
- package/dist/types/runtime-mcp/smithery-registry.d.ts +24 -0
- package/dist/types/runtime-mcp/types.d.ts +6 -0
- package/dist/types/sdk/acp/adapter.d.ts +1 -1
- package/dist/types/sdk/broker/broker-exit.d.ts +6 -2
- package/dist/types/sdk/broker/broker.d.ts +24 -2
- package/dist/types/sdk/broker/doctor-restart.d.ts +5 -0
- package/dist/types/sdk/broker/ensure.d.ts +14 -1
- package/dist/types/sdk/broker/session-index.d.ts +12 -6
- package/dist/types/sdk/bus/index.d.ts +12 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -1
- package/dist/types/sdk/client/client.d.ts +2 -0
- package/dist/types/sdk/host/query/handlers.d.ts +3 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +2 -0
- package/dist/types/sdk/session-list.d.ts +1 -1
- package/dist/types/sdk/session.d.ts +8 -0
- package/dist/types/session/agent-session.d.ts +38 -9
- package/dist/types/session/artifacts.d.ts +2 -2
- package/dist/types/session/internal/managed-gc-retirement-codec.d.ts +32 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +79 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +26 -1
- package/dist/types/session/internal/managed-task-owner-cleanup.d.ts +62 -0
- package/dist/types/session/internal/task-artifact-owner-access.d.ts +2 -0
- package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +9 -1
- package/dist/types/session/session-manager.d.ts +31 -5
- package/dist/types/session/session-retirement.d.ts +31 -7
- package/dist/types/session/session-storage.d.ts +69 -2
- package/dist/types/session/task-artifact-owner-codec.d.ts +2 -0
- package/dist/types/setup/credential-auto-import.d.ts +1 -1
- package/dist/types/setup/credential-import.d.ts +6 -4
- package/dist/types/skill-state/active-state.d.ts +38 -0
- package/dist/types/skill-state/workflow-mutation-guard.d.ts +3 -0
- package/dist/types/slash-commands/helpers/progress-report.d.ts +8 -0
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/isolation-dir.d.ts +26 -0
- package/dist/types/tools/ast-edit-write-path.d.ts +13 -0
- package/dist/types/tools/bash-skill-urls.d.ts +1 -1
- package/dist/types/tools/browser/cdp-url.d.ts +2 -0
- package/dist/types/tools/github-side-effect.d.ts +22 -0
- package/dist/types/tools/index.d.ts +3 -0
- package/dist/types/tools/monitor-mutation.d.ts +9 -0
- package/dist/types/tools/python.d.ts +9 -1
- package/dist/types/tools/recipe/recipe-guard.d.ts +7 -0
- package/dist/types/tools/restricted-cwd.d.ts +1 -0
- package/dist/types/tools/todo-write.d.ts +6 -0
- package/dist/types/utils/tools-checksum.d.ts +7 -0
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +21 -0
- package/scripts/verify-managed-empty-stop.ts +28 -0
- package/src/async/endpoint-id.ts +26 -0
- package/src/async/job-manager.ts +247 -132
- package/src/async/support.ts +0 -25
- package/src/capability/index.ts +15 -0
- package/src/capability/mcp.ts +2 -0
- package/src/capability/skill.ts +2 -0
- package/src/capability/types.ts +2 -0
- package/src/cli/auth-broker-cli.ts +1 -1
- package/src/cli/customize-doctor.ts +15 -2
- package/src/cli/doctor/repairs.ts +4 -1
- package/src/cli/doctor-supervisor.ts +3 -0
- package/src/cli/public-command-errors.ts +21 -1
- package/src/cli/public-command-registry.ts +28 -6
- package/src/cli/setup-cli.ts +4 -4
- package/src/cli/skills-cli.ts +5 -0
- package/src/commands/deep-interview.ts +47 -8
- package/src/commands/mcp-serve.ts +7 -0
- package/src/commands/sdk.ts +455 -397
- package/src/config/autorouting-tier-map.ts +117 -7
- package/src/config/file-lock.ts +316 -79
- package/src/config/mcp-schema.json +4 -0
- package/src/config/model-bindings-applier.ts +20 -0
- package/src/config/model-profile-activation.ts +240 -46
- package/src/config/model-registry.ts +47 -153
- package/src/config/skill-settings-defaults.ts +7 -0
- package/src/coordinator-mcp/policy.ts +3 -2
- package/src/coordinator-mcp/server.ts +2 -0
- package/src/cursor-delete-path.ts +10 -0
- package/src/cursor-mcp-name.ts +15 -0
- package/src/cursor.ts +13 -2
- package/src/customization/inventory.ts +4 -13
- package/src/dap/config.ts +2 -2
- package/src/debug/report-bundle-parent.ts +22 -0
- package/src/debug/report-bundle.ts +3 -0
- package/src/debug/system-info.ts +13 -1
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +10 -6
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +67 -4
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.ts +80 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +105 -10
- package/src/defaults/gjc/skills/ralplan/SKILL.md +6 -4
- package/src/defaults/gjc-defaults.test.ts +18 -1
- package/src/defaults/gjc-defaults.ts +3 -0
- package/src/defaults/gjc-skills.generated.ts +58 -36
- package/src/discovery/agents.ts +6 -1
- package/src/discovery/builtin.ts +13 -1
- package/src/discovery/helpers.ts +171 -29
- package/src/discovery/mcp-json.ts +26 -12
- package/src/discovery/ssh.ts +1 -1
- package/src/discovery/substitute-plugin-root.ts +3 -1
- package/src/edit/file-read-cache.ts +1 -1
- package/src/eval/py/executor.ts +796 -154
- package/src/eval/py/kernel.ts +79 -13
- package/src/eval/py/tool-bridge.ts +4 -0
- package/src/export/html/css-color.ts +9 -0
- package/src/export/html/html-export-destination.ts +16 -0
- package/src/export/html/index.ts +7 -10
- package/src/export/html/template.generated.ts +1 -1
- package/src/extensibility/extensions/function-hooks-internal.ts +27 -0
- package/src/extensibility/extensions/runner.ts +15 -1
- package/src/extensibility/extensions/types.ts +7 -0
- package/src/extensibility/gjc-plugins/mcp-policy.ts +2 -1
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +85 -8
- package/src/extensibility/plugins/manager.ts +3 -2
- package/src/extensibility/plugins/marketplace/cache.ts +15 -0
- package/src/extensibility/plugins/marketplace/manager.ts +11 -3
- package/src/extensibility/plugins/plugin-link-path.ts +12 -0
- package/src/extensibility/runtime-skill-discovery.ts +20 -2
- package/src/extensibility/skill-management.ts +9 -2
- package/src/extensibility/skills.ts +6 -1
- package/src/gjc-runtime/deep-interview-crystallize.ts +1814 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +20 -2
- package/src/gjc-runtime/deep-interview-runtime.ts +1415 -13
- package/src/gjc-runtime/deep-interview-stage.ts +98 -21
- package/src/gjc-runtime/gc-runtime.ts +206 -9
- package/src/gjc-runtime/launch-worktree.ts +70 -9
- package/src/gjc-runtime/psmux-detect.ts +28 -4
- package/src/gjc-runtime/ralplan-runtime.ts +447 -52
- package/src/gjc-runtime/session-state-sidecar.ts +3 -2
- package/src/gjc-runtime/sidecar-bootstrap-env.ts +19 -0
- package/src/gjc-runtime/state-argv.ts +2 -0
- package/src/gjc-runtime/state-migrations.ts +91 -29
- package/src/gjc-runtime/state-runtime.ts +3716 -135
- package/src/gjc-runtime/state-writer.ts +105 -16
- package/src/gjc-runtime/tmux-common.ts +8 -5
- package/src/gjc-runtime/ultragoal-runtime.ts +16 -4
- package/src/gjc-runtime/workflow-manifest.generated.json +70 -42
- package/src/gjc-runtime/workflow-manifest.ts +54 -21
- package/src/harness-control-plane/operate.ts +12 -4
- package/src/harness-control-plane/owner.ts +5 -1
- package/src/hashline/parser.ts +48 -9
- package/src/hashline/recovery.ts +13 -4
- package/src/internal-urls/docs-index.generated.ts +20 -19
- package/src/internal-urls/skill-path.ts +19 -0
- package/src/internal-urls/skill-protocol.ts +2 -0
- package/src/lsp/config.ts +12 -63
- package/src/lsp/edits.ts +6 -0
- package/src/lsp/workspace-path.ts +51 -0
- package/src/main.ts +195 -31
- package/src/memories/index.ts +88 -7
- package/src/modes/acp/acp-agent.ts +88 -4
- package/src/modes/components/custom-editor.ts +14 -0
- package/src/modes/components/customization/customization-dashboard.ts +12 -0
- package/src/modes/components/extensions/extension-dashboard.ts +2 -2
- package/src/modes/components/extensions/state-manager.ts +15 -5
- package/src/modes/components/hook-editor.ts +1 -1
- package/src/modes/components/hook-selector.ts +1 -1
- package/src/modes/components/model-selector.ts +1 -4
- package/src/modes/components/provider-onboarding-selector.ts +1 -1
- package/src/modes/components/status-line/segments.ts +4 -0
- package/src/modes/controllers/event-controller.ts +11 -4
- package/src/modes/controllers/input-controller.ts +76 -31
- package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -16
- package/src/modes/controllers/selector-controller.ts +24 -15
- package/src/modes/controllers/todo-command-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +5 -1
- package/src/modes/runtime-init.ts +4 -0
- package/src/modes/utils/abort-message.ts +3 -1
- package/src/modes/utils/interrupt-ownership.ts +12 -0
- package/src/modes/utils/keybinding-matchers.ts +4 -0
- package/src/modes/utils/macos-option-forwarding.ts +80 -0
- package/src/progress/collect-project-progress.ts +153 -0
- package/src/progress/progress-contract.ts +172 -0
- package/src/progress/progress-snapshot.ts +140 -0
- package/src/progress/project-progress.ts +446 -0
- package/src/progress/render-progress.ts +209 -0
- package/src/prompts/system/master-mode.md +1 -0
- package/src/prompts/tools/skill-discovery.md +1 -1
- package/src/runtime-mcp/client.ts +4 -2
- package/src/runtime-mcp/config-writer.ts +10 -6
- package/src/runtime-mcp/config.ts +1 -0
- package/src/runtime-mcp/json-rpc.ts +2 -1
- package/src/runtime-mcp/manager.ts +29 -11
- package/src/runtime-mcp/mcp-redirect.ts +73 -0
- package/src/runtime-mcp/oauth-flow.ts +14 -2
- package/src/runtime-mcp/oauth-public-url.ts +14 -0
- package/src/runtime-mcp/plugin-network-boundary.ts +4 -0
- package/src/runtime-mcp/pool.ts +6 -0
- package/src/runtime-mcp/project-host-values.ts +46 -0
- package/src/runtime-mcp/smithery-registry.ts +22 -3
- package/src/runtime-mcp/transports/http.ts +4 -3
- package/src/runtime-mcp/types.ts +6 -0
- package/src/sdk/acp/adapter.ts +95 -8
- package/src/sdk/broker/broker-exit.ts +52 -23
- package/src/sdk/broker/broker.ts +81 -6
- package/src/sdk/broker/doctor-restart.ts +4 -0
- package/src/sdk/broker/ensure.ts +31 -2
- package/src/sdk/broker/lifecycle.ts +711 -66
- package/src/sdk/broker/session-index.ts +191 -81
- package/src/sdk/bus/chat-daemon-cli.ts +38 -1
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/index.ts +238 -43
- package/src/sdk/bus/telegram-daemon-contract.ts +6 -1
- package/src/sdk/cli/session-cli.ts +17 -3
- package/src/sdk/client/client.ts +28 -8
- package/src/sdk/host/query/handlers.ts +9 -1
- package/src/sdk/host/session-runtime.test.ts +2705 -303
- package/src/sdk/host/session-runtime.ts +490 -143
- package/src/sdk/host/surface-policy.ts +1 -0
- package/src/sdk/index.ts +8 -0
- package/src/sdk/lifecycle-session.ts +3 -1
- package/src/sdk/prompt-deadline-manager.ts +15 -0
- package/src/sdk/protocol/operation-inventory.generated.json +146 -0
- package/src/sdk/protocol/operation-registry.ts +9 -2
- package/src/sdk/router/session-router.ts +82 -18
- package/src/sdk/session-list.ts +1 -0
- package/src/sdk/session.ts +69 -45
- package/src/session/agent-session.ts +679 -238
- package/src/session/artifacts.ts +176 -71
- package/src/session/internal/managed-gc-retirement-codec.ts +319 -0
- package/src/session/internal/managed-session-scope.ts +3779 -462
- package/src/session/internal/managed-session-storage.ts +501 -78
- package/src/session/internal/managed-task-owner-cleanup.ts +473 -0
- package/src/session/internal/task-artifact-owner-access.ts +19 -3
- package/src/session/internal/task-artifact-owner-transcript.ts +547 -1
- package/src/session/session-manager.ts +951 -133
- package/src/session/session-retirement.ts +573 -14
- package/src/session/session-storage.ts +447 -15
- package/src/session/task-artifact-owner-codec.ts +71 -8
- package/src/session/task-artifact-owner-retirement.ts +21 -19
- package/src/setup/credential-auto-import.ts +12 -3
- package/src/setup/credential-import.ts +176 -5
- package/src/setup/paseo/announce.ts +6 -1
- package/src/skill-state/active-state.ts +145 -2
- package/src/skill-state/workflow-mutation-guard.ts +102 -10
- package/src/slash-commands/builtin-registry.ts +39 -9
- package/src/slash-commands/helpers/mcp.ts +8 -4
- package/src/slash-commands/helpers/progress-report.ts +23 -0
- package/src/stt/setup.ts +3 -5
- package/src/task/commands.ts +3 -1
- package/src/task/executor.ts +4 -0
- package/src/task/index.ts +6 -0
- package/src/task/isolation-dir.ts +143 -0
- package/src/task/worktree.ts +16 -7
- package/src/tools/ask.ts +205 -6
- package/src/tools/ast-edit-write-path.ts +68 -0
- package/src/tools/ast-edit.ts +10 -1
- package/src/tools/bash-allowed-prefixes.ts +15 -0
- package/src/tools/bash-skill-urls.ts +56 -3
- package/src/tools/bash.ts +48 -79
- package/src/tools/browser/cdp-url.ts +18 -0
- package/src/tools/browser.ts +8 -1
- package/src/tools/descriptors.ts +5 -1
- package/src/tools/eval.ts +56 -36
- package/src/tools/gh.ts +10 -0
- package/src/tools/github-side-effect.ts +112 -0
- package/src/tools/image-gen.ts +1 -1
- package/src/tools/index.ts +3 -0
- package/src/tools/monitor-mutation.ts +27 -0
- package/src/tools/monitor.ts +7 -0
- package/src/tools/python.ts +188 -66
- package/src/tools/recipe/index.ts +9 -0
- package/src/tools/recipe/recipe-guard.ts +19 -0
- package/src/tools/restricted-cwd.ts +10 -0
- package/src/tools/skill-discovery.ts +5 -0
- package/src/tools/skill.ts +28 -1
- package/src/tools/todo-write.ts +10 -1
- package/src/tools/tool-catalog.generated.ts +1 -1
- package/src/tools/write.ts +1 -0
- package/src/utils/external-editor.ts +3 -2
- package/src/utils/tools-checksum.ts +34 -0
- package/src/utils/tools-manager.ts +24 -6
- package/src/web/insane/url-guard.ts +2 -1
- package/src/web/search/providers/codex.ts +3 -2
|
@@ -50,13 +50,17 @@ export async function writeMCPConfigFile(filePath: string, config: MCPConfigFile
|
|
|
50
50
|
const dir = path.dirname(filePath);
|
|
51
51
|
await fs.promises.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
52
52
|
|
|
53
|
-
//
|
|
54
|
-
|
|
53
|
+
// A predictable `${file}.tmp` can already be a symlink planted in the project.
|
|
54
|
+
// Write a unique sibling and fail closed if that path exists.
|
|
55
|
+
const tmpPath = `${filePath}.${crypto.randomUUID()}.tmp`;
|
|
55
56
|
const content = JSON.stringify(withSchema(config), null, 2);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
try {
|
|
58
|
+
await fs.promises.writeFile(tmpPath, content, { encoding: "utf-8", mode: 0o600, flag: "wx" });
|
|
59
|
+
await fs.promises.rename(tmpPath, filePath);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
await fs.promises.rm(tmpPath, { force: true });
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
60
64
|
// Invalidate the capability fs cache so subsequent reads see the new content
|
|
61
65
|
invalidateFsCache(filePath);
|
|
62
66
|
}
|
|
@@ -103,6 +103,7 @@ function convertToLegacyConfig(server: MCPServer): MCPServerConfig {
|
|
|
103
103
|
url: server.url ?? "",
|
|
104
104
|
};
|
|
105
105
|
if (server.headers) config.headers = server.headers;
|
|
106
|
+
if (server.publicNetwork === true) config.publicNetwork = true;
|
|
106
107
|
canonicalizeMCPEndpoint(config.url);
|
|
107
108
|
return config;
|
|
108
109
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { logger } from "@gajae-code/utils";
|
|
8
8
|
import { cancelMCPStream, MCP_HTTP_TIMEOUT_MS, MCP_MAX_CONTENT_BYTES, readMCPResponseText } from "./content-limits";
|
|
9
|
+
import { fetchMcpRespectingOrigin } from "./mcp-redirect";
|
|
9
10
|
import { redactMCPDiagnosticValue, redactMCPEndpoint } from "./redaction";
|
|
10
11
|
|
|
11
12
|
/** Parse SSE response format (lines starting with "data: ") */
|
|
@@ -70,7 +71,7 @@ export async function callMCP<T = unknown>(
|
|
|
70
71
|
|
|
71
72
|
const signal = AbortSignal.timeout(MCP_HTTP_TIMEOUT_MS);
|
|
72
73
|
const response = await translateMCPTimeout(
|
|
73
|
-
|
|
74
|
+
fetchMcpRespectingOrigin(url, {
|
|
74
75
|
method: "POST",
|
|
75
76
|
headers: { "Content-Type": "application/json", Accept: "application/json, text/event-stream" },
|
|
76
77
|
body: JSON.stringify(body),
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
MCPPoolLeaseObsoleteError,
|
|
35
35
|
MCPPoolLeaseReleaseError,
|
|
36
36
|
} from "./pool";
|
|
37
|
+
import { decideUntrustedProjectConfigValue, isUntrustedProjectMcpSource } from "./project-host-values";
|
|
37
38
|
import type { MCPProtocolObservation } from "./protocol";
|
|
38
39
|
import { DEFAULT_MCP_STARTUP_WAIT_MS, MAX_MCP_STARTUP_WAIT_MS, MCP_STARTUP_WAIT_GRACE_MS } from "./startup-policy";
|
|
39
40
|
import type { MCPToolDetails } from "./tool-bridge";
|
|
@@ -1313,8 +1314,9 @@ export class MCPManager {
|
|
|
1313
1314
|
const connectionAbort = new AbortController();
|
|
1314
1315
|
this.#pendingConnectionControllers.set(name, connectionAbort);
|
|
1315
1316
|
// Resolve auth config before connecting, but do so per-server in parallel.
|
|
1317
|
+
const configSource = sources[name] ?? this.#sources.get(name);
|
|
1316
1318
|
const acquireInitialLease = async (): Promise<MCPPoolLease> => {
|
|
1317
|
-
const resolvedConfig = await this.#resolveAuthConfig(config);
|
|
1319
|
+
const resolvedConfig = await this.#resolveAuthConfig(config, false, configSource);
|
|
1318
1320
|
let lease: MCPPoolLease | undefined;
|
|
1319
1321
|
try {
|
|
1320
1322
|
lease = await this.#acquireLease(name, resolvedConfig, config, connectionAbort);
|
|
@@ -1371,7 +1373,7 @@ export class MCPManager {
|
|
|
1371
1373
|
// Wire auth refresh for HTTP transports, and reconnect for any transport.
|
|
1372
1374
|
if (connection.transport instanceof HttpTransport && config.auth?.type === "oauth") {
|
|
1373
1375
|
connection.transport.onAuthError = async () => {
|
|
1374
|
-
const refreshed = await this.#resolveAuthConfig(config, true);
|
|
1376
|
+
const refreshed = await this.#resolveAuthConfig(config, true, configSource);
|
|
1375
1377
|
if (refreshed.type === "http" || refreshed.type === "sse") {
|
|
1376
1378
|
return refreshed.headers ?? null;
|
|
1377
1379
|
}
|
|
@@ -2152,7 +2154,11 @@ export class MCPManager {
|
|
|
2152
2154
|
const config = this.#serverConfigs.get(name);
|
|
2153
2155
|
if (!config) throw new Error(`MCP server unavailable: ${name}`);
|
|
2154
2156
|
await this.#closePendingConnectionCleanup(name);
|
|
2155
|
-
const resolved = await this.#resolveAuthConfig(
|
|
2157
|
+
const resolved = await this.#resolveAuthConfig(
|
|
2158
|
+
config,
|
|
2159
|
+
false,
|
|
2160
|
+
this.#sources.get(name) ?? this.#connections.get(name)?._source,
|
|
2161
|
+
);
|
|
2156
2162
|
const abort = new AbortController();
|
|
2157
2163
|
this.#pendingConnectionControllers.set(name, abort);
|
|
2158
2164
|
let prepared:
|
|
@@ -2308,9 +2314,9 @@ export class MCPManager {
|
|
|
2308
2314
|
/**
|
|
2309
2315
|
* Resolve auth and shell-command substitutions in config before connecting.
|
|
2310
2316
|
*/
|
|
2311
|
-
async prepareConfig(config: MCPServerConfig): Promise<MCPServerConfig> {
|
|
2317
|
+
async prepareConfig(config: MCPServerConfig, source?: SourceMeta): Promise<MCPServerConfig> {
|
|
2312
2318
|
this.#assertRawMCPAccessAllowed();
|
|
2313
|
-
return this.#resolveAuthConfig(config);
|
|
2319
|
+
return this.#resolveAuthConfig(config, false, source);
|
|
2314
2320
|
}
|
|
2315
2321
|
|
|
2316
2322
|
/** Acquire a prepared, pool-owned lease for a scoped transient operation. */
|
|
@@ -2318,7 +2324,7 @@ export class MCPManager {
|
|
|
2318
2324
|
name: string,
|
|
2319
2325
|
config: MCPServerConfig,
|
|
2320
2326
|
fn: (lease: MCPPoolLease) => Promise<T> | T,
|
|
2321
|
-
options: { signal?: AbortSignal } = {},
|
|
2327
|
+
options: { signal?: AbortSignal; source?: SourceMeta } = {},
|
|
2322
2328
|
): Promise<T> {
|
|
2323
2329
|
this.#assertRawMCPAccessAllowed();
|
|
2324
2330
|
const lifecycleEpoch = this.#assertScopedAdmission();
|
|
@@ -2347,7 +2353,7 @@ export class MCPManager {
|
|
|
2347
2353
|
operation: ScopedOperation,
|
|
2348
2354
|
config: MCPServerConfig,
|
|
2349
2355
|
fn: (lease: MCPPoolLease) => Promise<T> | T,
|
|
2350
|
-
options: { signal?: AbortSignal },
|
|
2356
|
+
options: { signal?: AbortSignal; source?: SourceMeta },
|
|
2351
2357
|
): Promise<T> {
|
|
2352
2358
|
const callerSignal = options.signal;
|
|
2353
2359
|
const onAbort = () =>
|
|
@@ -2376,7 +2382,11 @@ export class MCPManager {
|
|
|
2376
2382
|
let failed = false;
|
|
2377
2383
|
let primaryError: unknown;
|
|
2378
2384
|
try {
|
|
2379
|
-
const resolvedConfigPromise = this.#resolveAuthConfig(
|
|
2385
|
+
const resolvedConfigPromise = this.#resolveAuthConfig(
|
|
2386
|
+
config,
|
|
2387
|
+
false,
|
|
2388
|
+
options.source ?? this.#sources.get(operation.name),
|
|
2389
|
+
);
|
|
2380
2390
|
void resolvedConfigPromise.catch(() => {});
|
|
2381
2391
|
const resolvedConfig = await Promise.race([resolvedConfigPromise, abortPromise]);
|
|
2382
2392
|
if (operation.lifecycleEpoch !== this.#scopedLifecycleEpoch || this.#scopedLifecycle !== "open") {
|
|
@@ -2857,7 +2867,7 @@ export class MCPManager {
|
|
|
2857
2867
|
throw new MCPManagerLifecycleError(this.#scopedLifecycle === "disconnecting" ? "disconnect" : "reconnect");
|
|
2858
2868
|
};
|
|
2859
2869
|
assertLifecycle();
|
|
2860
|
-
const resolvedConfig = await this.#resolveAuthConfig(config);
|
|
2870
|
+
const resolvedConfig = await this.#resolveAuthConfig(config, false, source);
|
|
2861
2871
|
assertLifecycle();
|
|
2862
2872
|
const connectionAbort = new AbortController();
|
|
2863
2873
|
this.#pendingConnectionControllers.set(name, connectionAbort);
|
|
@@ -2909,7 +2919,7 @@ export class MCPManager {
|
|
|
2909
2919
|
// Wire auth refresh for HTTP transports, and reconnect for any transport.
|
|
2910
2920
|
if (connection.transport instanceof HttpTransport && config.auth?.type === "oauth") {
|
|
2911
2921
|
connection.transport.onAuthError = async () => {
|
|
2912
|
-
const refreshed = await this.#resolveAuthConfig(config, true);
|
|
2922
|
+
const refreshed = await this.#resolveAuthConfig(config, true, source);
|
|
2913
2923
|
if (refreshed.type === "http" || refreshed.type === "sse") {
|
|
2914
2924
|
return refreshed.headers ?? null;
|
|
2915
2925
|
}
|
|
@@ -3174,7 +3184,11 @@ export class MCPManager {
|
|
|
3174
3184
|
/**
|
|
3175
3185
|
* Resolve OAuth credentials and shell commands in config.
|
|
3176
3186
|
*/
|
|
3177
|
-
async #resolveAuthConfig(
|
|
3187
|
+
async #resolveAuthConfig(
|
|
3188
|
+
config: MCPServerConfig,
|
|
3189
|
+
forceRefresh = false,
|
|
3190
|
+
source?: SourceMeta,
|
|
3191
|
+
): Promise<MCPServerConfig> {
|
|
3178
3192
|
let resolved: MCPServerConfig = { ...config };
|
|
3179
3193
|
|
|
3180
3194
|
const auth = config.auth;
|
|
@@ -3240,7 +3254,11 @@ export class MCPManager {
|
|
|
3240
3254
|
resolved = { ...resolved, noInheritEnv: true };
|
|
3241
3255
|
}
|
|
3242
3256
|
|
|
3257
|
+
const untrustedProject = isUntrustedProjectMcpSource(source, this.#toolsOnly);
|
|
3243
3258
|
const resolveValue = async (value: string): Promise<string | undefined> => {
|
|
3259
|
+
const decision = decideUntrustedProjectConfigValue(value, untrustedProject);
|
|
3260
|
+
if (decision.action === "drop") return undefined;
|
|
3261
|
+
if (decision.action === "literal") return decision.value;
|
|
3244
3262
|
try {
|
|
3245
3263
|
const resolvedValue = await configValue.resolveConfigValue(value);
|
|
3246
3264
|
if (this.#toolsOnly && !resolvedValue) throw new MCPExpectedFailure();
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { cancelMCPStream } from "./content-limits";
|
|
2
|
+
|
|
3
|
+
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
4
|
+
const MAX_REDIRECTS = 20;
|
|
5
|
+
const REQUEST_BODY_HEADERS = [
|
|
6
|
+
"content-encoding",
|
|
7
|
+
"content-language",
|
|
8
|
+
"content-length",
|
|
9
|
+
"content-location",
|
|
10
|
+
"content-type",
|
|
11
|
+
] as const;
|
|
12
|
+
|
|
13
|
+
function rewritesToGet(status: number, method: string): boolean {
|
|
14
|
+
return (
|
|
15
|
+
((status === 301 || status === 302) && method === "POST") ||
|
|
16
|
+
(status === 303 && method !== "GET" && method !== "HEAD")
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Follow same-origin MCP redirects only.
|
|
22
|
+
*
|
|
23
|
+
* A cross-origin 307/308 would carry custom credential headers, `Mcp-Session-Id`,
|
|
24
|
+
* and the JSON-RPC body. The first request uses `rawUrl` unchanged so a caller
|
|
25
|
+
* that cannot be parsed (diagnostic redaction) still reaches fetch, but with
|
|
26
|
+
* `redirect: "manual"` so that hop is not followed either.
|
|
27
|
+
*/
|
|
28
|
+
export async function fetchMcpRespectingOrigin(rawUrl: string, init: BunFetchRequestInit): Promise<Response> {
|
|
29
|
+
let currentUrl: URL;
|
|
30
|
+
try {
|
|
31
|
+
currentUrl = new URL(rawUrl);
|
|
32
|
+
} catch {
|
|
33
|
+
return fetch(rawUrl, { ...init, redirect: "manual" });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let requestTarget = rawUrl;
|
|
37
|
+
let currentInit: BunFetchRequestInit = { ...init, redirect: "manual" };
|
|
38
|
+
for (let redirectCount = 0; ; redirectCount++) {
|
|
39
|
+
const response = await fetch(requestTarget, currentInit);
|
|
40
|
+
if (!REDIRECT_STATUSES.has(response.status)) return response;
|
|
41
|
+
|
|
42
|
+
const location = response.headers.get("location");
|
|
43
|
+
if (!location) return response;
|
|
44
|
+
if (redirectCount >= MAX_REDIRECTS) {
|
|
45
|
+
cancelMCPStream(response.body);
|
|
46
|
+
throw new Error("MCP redirect limit exceeded");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let nextUrl: URL;
|
|
50
|
+
try {
|
|
51
|
+
nextUrl = new URL(location, currentUrl);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
cancelMCPStream(response.body);
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
if (nextUrl.origin !== currentUrl.origin) {
|
|
57
|
+
cancelMCPStream(response.body);
|
|
58
|
+
throw new Error("cross-origin redirects are not allowed");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const headers = new Headers(currentInit.headers);
|
|
62
|
+
const method = (currentInit.method ?? "GET").toUpperCase();
|
|
63
|
+
if (rewritesToGet(response.status, method)) {
|
|
64
|
+
for (const name of REQUEST_BODY_HEADERS) headers.delete(name);
|
|
65
|
+
currentInit = { ...currentInit, method: "GET", headers, body: undefined, redirect: "manual" };
|
|
66
|
+
} else {
|
|
67
|
+
currentInit = { ...currentInit, headers, redirect: "manual" };
|
|
68
|
+
}
|
|
69
|
+
currentUrl = nextUrl;
|
|
70
|
+
requestTarget = nextUrl.toString();
|
|
71
|
+
cancelMCPStream(response.body);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
import type { OAuthCallbackFlowOptions } from "@gajae-code/ai/utils/oauth/callback-server";
|
|
9
9
|
import { OAuthCallbackFlow } from "@gajae-code/ai/utils/oauth/callback-server";
|
|
10
10
|
import type { OAuthController, OAuthCredentials } from "@gajae-code/ai/utils/oauth/types";
|
|
11
|
+
import { guardedPublicFetch } from "../web/insane/url-guard";
|
|
12
|
+
import { assertPublicOAuthUrl } from "./oauth-public-url";
|
|
11
13
|
|
|
12
14
|
const DEFAULT_PORT = 3000;
|
|
13
15
|
const CALLBACK_PATH = "/callback";
|
|
@@ -261,8 +263,10 @@ export class MCPOAuthFlow extends OAuthCallbackFlow {
|
|
|
261
263
|
params.set("client_secret", clientSecret);
|
|
262
264
|
}
|
|
263
265
|
|
|
266
|
+
await assertPublicOAuthUrl(this.config.tokenUrl, { signal: this.ctrl.signal });
|
|
264
267
|
const response = await fetch(this.config.tokenUrl, {
|
|
265
268
|
method: "POST",
|
|
269
|
+
redirect: "error",
|
|
266
270
|
headers: {
|
|
267
271
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
268
272
|
},
|
|
@@ -340,8 +344,10 @@ export class MCPOAuthFlow extends OAuthCallbackFlow {
|
|
|
340
344
|
if (!registrationEndpoint) return;
|
|
341
345
|
|
|
342
346
|
try {
|
|
347
|
+
await assertPublicOAuthUrl(registrationEndpoint, { signal });
|
|
343
348
|
const response = await fetch(registrationEndpoint, {
|
|
344
349
|
method: "POST",
|
|
350
|
+
redirect: "error",
|
|
345
351
|
headers: {
|
|
346
352
|
"Content-Type": "application/json",
|
|
347
353
|
Accept: "application/json",
|
|
@@ -380,8 +386,10 @@ export class MCPOAuthFlow extends OAuthCallbackFlow {
|
|
|
380
386
|
try {
|
|
381
387
|
const authorizationEndpoint = new URL(this.config.authorizationUrl);
|
|
382
388
|
const metadataUrl = new URL("/.well-known/oauth-authorization-server", authorizationEndpoint.origin);
|
|
389
|
+
await assertPublicOAuthUrl(metadataUrl.toString(), { signal });
|
|
383
390
|
const response = await fetch(metadataUrl.toString(), {
|
|
384
391
|
method: "GET",
|
|
392
|
+
redirect: "error",
|
|
385
393
|
headers: { Accept: "application/json" },
|
|
386
394
|
signal,
|
|
387
395
|
});
|
|
@@ -401,12 +409,16 @@ export class MCPOAuthFlow extends OAuthCallbackFlow {
|
|
|
401
409
|
|
|
402
410
|
async #assertClientIdNotRequired(authorizationUrl: string, signal?: AbortSignal): Promise<void> {
|
|
403
411
|
try {
|
|
404
|
-
|
|
412
|
+
// Pin the GET to the addresses that passed validation. A plain fetch would
|
|
413
|
+
// resolve the hostname again. Skip the probe when that route cannot be
|
|
414
|
+
// established; the flow signal cancels the DNS lookup.
|
|
415
|
+
const probed = await guardedPublicFetch(authorizationUrl, {
|
|
405
416
|
method: "GET",
|
|
406
|
-
redirect: "manual",
|
|
407
417
|
headers: { Accept: "text/plain,text/html,application/json" },
|
|
408
418
|
signal,
|
|
409
419
|
});
|
|
420
|
+
if (!probed.ok) return;
|
|
421
|
+
const response = probed.response;
|
|
410
422
|
if (response.status < 400) return;
|
|
411
423
|
const body = await response.text();
|
|
412
424
|
if (/client[_-]?id/i.test(body) && /(required|missing|invalid)/i.test(body)) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { validatePublicHttpUrl } from "../web/insane/url-guard";
|
|
2
|
+
|
|
3
|
+
export async function assertPublicOAuthUrl(url: string, options: { signal?: AbortSignal } = {}): Promise<void> {
|
|
4
|
+
if (options.signal?.aborted) throw abortReason(options.signal);
|
|
5
|
+
const checked = await validatePublicHttpUrl(url, { signal: options.signal });
|
|
6
|
+
if (options.signal?.aborted) throw abortReason(options.signal);
|
|
7
|
+
if (!checked.ok) {
|
|
8
|
+
throw new Error(`Refusing non-public OAuth endpoint: ${checked.reason ?? url}`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function abortReason(signal: AbortSignal): Error {
|
|
13
|
+
return signal.reason instanceof Error ? signal.reason : new Error("OAuth URL validation aborted");
|
|
14
|
+
}
|
|
@@ -19,6 +19,10 @@ export function bindPluginMcpToPublicNetwork<T extends MCPHttpServerConfig | MCP
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/** The marker is an internal symbol, so persisted user MCP configs cannot opt into or forge this path. */
|
|
22
|
+
export function usesPublicNetworkMcpFetch(config: MCPHttpServerConfig | MCPSseServerConfig): boolean {
|
|
23
|
+
return ("publicNetwork" in config && config.publicNetwork === true) || isPluginMcpPublicNetworkBound(config);
|
|
24
|
+
}
|
|
25
|
+
|
|
22
26
|
export function isPluginMcpPublicNetworkBound(config: MCPHttpServerConfig | MCPSseServerConfig): boolean {
|
|
23
27
|
return (
|
|
24
28
|
(config as MCPHttpServerConfig & { [PLUGIN_MCP_PUBLIC_NETWORK_BOUNDARY]?: boolean })[
|
package/src/runtime-mcp/pool.ts
CHANGED
|
@@ -307,6 +307,12 @@ class MCPPoolLeaseImpl implements MCPPoolLease {
|
|
|
307
307
|
notify: (method, params) => lease.notify(method, params),
|
|
308
308
|
close: () => lease.release(),
|
|
309
309
|
closeBeforeReconnect: physical.transport.closeBeforeReconnect,
|
|
310
|
+
get onRequest() {
|
|
311
|
+
return physical.transport.onRequest;
|
|
312
|
+
},
|
|
313
|
+
set onRequest(handler) {
|
|
314
|
+
physical.transport.onRequest = handler;
|
|
315
|
+
},
|
|
310
316
|
};
|
|
311
317
|
const facade = { ...physical, transport };
|
|
312
318
|
for (const property of ["tools", "resources", "resourceTemplates", "prompts"] as const) {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-value policy for MCP config that comes from an untrusted project file.
|
|
3
|
+
*
|
|
4
|
+
* User mcp.json and an explicit `--mcp-config` file may still substitute
|
|
5
|
+
* secret-named environment variables and run `!` shell commands. A project
|
|
6
|
+
* file (native `.gjc/mcp.json` or a repo-root `mcp.json`) uses the same
|
|
7
|
+
* sensitive-name rule as project `ssh.json`: do not copy those host values
|
|
8
|
+
* into server env or headers, and do not execute `!`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { SourceMeta } from "../capability/types";
|
|
12
|
+
import { isSensitiveEnvName } from "../discovery/helpers";
|
|
13
|
+
|
|
14
|
+
const UNTRUSTED_PROJECT_MCP_PROVIDERS = new Set(["native", "mcp-json"]);
|
|
15
|
+
|
|
16
|
+
export function nativeMcpSource(level: "user" | "project", filePath: string): SourceMeta {
|
|
17
|
+
return {
|
|
18
|
+
provider: "native",
|
|
19
|
+
providerName: "GJC",
|
|
20
|
+
path: filePath,
|
|
21
|
+
level,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** True when this server was loaded from untrusted project MCP config. */
|
|
26
|
+
export function isUntrustedProjectMcpSource(source: SourceMeta | undefined, toolsOnly: boolean): boolean {
|
|
27
|
+
if (toolsOnly || !source || source.level !== "project") return false;
|
|
28
|
+
return UNTRUSTED_PROJECT_MCP_PROVIDERS.has(source.provider);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type ProjectHostValueDecision =
|
|
32
|
+
| { action: "resolve" }
|
|
33
|
+
| { action: "drop" }
|
|
34
|
+
| { action: "literal"; value: string };
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Decide how to interpret one env or header value.
|
|
38
|
+
* `drop` means "do not run the shell, and omit the value".
|
|
39
|
+
* `literal` means "do not read process.env".
|
|
40
|
+
*/
|
|
41
|
+
export function decideUntrustedProjectConfigValue(value: string, untrusted: boolean): ProjectHostValueDecision {
|
|
42
|
+
if (!untrusted) return { action: "resolve" };
|
|
43
|
+
if (value.startsWith("!")) return { action: "drop" };
|
|
44
|
+
if (isSensitiveEnvName(value)) return { action: "literal", value };
|
|
45
|
+
return { action: "resolve" };
|
|
46
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { logger } from "@gajae-code/utils";
|
|
2
|
+
import { type AddressResolver, validatePublicHttpUrl } from "../web/insane/url-guard";
|
|
2
3
|
import type { MCPServerConfig } from "./types";
|
|
3
4
|
|
|
4
5
|
const SMITHERY_REGISTRY_BASE_URL = "https://registry.smithery.ai";
|
|
@@ -289,6 +290,20 @@ function chooseConnection(
|
|
|
289
290
|
return null;
|
|
290
291
|
}
|
|
291
292
|
|
|
293
|
+
export async function resolveSmitheryServerConfig(
|
|
294
|
+
qualifiedName: string,
|
|
295
|
+
selected: { connection: SmitheryConnection; useDirectHttp: boolean },
|
|
296
|
+
options?: { resolver?: AddressResolver },
|
|
297
|
+
): Promise<MCPServerConfig | null> {
|
|
298
|
+
let useDirectHttp = selected.useDirectHttp;
|
|
299
|
+
const deploymentUrl = selected.connection.deploymentUrl;
|
|
300
|
+
if (useDirectHttp && selected.connection.type === "http" && deploymentUrl) {
|
|
301
|
+
const checked = await validatePublicHttpUrl(deploymentUrl, { resolver: options?.resolver });
|
|
302
|
+
if (!checked.ok) useDirectHttp = false;
|
|
303
|
+
}
|
|
304
|
+
return createConfig(qualifiedName, { connection: selected.connection, useDirectHttp });
|
|
305
|
+
}
|
|
306
|
+
|
|
292
307
|
function createConfig(
|
|
293
308
|
qualifiedName: string,
|
|
294
309
|
selected: { connection: SmitheryConnection; useDirectHttp: boolean },
|
|
@@ -297,6 +312,7 @@ function createConfig(
|
|
|
297
312
|
return {
|
|
298
313
|
type: "http",
|
|
299
314
|
url: selected.connection.deploymentUrl,
|
|
315
|
+
publicNetwork: true,
|
|
300
316
|
};
|
|
301
317
|
}
|
|
302
318
|
|
|
@@ -335,7 +351,10 @@ async function fetchServerDetailsFromEntry(
|
|
|
335
351
|
return null;
|
|
336
352
|
}
|
|
337
353
|
|
|
338
|
-
function toSearchResult(
|
|
354
|
+
async function toSearchResult(
|
|
355
|
+
entry: SmitherySearchEntry,
|
|
356
|
+
details: SmitheryServerDetails,
|
|
357
|
+
): Promise<SmitherySearchResult | null> {
|
|
339
358
|
if (!entry.id) return null;
|
|
340
359
|
const qualifiedName = normalizeQualifiedName(
|
|
341
360
|
details.qualifiedName ?? entry.qualifiedName ?? `${entry.namespace}/${entry.slug}`,
|
|
@@ -343,7 +362,7 @@ function toSearchResult(entry: SmitherySearchEntry, details: SmitheryServerDetai
|
|
|
343
362
|
const selected = chooseConnection(details);
|
|
344
363
|
if (!selected) return null;
|
|
345
364
|
|
|
346
|
-
const config =
|
|
365
|
+
const config = await resolveSmitheryServerConfig(qualifiedName, selected);
|
|
347
366
|
if (!config) return null;
|
|
348
367
|
|
|
349
368
|
const requiredInputs = getSchemaInputs(selected.connection.configSchema);
|
|
@@ -450,7 +469,7 @@ export async function searchSmitheryRegistry(
|
|
|
450
469
|
try {
|
|
451
470
|
const details = await fetchServerDetailsFromEntry(entry, { apiKey: options?.apiKey });
|
|
452
471
|
if (!details) return null;
|
|
453
|
-
return toSearchResult(entry, details);
|
|
472
|
+
return await toSearchResult(entry, details);
|
|
454
473
|
} catch (error) {
|
|
455
474
|
detailFailures.push({
|
|
456
475
|
identity: getEntryIdentityKey(entry) ?? entry.id ?? "unknown",
|
|
@@ -28,7 +28,8 @@ import {
|
|
|
28
28
|
MCP_MAX_SSE_REQUEST_MESSAGES,
|
|
29
29
|
readMCPResponseText,
|
|
30
30
|
} from "../content-limits";
|
|
31
|
-
import {
|
|
31
|
+
import { fetchMcpRespectingOrigin } from "../mcp-redirect";
|
|
32
|
+
import { fetchPluginMcpRequest, usesPublicNetworkMcpFetch } from "../plugin-network-boundary";
|
|
32
33
|
import { buildModernMcpHeaders, type MCPModernClientContext, type MCPProtocolEra, withModernMeta } from "../protocol";
|
|
33
34
|
|
|
34
35
|
/**
|
|
@@ -155,9 +156,9 @@ export class HttpTransport implements MCPTransport {
|
|
|
155
156
|
}
|
|
156
157
|
|
|
157
158
|
#fetch(init: BunFetchRequestInit): Promise<Response> {
|
|
158
|
-
return
|
|
159
|
+
return usesPublicNetworkMcpFetch(this.config)
|
|
159
160
|
? fetchPluginMcpRequest(this.config.url, init)
|
|
160
|
-
:
|
|
161
|
+
: fetchMcpRespectingOrigin(this.config.url, init);
|
|
161
162
|
}
|
|
162
163
|
|
|
163
164
|
#trackReader(promise: Promise<void>, controller?: AbortController): void {
|
package/src/runtime-mcp/types.ts
CHANGED
|
@@ -139,6 +139,12 @@ export interface MCPHttpServerConfig extends MCPServerConfigBase {
|
|
|
139
139
|
type: "http";
|
|
140
140
|
url: string;
|
|
141
141
|
headers?: Record<string, string>;
|
|
142
|
+
/**
|
|
143
|
+
* When true, every request and redirect for this URL is rechecked against
|
|
144
|
+
* the public-network policy. Persisted so a later process cannot fall back
|
|
145
|
+
* to an unguarded fetch. Ordinary localhost MCP servers leave this unset.
|
|
146
|
+
*/
|
|
147
|
+
publicNetwork?: boolean;
|
|
142
148
|
}
|
|
143
149
|
|
|
144
150
|
/** SSE server configuration (deprecated, use HTTP) */
|
package/src/sdk/acp/adapter.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { logger } from "@gajae-code/utils";
|
|
3
3
|
import { lifecycleRequestTimeoutMs } from "../broker/startup-budget";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_SDK_REQUEST_TIMEOUT_MS,
|
|
6
|
+
type SdkClient,
|
|
7
|
+
SdkClientError,
|
|
8
|
+
type SdkDispatchContext,
|
|
9
|
+
type SdkSentRecord,
|
|
10
|
+
} from "../client";
|
|
5
11
|
import type { AbortScope } from "../host/control/operations";
|
|
6
12
|
import { assertReverseResponseFrame, ReverseLeaseError } from "../host/reverse-leases";
|
|
7
13
|
import {
|
|
@@ -143,6 +149,9 @@ const ACP_MCP_PRESERVED_LAUNCH_CODES = new Set([
|
|
|
143
149
|
"readiness_timeout",
|
|
144
150
|
"spawn_failed",
|
|
145
151
|
"worktree_in_use",
|
|
152
|
+
"uncertain_after_send",
|
|
153
|
+
// Attach-only refusal: raised by ensureBroker before any lifecycle request or MCP server.
|
|
154
|
+
"broker_unavailable",
|
|
146
155
|
]);
|
|
147
156
|
|
|
148
157
|
/**
|
|
@@ -531,7 +540,11 @@ export class AcpSdkAdapter {
|
|
|
531
540
|
return envelope?.result ?? response;
|
|
532
541
|
}
|
|
533
542
|
|
|
534
|
-
async #requestSession(
|
|
543
|
+
async #requestSession(
|
|
544
|
+
frame: JsonObject,
|
|
545
|
+
raw = false,
|
|
546
|
+
options?: { timeoutMs: number; deadline?: number },
|
|
547
|
+
): Promise<unknown> {
|
|
535
548
|
const router = this.#router;
|
|
536
549
|
if (!router)
|
|
537
550
|
throw new AcpSdkAdapterError(
|
|
@@ -603,11 +616,68 @@ export class AcpSdkAdapter {
|
|
|
603
616
|
// request that named no readiness budget is queued for the default one, so it
|
|
604
617
|
// needs the same extension rather than the client's generic request deadline.
|
|
605
618
|
const timeoutMs = lifecycleRequestTimeoutMs(operation, input);
|
|
606
|
-
const
|
|
619
|
+
const budgetMs = timeoutMs ?? DEFAULT_SDK_REQUEST_TIMEOUT_MS;
|
|
620
|
+
const deadline = Date.now() + budgetMs;
|
|
621
|
+
let sent = false;
|
|
622
|
+
let sentRecord: SdkSentRecord | undefined;
|
|
623
|
+
let uncertainty: SdkClientError | undefined;
|
|
624
|
+
const deadlineFailure = () =>
|
|
625
|
+
uncertainty ??
|
|
626
|
+
(sent
|
|
627
|
+
? new SdkClientError(
|
|
628
|
+
"uncertain_after_send",
|
|
629
|
+
"ACP lifecycle deadline elapsed after dispatch.",
|
|
630
|
+
sentRecord ?? {
|
|
631
|
+
operation,
|
|
632
|
+
idempotencyKey,
|
|
633
|
+
},
|
|
634
|
+
)
|
|
635
|
+
: new SdkClientError("timeout", "ACP lifecycle deadline elapsed before dispatch.", undefined, undefined, {
|
|
636
|
+
transport: true,
|
|
637
|
+
}));
|
|
638
|
+
const options = {
|
|
607
639
|
idempotencyKey,
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
640
|
+
deadline,
|
|
641
|
+
// SdkClient reads this after connecting, so reconnect and replay spend the same allowance.
|
|
642
|
+
get timeoutMs() {
|
|
643
|
+
return Math.max(0, deadline - Date.now());
|
|
644
|
+
},
|
|
645
|
+
beforeDispatch: () => {
|
|
646
|
+
if (Date.now() >= deadline) throw deadlineFailure();
|
|
647
|
+
},
|
|
648
|
+
onDispatch: (context: SdkDispatchContext) => {
|
|
649
|
+
sent = true;
|
|
650
|
+
if (typeof context.frame.id === "string") sentRecord = client.getSentRecord(context.frame.id);
|
|
651
|
+
},
|
|
652
|
+
};
|
|
653
|
+
const expired = Promise.withResolvers<never>();
|
|
654
|
+
const timer = setTimeout(() => expired.reject(deadlineFailure()), budgetMs);
|
|
655
|
+
const recover = async () => {
|
|
656
|
+
try {
|
|
657
|
+
const result = await client.global(operation, input, options);
|
|
658
|
+
if (Date.now() >= deadline) throw deadlineFailure();
|
|
659
|
+
return result;
|
|
660
|
+
} catch (firstError) {
|
|
661
|
+
if (!(firstError instanceof SdkClientError) || firstError.code !== "uncertain_after_send") throw firstError;
|
|
662
|
+
uncertainty = firstError;
|
|
663
|
+
// Same-key replay joins the original durable claim, within the original deadline.
|
|
664
|
+
// Failure to recover does not prove that the committed operation failed.
|
|
665
|
+
if (Date.now() >= deadline) throw firstError;
|
|
666
|
+
try {
|
|
667
|
+
const result = await client.global(operation, input, options);
|
|
668
|
+
if (Date.now() >= deadline) throw firstError;
|
|
669
|
+
return result;
|
|
670
|
+
} catch (replayError) {
|
|
671
|
+
if (replayError !== firstError) Object.assign(firstError, { recovery: replayError });
|
|
672
|
+
throw firstError;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
try {
|
|
677
|
+
return await Promise.race([recover(), expired.promise]);
|
|
678
|
+
} finally {
|
|
679
|
+
clearTimeout(timer);
|
|
680
|
+
}
|
|
611
681
|
}
|
|
612
682
|
|
|
613
683
|
async sdkControl(params: { operation: string; input?: JsonObject }): Promise<unknown> {
|
|
@@ -646,7 +716,7 @@ export class AcpSdkAdapter {
|
|
|
646
716
|
throw new AcpSdkAdapterError("method_not_found", `Unsupported ACP SDK method: ${method}`);
|
|
647
717
|
}
|
|
648
718
|
|
|
649
|
-
async registerProvider(provider: AcpProviderRegistration): Promise<void> {
|
|
719
|
+
async registerProvider(provider: AcpProviderRegistration, timeoutMs?: number, deadline?: number): Promise<void> {
|
|
650
720
|
if (!this.#router)
|
|
651
721
|
throw new AcpSdkAdapterError(
|
|
652
722
|
"operation_prohibited",
|
|
@@ -665,10 +735,15 @@ export class AcpSdkAdapter {
|
|
|
665
735
|
...(previousLeaseId ? { expectedLeaseId: previousLeaseId } : {}),
|
|
666
736
|
},
|
|
667
737
|
true,
|
|
738
|
+
timeoutMs === undefined ? undefined : { timeoutMs, ...(deadline === undefined ? {} : { deadline }) },
|
|
668
739
|
);
|
|
669
740
|
const result = object(object(response)?.result) ?? object(response) ?? {};
|
|
670
741
|
if (typeof result.leaseId !== "string")
|
|
671
742
|
throw new AcpSdkAdapterError("invalid_reverse_frame", "Provider registration omitted leaseId.");
|
|
743
|
+
if (this.#closed)
|
|
744
|
+
throw new AcpSdkAdapterError("connection_closed", "ACP provider activation closed before acknowledgement.");
|
|
745
|
+
if (deadline !== undefined && Date.now() >= deadline)
|
|
746
|
+
throw new AcpSdkAdapterError("provider_activation_exhausted", "ACP provider activation deadline elapsed.");
|
|
672
747
|
this.#leases.set(provider.capability, result.leaseId);
|
|
673
748
|
if (previousLeaseId && previousLeaseId !== result.leaseId) this.#abortReverseForCapability(provider.capability);
|
|
674
749
|
}
|
|
@@ -694,9 +769,20 @@ export class AcpSdkAdapter {
|
|
|
694
769
|
const connectionId = attachment?.connectionId;
|
|
695
770
|
try {
|
|
696
771
|
for (const provider of this.#providers) {
|
|
772
|
+
const providerRemainingMs = PROVIDER_ACTIVATION_BUDGET_MS - (Date.now() - startedAt);
|
|
773
|
+
if (providerRemainingMs <= 0) throw this.#providerActivationExhausted(attempt, startedAt);
|
|
697
774
|
try {
|
|
698
|
-
await this.registerProvider(
|
|
775
|
+
await this.registerProvider(
|
|
776
|
+
provider,
|
|
777
|
+
Math.max(1, providerRemainingMs),
|
|
778
|
+
startedAt + PROVIDER_ACTIVATION_BUDGET_MS,
|
|
779
|
+
);
|
|
780
|
+
if (Date.now() - startedAt >= PROVIDER_ACTIVATION_BUDGET_MS)
|
|
781
|
+
throw this.#providerActivationExhausted(attempt, startedAt);
|
|
699
782
|
} catch (error) {
|
|
783
|
+
if (providerErrorCode(error) === "uncertain_after_send") throw error;
|
|
784
|
+
if (Date.now() - startedAt >= PROVIDER_ACTIVATION_BUDGET_MS)
|
|
785
|
+
throw this.#providerActivationExhausted(attempt, startedAt);
|
|
700
786
|
if (providerErrorCode(error) === "provider_lease_conflict") {
|
|
701
787
|
this.#leases.delete(provider.capability);
|
|
702
788
|
this.#abortReverseForCapability(provider.capability);
|
|
@@ -725,6 +811,7 @@ export class AcpSdkAdapter {
|
|
|
725
811
|
this.#providersActivated = true;
|
|
726
812
|
return;
|
|
727
813
|
} catch (error) {
|
|
814
|
+
if (providerErrorCode(error) === "uncertain_after_send") throw error;
|
|
728
815
|
if (this.#router && attachment !== this.#attachment && !this.#closed) {
|
|
729
816
|
this.#providersActivated = false;
|
|
730
817
|
continue;
|