@namzu/sdk 31.1.0 → 33.0.0
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 +188 -0
- package/dist/agents/ReactiveAgent.d.ts.map +1 -1
- package/dist/agents/ReactiveAgent.js +4 -0
- package/dist/agents/ReactiveAgent.js.map +1 -1
- package/dist/agents/SupervisorAgent.d.ts.map +1 -1
- package/dist/agents/SupervisorAgent.js +4 -0
- package/dist/agents/SupervisorAgent.js.map +1 -1
- package/dist/agents/runAgent.d.ts +4 -0
- package/dist/agents/runAgent.d.ts.map +1 -1
- package/dist/agents/runAgent.js +4 -0
- package/dist/agents/runAgent.js.map +1 -1
- package/dist/authorization/gate.d.ts +1 -1
- package/dist/authorization/gate.js +1 -1
- package/dist/connector/index.d.ts +1 -1
- package/dist/connector/index.d.ts.map +1 -1
- package/dist/connector/index.js +1 -1
- package/dist/connector/index.js.map +1 -1
- package/dist/connector/mcp/adapter.d.ts.map +1 -1
- package/dist/connector/mcp/adapter.js +71 -16
- package/dist/connector/mcp/adapter.js.map +1 -1
- package/dist/connector/mcp/client.d.ts.map +1 -1
- package/dist/connector/mcp/client.js +3 -2
- package/dist/connector/mcp/client.js.map +1 -1
- package/dist/connector/mcp/http-redirect.d.ts +17 -0
- package/dist/connector/mcp/http-redirect.d.ts.map +1 -0
- package/dist/connector/mcp/http-redirect.js +31 -0
- package/dist/connector/mcp/http-redirect.js.map +1 -0
- package/dist/connector/mcp/http-sse.d.ts.map +1 -1
- package/dist/connector/mcp/http-sse.js +5 -0
- package/dist/connector/mcp/http-sse.js.map +1 -1
- package/dist/connector/mcp/image-admission.d.ts +16 -0
- package/dist/connector/mcp/image-admission.d.ts.map +1 -0
- package/dist/connector/mcp/image-admission.js +288 -0
- package/dist/connector/mcp/image-admission.js.map +1 -0
- package/dist/connector/mcp/stdio.d.ts +13 -0
- package/dist/connector/mcp/stdio.d.ts.map +1 -1
- package/dist/connector/mcp/stdio.js +66 -23
- package/dist/connector/mcp/stdio.js.map +1 -1
- package/dist/connector/mcp/streamable-http.d.ts.map +1 -1
- package/dist/connector/mcp/streamable-http.js +3 -0
- package/dist/connector/mcp/streamable-http.js.map +1 -1
- package/dist/constants/tools/index.js +1 -1
- package/dist/constants/tools/index.js.map +1 -1
- package/dist/directory/derive-supervisor.d.ts.map +1 -1
- package/dist/directory/derive-supervisor.js +5 -0
- package/dist/directory/derive-supervisor.js.map +1 -1
- package/dist/directory/derive.d.ts.map +1 -1
- package/dist/directory/derive.js +5 -0
- package/dist/directory/derive.js.map +1 -1
- package/dist/directory/load.d.ts.map +1 -1
- package/dist/directory/load.js +1 -0
- package/dist/directory/load.js.map +1 -1
- package/dist/directory/types.d.ts +2 -0
- package/dist/directory/types.d.ts.map +1 -1
- package/dist/execution/base.d.ts +13 -0
- package/dist/execution/base.d.ts.map +1 -1
- package/dist/execution/base.js +103 -8
- package/dist/execution/base.js.map +1 -1
- package/dist/execution/code-runtime/types.d.ts +11 -1
- package/dist/execution/code-runtime/types.d.ts.map +1 -1
- package/dist/execution/code-runtime/types.js.map +1 -1
- package/dist/execution/code-runtime/worker.d.ts.map +1 -1
- package/dist/execution/code-runtime/worker.js +67 -9
- package/dist/execution/code-runtime/worker.js.map +1 -1
- package/dist/execution/errors.d.ts +12 -0
- package/dist/execution/errors.d.ts.map +1 -0
- package/dist/execution/errors.js +21 -0
- package/dist/execution/errors.js.map +1 -0
- package/dist/execution/factory.d.ts.map +1 -1
- package/dist/execution/factory.js +1 -0
- package/dist/execution/factory.js.map +1 -1
- package/dist/execution/hybrid.d.ts +5 -0
- package/dist/execution/hybrid.d.ts.map +1 -1
- package/dist/execution/hybrid.js +36 -16
- package/dist/execution/hybrid.js.map +1 -1
- package/dist/execution/index.d.ts +1 -0
- package/dist/execution/index.d.ts.map +1 -1
- package/dist/execution/index.js +1 -0
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/local.d.ts +13 -0
- package/dist/execution/local.d.ts.map +1 -1
- package/dist/execution/local.js +145 -19
- package/dist/execution/local.js.map +1 -1
- package/dist/execution/output.d.ts +27 -0
- package/dist/execution/output.d.ts.map +1 -0
- package/dist/execution/output.js +105 -0
- package/dist/execution/output.js.map +1 -0
- package/dist/execution/remote.d.ts +26 -2
- package/dist/execution/remote.d.ts.map +1 -1
- package/dist/execution/remote.js +86 -15
- package/dist/execution/remote.js.map +1 -1
- package/dist/manager/agent/lifecycle.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.js +1 -0
- package/dist/manager/agent/lifecycle.js.map +1 -1
- package/dist/plugin/lifecycle.d.ts.map +1 -1
- package/dist/plugin/lifecycle.js +9 -3
- package/dist/plugin/lifecycle.js.map +1 -1
- package/dist/process/kill-tree.d.ts +11 -13
- package/dist/process/kill-tree.d.ts.map +1 -1
- package/dist/process/kill-tree.js +11 -13
- package/dist/process/kill-tree.js.map +1 -1
- package/dist/provider/errors.d.ts +1 -0
- package/dist/provider/errors.d.ts.map +1 -1
- package/dist/provider/errors.js +39 -1
- package/dist/provider/errors.js.map +1 -1
- package/dist/provider/fallback.d.ts.map +1 -1
- package/dist/provider/fallback.js +31 -0
- package/dist/provider/fallback.js.map +1 -1
- package/dist/provider/idle-timeout.d.ts.map +1 -1
- package/dist/provider/idle-timeout.js +5 -0
- package/dist/provider/idle-timeout.js.map +1 -1
- package/dist/provider/retry.d.ts.map +1 -1
- package/dist/provider/retry.js +5 -0
- package/dist/provider/retry.js.map +1 -1
- package/dist/public-runtime.d.ts +7 -5
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +9 -4
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-types.d.ts +2 -1
- package/dist/public-types.d.ts.map +1 -1
- package/dist/registry/tool/execute.d.ts +7 -1
- package/dist/registry/tool/execute.d.ts.map +1 -1
- package/dist/registry/tool/execute.js +201 -54
- package/dist/registry/tool/execute.js.map +1 -1
- package/dist/run/command-gate.d.ts.map +1 -1
- package/dist/run/command-gate.js +11 -0
- package/dist/run/command-gate.js.map +1 -1
- package/dist/run/reporter.d.ts.map +1 -1
- package/dist/run/reporter.js +6 -1
- package/dist/run/reporter.js.map +1 -1
- package/dist/run/workspace-fingerprint.d.ts +4 -4
- package/dist/run/workspace-fingerprint.d.ts.map +1 -1
- package/dist/run/workspace-fingerprint.js +6 -4
- package/dist/run/workspace-fingerprint.js.map +1 -1
- package/dist/runtime/bidi/index.d.ts +1 -1
- package/dist/runtime/bidi/index.d.ts.map +1 -1
- package/dist/runtime/bidi/index.js +1 -1
- package/dist/runtime/bidi/index.js.map +1 -1
- package/dist/runtime/bidi/session.d.ts +19 -0
- package/dist/runtime/bidi/session.d.ts.map +1 -1
- package/dist/runtime/bidi/session.js +150 -26
- package/dist/runtime/bidi/session.js.map +1 -1
- package/dist/runtime/query/executor.d.ts +43 -1
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +824 -174
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.d.ts +5 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +47 -4
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +1 -0
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +34 -14
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/advisory.js +2 -2
- package/dist/runtime/query/iteration/phases/advisory.js.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.js +84 -22
- package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
- package/dist/runtime/query/iteration/provider-rejected-image.d.ts +13 -0
- package/dist/runtime/query/iteration/provider-rejected-image.d.ts.map +1 -0
- package/dist/runtime/query/iteration/provider-rejected-image.js +91 -0
- package/dist/runtime/query/iteration/provider-rejected-image.js.map +1 -0
- package/dist/runtime/query/iteration/stream-turn.d.ts +4 -1
- package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.js +7 -3
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/runtime/query/request-rich-content.d.ts +13 -0
- package/dist/runtime/query/request-rich-content.d.ts.map +1 -1
- package/dist/runtime/query/request-rich-content.js +173 -17
- package/dist/runtime/query/request-rich-content.js.map +1 -1
- package/dist/runtime/query/result.d.ts.map +1 -1
- package/dist/runtime/query/result.js +1 -0
- package/dist/runtime/query/result.js.map +1 -1
- package/dist/runtime/query/resume-pending.d.ts +5 -1
- package/dist/runtime/query/resume-pending.d.ts.map +1 -1
- package/dist/runtime/query/resume-pending.js +66 -1
- package/dist/runtime/query/resume-pending.js.map +1 -1
- package/dist/runtime/query/tool-output-budget.d.ts.map +1 -1
- package/dist/runtime/query/tool-output-budget.js +45 -16
- package/dist/runtime/query/tool-output-budget.js.map +1 -1
- package/dist/runtime/query/tooling.d.ts +6 -0
- package/dist/runtime/query/tooling.d.ts.map +1 -1
- package/dist/runtime/query/tooling.js +4 -0
- package/dist/runtime/query/tooling.js.map +1 -1
- package/dist/sandbox/provider/local.d.ts.map +1 -1
- package/dist/sandbox/provider/local.js +193 -41
- package/dist/sandbox/provider/local.js.map +1 -1
- package/dist/scheduler/delegating.d.ts +2 -1
- package/dist/scheduler/delegating.d.ts.map +1 -1
- package/dist/scheduler/delegating.js +6 -5
- package/dist/scheduler/delegating.js.map +1 -1
- package/dist/scheduler/local.d.ts +2 -1
- package/dist/scheduler/local.d.ts.map +1 -1
- package/dist/scheduler/local.js +2 -2
- package/dist/scheduler/local.js.map +1 -1
- package/dist/session/workspace/driver.d.ts +12 -4
- package/dist/session/workspace/driver.d.ts.map +1 -1
- package/dist/session/workspace/git-worktree.d.ts +17 -3
- package/dist/session/workspace/git-worktree.d.ts.map +1 -1
- package/dist/session/workspace/git-worktree.js +153 -34
- package/dist/session/workspace/git-worktree.js.map +1 -1
- package/dist/skills/registry.d.ts +15 -0
- package/dist/skills/registry.d.ts.map +1 -1
- package/dist/skills/registry.js +33 -0
- package/dist/skills/registry.js.map +1 -1
- package/dist/store/attachment/index.d.ts +24 -2
- package/dist/store/attachment/index.d.ts.map +1 -1
- package/dist/store/attachment/index.js +52 -5
- package/dist/store/attachment/index.js.map +1 -1
- package/dist/tools/builtins/computer-use.d.ts.map +1 -1
- package/dist/tools/builtins/computer-use.js +39 -2
- package/dist/tools/builtins/computer-use.js.map +1 -1
- package/dist/tools/builtins/lsp.d.ts +1 -1
- package/dist/tools/builtins/run-code.d.ts.map +1 -1
- package/dist/tools/builtins/run-code.js +24 -16
- package/dist/tools/builtins/run-code.js.map +1 -1
- package/dist/tools/builtins/skill.d.ts +2 -1
- package/dist/tools/builtins/skill.d.ts.map +1 -1
- package/dist/tools/builtins/skill.js +250 -6
- package/dist/tools/builtins/skill.js.map +1 -1
- package/dist/tools/coordinator/agent.d.ts.map +1 -1
- package/dist/tools/coordinator/agent.js +100 -30
- package/dist/tools/coordinator/agent.js.map +1 -1
- package/dist/types/agent/base.d.ts +4 -0
- package/dist/types/agent/base.d.ts.map +1 -1
- package/dist/types/agent/factory.d.ts +1 -0
- package/dist/types/agent/factory.d.ts.map +1 -1
- package/dist/types/agent/manager.d.ts +1 -1
- package/dist/types/agent/manager.d.ts.map +1 -1
- package/dist/types/agent/scheduler.d.ts +10 -1
- package/dist/types/agent/scheduler.d.ts.map +1 -1
- package/dist/types/authorization/index.d.ts +12 -12
- package/dist/types/bidi/index.d.ts +19 -2
- package/dist/types/bidi/index.d.ts.map +1 -1
- package/dist/types/computer-use/index.d.ts +18 -0
- package/dist/types/computer-use/index.d.ts.map +1 -1
- package/dist/types/connector/execution-context.d.ts +3 -1
- package/dist/types/connector/execution-context.d.ts.map +1 -1
- package/dist/types/execution/index.d.ts +43 -1
- package/dist/types/execution/index.d.ts.map +1 -1
- package/dist/types/hitl/index.d.ts +11 -0
- package/dist/types/hitl/index.d.ts.map +1 -1
- package/dist/types/hitl/index.js.map +1 -1
- package/dist/types/message/index.d.ts +31 -1
- package/dist/types/message/index.d.ts.map +1 -1
- package/dist/types/message/index.js +43 -0
- package/dist/types/message/index.js.map +1 -1
- package/dist/types/plugin/index.d.ts +14 -1
- package/dist/types/plugin/index.d.ts.map +1 -1
- package/dist/types/plugin/index.js +1 -0
- package/dist/types/plugin/index.js.map +1 -1
- package/dist/types/provider/error.d.ts +4 -1
- package/dist/types/provider/error.d.ts.map +1 -1
- package/dist/types/provider/interface.d.ts +14 -0
- package/dist/types/provider/interface.d.ts.map +1 -1
- package/dist/types/run/events.d.ts +10 -2
- package/dist/types/run/events.d.ts.map +1 -1
- package/dist/types/run/events.js.map +1 -1
- package/dist/types/sandbox/index.d.ts +15 -10
- package/dist/types/sandbox/index.d.ts.map +1 -1
- package/dist/types/sandbox/index.js.map +1 -1
- package/dist/types/tool/index.d.ts +129 -12
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/dist/types/workspace/ref.d.ts +5 -0
- package/dist/types/workspace/ref.d.ts.map +1 -1
- package/dist/utils/abort.d.ts +8 -0
- package/dist/utils/abort.d.ts.map +1 -1
- package/dist/utils/abort.js +16 -0
- package/dist/utils/abort.js.map +1 -1
- package/package.json +4 -4
- package/src/agents/ReactiveAgent.ts +4 -0
- package/src/agents/SupervisorAgent.ts +4 -0
- package/src/agents/runAgent.ts +8 -0
- package/src/authorization/gate.ts +1 -1
- package/src/connector/index.ts +2 -0
- package/src/connector/mcp/adapter.ts +82 -21
- package/src/connector/mcp/client.ts +2 -1
- package/src/connector/mcp/http-redirect.ts +32 -0
- package/src/connector/mcp/http-sse.ts +5 -0
- package/src/connector/mcp/image-admission.ts +272 -0
- package/src/connector/mcp/stdio.ts +63 -21
- package/src/connector/mcp/streamable-http.ts +3 -0
- package/src/constants/tools/index.ts +1 -1
- package/src/directory/derive-supervisor.ts +5 -0
- package/src/directory/derive.ts +5 -0
- package/src/directory/load.ts +1 -0
- package/src/directory/types.ts +2 -0
- package/src/execution/base.ts +124 -7
- package/src/execution/code-runtime/types.ts +15 -1
- package/src/execution/code-runtime/worker.ts +72 -9
- package/src/execution/errors.ts +24 -0
- package/src/execution/factory.ts +1 -0
- package/src/execution/hybrid.ts +47 -16
- package/src/execution/index.ts +1 -0
- package/src/execution/local.ts +164 -19
- package/src/execution/output.ts +104 -0
- package/src/execution/remote.ts +122 -17
- package/src/manager/agent/lifecycle.ts +1 -0
- package/src/plugin/lifecycle.ts +9 -3
- package/src/process/kill-tree.ts +11 -13
- package/src/provider/errors.ts +40 -1
- package/src/provider/fallback.ts +39 -0
- package/src/provider/idle-timeout.ts +8 -0
- package/src/provider/retry.ts +8 -0
- package/src/public-runtime.ts +23 -2
- package/src/public-types.ts +11 -0
- package/src/registry/tool/execute.ts +230 -64
- package/src/run/command-gate.ts +12 -0
- package/src/run/reporter.ts +7 -1
- package/src/run/workspace-fingerprint.ts +5 -4
- package/src/runtime/bidi/index.ts +1 -1
- package/src/runtime/bidi/session.ts +177 -28
- package/src/runtime/query/executor.ts +987 -172
- package/src/runtime/query/index.ts +59 -6
- package/src/runtime/query/iteration/index.ts +69 -29
- package/src/runtime/query/iteration/phases/advisory.ts +2 -2
- package/src/runtime/query/iteration/phases/tool-review.ts +87 -23
- package/src/runtime/query/iteration/provider-rejected-image.ts +106 -0
- package/src/runtime/query/iteration/stream-turn.ts +12 -2
- package/src/runtime/query/request-rich-content.ts +213 -15
- package/src/runtime/query/result.ts +1 -0
- package/src/runtime/query/resume-pending.ts +83 -1
- package/src/runtime/query/tool-output-budget.ts +52 -17
- package/src/runtime/query/tooling.ts +10 -0
- package/src/sandbox/provider/local.ts +213 -45
- package/src/scheduler/delegating.ts +6 -5
- package/src/scheduler/local.ts +3 -2
- package/src/session/workspace/driver.ts +12 -4
- package/src/session/workspace/git-worktree.ts +173 -34
- package/src/skills/registry.ts +47 -0
- package/src/store/attachment/index.ts +100 -15
- package/src/tools/builtins/computer-use.ts +45 -2
- package/src/tools/builtins/run-code.ts +24 -16
- package/src/tools/builtins/skill.ts +325 -7
- package/src/tools/coordinator/agent.ts +108 -33
- package/src/types/agent/base.ts +4 -0
- package/src/types/agent/factory.ts +1 -0
- package/src/types/agent/manager.ts +1 -1
- package/src/types/agent/scheduler.ts +10 -1
- package/src/types/bidi/index.ts +19 -2
- package/src/types/computer-use/index.ts +19 -0
- package/src/types/connector/execution-context.ts +3 -0
- package/src/types/execution/index.ts +46 -1
- package/src/types/hitl/index.ts +11 -0
- package/src/types/message/index.ts +78 -1
- package/src/types/plugin/index.ts +15 -0
- package/src/types/provider/error.ts +4 -1
- package/src/types/provider/interface.ts +18 -0
- package/src/types/run/events.ts +18 -4
- package/src/types/sandbox/index.ts +15 -10
- package/src/types/tool/index.ts +159 -15
- package/src/types/workspace/ref.ts +5 -0
- package/src/utils/abort.ts +18 -0
|
@@ -170,13 +170,14 @@ export class MCPClient {
|
|
|
170
170
|
async disconnect(): Promise<void> {
|
|
171
171
|
const reason = new Error('MCPClient disconnecting')
|
|
172
172
|
this.abortCancellations(reason)
|
|
173
|
-
|
|
173
|
+
const alreadyDisconnected = this.status === 'disconnected'
|
|
174
174
|
|
|
175
175
|
this.rejectAllPending('MCPClient disconnecting')
|
|
176
176
|
|
|
177
177
|
await this.transport.close()
|
|
178
178
|
this.status = 'disconnected'
|
|
179
179
|
this.connectedAt = undefined
|
|
180
|
+
if (alreadyDisconnected) return
|
|
180
181
|
this.log.info('MCP client disconnected')
|
|
181
182
|
this.emitLifecycle({ type: 'mcp_client_disconnected', clientId: this.id })
|
|
182
183
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A request reached the configured MCP server, but no response from the
|
|
3
|
+
* requested operation was received because that server redirected it.
|
|
4
|
+
*
|
|
5
|
+
* This stays an internal transport-to-adapter signal. The public tool result
|
|
6
|
+
* carries the stable, serializable outcome fields instead of exposing an
|
|
7
|
+
* Error subclass as API.
|
|
8
|
+
*/
|
|
9
|
+
export class MCPHttpRedirectError extends Error {
|
|
10
|
+
readonly code = 'mcp_http_redirect_refused'
|
|
11
|
+
readonly remoteOutcomeUnknown: boolean
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
readonly status: number,
|
|
15
|
+
readonly requestMethod?: string,
|
|
16
|
+
) {
|
|
17
|
+
const base = `MCP HTTP ${status} redirect was not followed because authenticated requests must remain at their configured endpoint. Configure the final MCP endpoint directly.`
|
|
18
|
+
const outcome =
|
|
19
|
+
requestMethod === 'tools/call'
|
|
20
|
+
? ' The configured MCP server received the tool call, so the remote outcome is unknown; do not automatically retry.'
|
|
21
|
+
: ''
|
|
22
|
+
super(base + outcome)
|
|
23
|
+
this.name = 'MCPHttpRedirectError'
|
|
24
|
+
this.remoteOutcomeUnknown = requestMethod === 'tools/call'
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function refuseMcpHttpRedirect(response: Response, requestMethod?: string): void {
|
|
29
|
+
if (response.status >= 300 && response.status < 400) {
|
|
30
|
+
throw new MCPHttpRedirectError(response.status, requestMethod)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
import { SCOPE_ATTRIBUTE } from '../../utils/log/types.js'
|
|
8
8
|
import { type Logger, resolveLogger } from '../../utils/logger.js'
|
|
9
9
|
import { ConnectorHttpOperation, validateConnectorTimeoutMs } from '../http-operation.js'
|
|
10
|
+
import { refuseMcpHttpRedirect } from './http-redirect.js'
|
|
10
11
|
|
|
11
12
|
const DEFAULT_TIMEOUT_MS = 30_000
|
|
12
13
|
|
|
@@ -111,10 +112,12 @@ export class HttpSseTransport implements MCPTransport {
|
|
|
111
112
|
...this.config.headers,
|
|
112
113
|
},
|
|
113
114
|
body: JSON.stringify(message),
|
|
115
|
+
redirect: 'manual',
|
|
114
116
|
signal: operation.signal,
|
|
115
117
|
}),
|
|
116
118
|
)
|
|
117
119
|
|
|
120
|
+
refuseMcpHttpRedirect(response, message.method)
|
|
118
121
|
if (!response.ok) {
|
|
119
122
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
|
120
123
|
}
|
|
@@ -202,9 +205,11 @@ export class HttpSseTransport implements MCPTransport {
|
|
|
202
205
|
Accept: 'text/event-stream',
|
|
203
206
|
...this.config.headers,
|
|
204
207
|
},
|
|
208
|
+
redirect: 'manual',
|
|
205
209
|
signal,
|
|
206
210
|
})
|
|
207
211
|
|
|
212
|
+
refuseMcpHttpRedirect(response)
|
|
208
213
|
if (!response.ok || !response.body) {
|
|
209
214
|
throw new Error(`SSE connection failed: HTTP ${response.status}`)
|
|
210
215
|
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import type { MCPContentBlock } from '../../types/connector/index.js'
|
|
2
|
+
|
|
3
|
+
const MAX_ENCODED_BYTES = 24 * 1024 * 1024
|
|
4
|
+
const MAX_DIMENSION = 16_384
|
|
5
|
+
const MAX_PIXELS = 40_000_000
|
|
6
|
+
|
|
7
|
+
type MCPImageBlock = Extract<MCPContentBlock, { type: 'image' }>
|
|
8
|
+
type ImageMediaType = 'image/png' | 'image/jpeg' | 'image/webp' | 'image/gif'
|
|
9
|
+
|
|
10
|
+
const IMAGE_MEDIA_TYPES = new Set<ImageMediaType>([
|
|
11
|
+
'image/png',
|
|
12
|
+
'image/jpeg',
|
|
13
|
+
'image/webp',
|
|
14
|
+
'image/gif',
|
|
15
|
+
])
|
|
16
|
+
|
|
17
|
+
function isImageMediaType(value: string): value is ImageMediaType {
|
|
18
|
+
return IMAGE_MEDIA_TYPES.has(value as ImageMediaType)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function decodeCanonicalBase64(value: string): Uint8Array | null {
|
|
22
|
+
if (value.length === 0 || value.length > MAX_ENCODED_BYTES) return null
|
|
23
|
+
if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)) {
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
const decoded = Buffer.from(value, 'base64')
|
|
27
|
+
return decoded.toString('base64') === value ? decoded : null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function dimensionsAreSafe(width: number, height: number): boolean {
|
|
31
|
+
return (
|
|
32
|
+
Number.isSafeInteger(width) &&
|
|
33
|
+
Number.isSafeInteger(height) &&
|
|
34
|
+
width > 0 &&
|
|
35
|
+
height > 0 &&
|
|
36
|
+
width <= MAX_DIMENSION &&
|
|
37
|
+
height <= MAX_DIMENSION &&
|
|
38
|
+
width * height <= MAX_PIXELS
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const crcTable = (() => {
|
|
43
|
+
const table = new Uint32Array(256)
|
|
44
|
+
for (let n = 0; n < 256; n += 1) {
|
|
45
|
+
let value = n
|
|
46
|
+
for (let bit = 0; bit < 8; bit += 1) {
|
|
47
|
+
value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1
|
|
48
|
+
}
|
|
49
|
+
table[n] = value >>> 0
|
|
50
|
+
}
|
|
51
|
+
return table
|
|
52
|
+
})()
|
|
53
|
+
|
|
54
|
+
function crc32(bytes: Uint8Array, start: number, end: number): number {
|
|
55
|
+
let crc = 0xffffffff
|
|
56
|
+
for (let offset = start; offset < end; offset += 1) {
|
|
57
|
+
crc = (crcTable[(crc ^ (bytes[offset] ?? 0)) & 0xff] ?? 0) ^ (crc >>> 8)
|
|
58
|
+
}
|
|
59
|
+
return (crc ^ 0xffffffff) >>> 0
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isPng(bytes: Uint8Array): boolean {
|
|
63
|
+
const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)
|
|
64
|
+
if (bytes.length < 45 || !buffer.subarray(0, 8).equals(Buffer.from('89504e470d0a1a0a', 'hex'))) {
|
|
65
|
+
return false
|
|
66
|
+
}
|
|
67
|
+
let offset = 8
|
|
68
|
+
let sawHeader = false
|
|
69
|
+
let sawData = false
|
|
70
|
+
while (offset + 12 <= bytes.length) {
|
|
71
|
+
const length = buffer.readUInt32BE(offset)
|
|
72
|
+
const typeStart = offset + 4
|
|
73
|
+
const dataStart = typeStart + 4
|
|
74
|
+
const dataEnd = dataStart + length
|
|
75
|
+
const chunkEnd = dataEnd + 4
|
|
76
|
+
if (dataEnd < dataStart || chunkEnd > bytes.length) return false
|
|
77
|
+
const type = buffer.toString('ascii', typeStart, dataStart)
|
|
78
|
+
if (buffer.readUInt32BE(dataEnd) !== crc32(bytes, typeStart, dataEnd)) return false
|
|
79
|
+
if (!sawHeader) {
|
|
80
|
+
if (type !== 'IHDR' || length !== 13) return false
|
|
81
|
+
const width = buffer.readUInt32BE(dataStart)
|
|
82
|
+
const height = buffer.readUInt32BE(dataStart + 4)
|
|
83
|
+
if (!dimensionsAreSafe(width, height)) return false
|
|
84
|
+
sawHeader = true
|
|
85
|
+
} else if (type === 'IHDR') return false
|
|
86
|
+
if (type === 'IDAT') sawData = true
|
|
87
|
+
if (type === 'IEND') return length === 0 && sawHeader && sawData && chunkEnd === bytes.length
|
|
88
|
+
offset = chunkEnd
|
|
89
|
+
}
|
|
90
|
+
return false
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const JPEG_SOF_MARKERS = new Set([
|
|
94
|
+
0xc0, 0xc1, 0xc2, 0xc3, 0xc5, 0xc6, 0xc7, 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf,
|
|
95
|
+
])
|
|
96
|
+
|
|
97
|
+
function isJpeg(bytes: Uint8Array): boolean {
|
|
98
|
+
const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)
|
|
99
|
+
if (bytes.length < 11 || bytes[0] !== 0xff || bytes[1] !== 0xd8) return false
|
|
100
|
+
let offset = 2
|
|
101
|
+
let sawFrame = false
|
|
102
|
+
let sawScan = false
|
|
103
|
+
let inScan = false
|
|
104
|
+
while (offset < bytes.length) {
|
|
105
|
+
if (inScan && bytes[offset] !== 0xff) {
|
|
106
|
+
offset += 1
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
if (bytes[offset] !== 0xff) return false
|
|
110
|
+
while (offset < bytes.length && bytes[offset] === 0xff) offset += 1
|
|
111
|
+
if (offset >= bytes.length) return false
|
|
112
|
+
const marker = bytes[offset] ?? 0
|
|
113
|
+
offset += 1
|
|
114
|
+
if (inScan && (marker === 0x00 || (marker >= 0xd0 && marker <= 0xd7))) continue
|
|
115
|
+
inScan = false
|
|
116
|
+
if (marker === 0xd9) return sawFrame && sawScan && offset === bytes.length
|
|
117
|
+
if (marker === 0xd8 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) continue
|
|
118
|
+
if (offset + 2 > bytes.length) return false
|
|
119
|
+
const length = buffer.readUInt16BE(offset)
|
|
120
|
+
if (length < 2 || offset + length > bytes.length) return false
|
|
121
|
+
if (JPEG_SOF_MARKERS.has(marker)) {
|
|
122
|
+
if (length < 8) return false
|
|
123
|
+
const height = buffer.readUInt16BE(offset + 3)
|
|
124
|
+
const width = buffer.readUInt16BE(offset + 5)
|
|
125
|
+
if (!dimensionsAreSafe(width, height)) return false
|
|
126
|
+
sawFrame = true
|
|
127
|
+
}
|
|
128
|
+
offset += length
|
|
129
|
+
if (marker === 0xda) {
|
|
130
|
+
sawScan = true
|
|
131
|
+
inScan = true
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return false
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function skipGifSubBlocks(bytes: Uint8Array, start: number): number | null {
|
|
138
|
+
let offset = start
|
|
139
|
+
while (offset < bytes.length) {
|
|
140
|
+
const length = bytes[offset] ?? 0
|
|
141
|
+
offset += 1
|
|
142
|
+
if (length === 0) return offset
|
|
143
|
+
if (offset + length > bytes.length) return null
|
|
144
|
+
offset += length
|
|
145
|
+
}
|
|
146
|
+
return null
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function isGif(bytes: Uint8Array): boolean {
|
|
150
|
+
const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)
|
|
151
|
+
if (bytes.length < 14) return false
|
|
152
|
+
const signature = buffer.toString('ascii', 0, 6)
|
|
153
|
+
if (signature !== 'GIF87a' && signature !== 'GIF89a') return false
|
|
154
|
+
if (!dimensionsAreSafe(buffer.readUInt16LE(6), buffer.readUInt16LE(8))) return false
|
|
155
|
+
let offset = 13
|
|
156
|
+
const packed = bytes[10] ?? 0
|
|
157
|
+
if ((packed & 0x80) !== 0) offset += 3 * 2 ** ((packed & 0x07) + 1)
|
|
158
|
+
if (offset > bytes.length) return false
|
|
159
|
+
let sawImage = false
|
|
160
|
+
while (offset < bytes.length) {
|
|
161
|
+
const marker = bytes[offset] ?? 0
|
|
162
|
+
offset += 1
|
|
163
|
+
if (marker === 0x3b) return sawImage && offset === bytes.length
|
|
164
|
+
if (marker === 0x21) {
|
|
165
|
+
if (offset >= bytes.length) return false
|
|
166
|
+
offset += 1
|
|
167
|
+
const next = skipGifSubBlocks(bytes, offset)
|
|
168
|
+
if (next === null) return false
|
|
169
|
+
offset = next
|
|
170
|
+
continue
|
|
171
|
+
}
|
|
172
|
+
if (marker !== 0x2c || offset + 9 > bytes.length) return false
|
|
173
|
+
const width = buffer.readUInt16LE(offset + 4)
|
|
174
|
+
const height = buffer.readUInt16LE(offset + 6)
|
|
175
|
+
if (!dimensionsAreSafe(width, height)) return false
|
|
176
|
+
const imagePacked = bytes[offset + 8] ?? 0
|
|
177
|
+
offset += 9
|
|
178
|
+
if ((imagePacked & 0x80) !== 0) offset += 3 * 2 ** ((imagePacked & 0x07) + 1)
|
|
179
|
+
if (offset >= bytes.length) return false
|
|
180
|
+
offset += 1
|
|
181
|
+
const next = skipGifSubBlocks(bytes, offset)
|
|
182
|
+
if (next === null) return false
|
|
183
|
+
offset = next
|
|
184
|
+
sawImage = true
|
|
185
|
+
}
|
|
186
|
+
return false
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function readUInt24LE(bytes: Uint8Array, offset: number): number {
|
|
190
|
+
return (bytes[offset] ?? 0) | ((bytes[offset + 1] ?? 0) << 8) | ((bytes[offset + 2] ?? 0) << 16)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function webpDimensions(type: string, data: Uint8Array): readonly [number, number] | null {
|
|
194
|
+
const buffer = Buffer.from(data.buffer, data.byteOffset, data.byteLength)
|
|
195
|
+
if (type === 'VP8X') {
|
|
196
|
+
if (data.length < 10) return null
|
|
197
|
+
return [readUInt24LE(data, 4) + 1, readUInt24LE(data, 7) + 1]
|
|
198
|
+
}
|
|
199
|
+
if (type === 'VP8L') {
|
|
200
|
+
if (data.length < 5 || data[0] !== 0x2f) return null
|
|
201
|
+
const bits = buffer.readUInt32LE(1)
|
|
202
|
+
return [(bits & 0x3fff) + 1, ((bits >>> 14) & 0x3fff) + 1]
|
|
203
|
+
}
|
|
204
|
+
if (type === 'VP8 ') {
|
|
205
|
+
if (data.length < 10 || data[3] !== 0x9d || data[4] !== 0x01 || data[5] !== 0x2a) {
|
|
206
|
+
return null
|
|
207
|
+
}
|
|
208
|
+
return [buffer.readUInt16LE(6) & 0x3fff, buffer.readUInt16LE(8) & 0x3fff]
|
|
209
|
+
}
|
|
210
|
+
return null
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function isWebp(bytes: Uint8Array): boolean {
|
|
214
|
+
const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)
|
|
215
|
+
if (
|
|
216
|
+
bytes.length < 20 ||
|
|
217
|
+
buffer.toString('ascii', 0, 4) !== 'RIFF' ||
|
|
218
|
+
buffer.readUInt32LE(4) + 8 !== bytes.length ||
|
|
219
|
+
buffer.toString('ascii', 8, 12) !== 'WEBP'
|
|
220
|
+
) {
|
|
221
|
+
return false
|
|
222
|
+
}
|
|
223
|
+
let offset = 12
|
|
224
|
+
let dimensions: readonly [number, number] | null = null
|
|
225
|
+
let sawImagePayload = false
|
|
226
|
+
while (offset + 8 <= bytes.length) {
|
|
227
|
+
const type = buffer.toString('ascii', offset, offset + 4)
|
|
228
|
+
const length = buffer.readUInt32LE(offset + 4)
|
|
229
|
+
const dataStart = offset + 8
|
|
230
|
+
const dataEnd = dataStart + length
|
|
231
|
+
if (dataEnd < dataStart || dataEnd > bytes.length) return false
|
|
232
|
+
const current = webpDimensions(type, bytes.subarray(dataStart, dataEnd))
|
|
233
|
+
if (current && dimensions === null) dimensions = current
|
|
234
|
+
if (type === 'VP8 ' || type === 'VP8L') sawImagePayload = current !== null
|
|
235
|
+
offset = dataEnd + (length & 1)
|
|
236
|
+
}
|
|
237
|
+
return (
|
|
238
|
+
offset === bytes.length &&
|
|
239
|
+
dimensions !== null &&
|
|
240
|
+
sawImagePayload &&
|
|
241
|
+
dimensionsAreSafe(dimensions[0], dimensions[1])
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function imageIsAdmitted(block: MCPImageBlock): boolean {
|
|
246
|
+
if (!isImageMediaType(block.mimeType)) return false
|
|
247
|
+
const bytes = decodeCanonicalBase64(block.data)
|
|
248
|
+
if (!bytes) return false
|
|
249
|
+
switch (block.mimeType) {
|
|
250
|
+
case 'image/png':
|
|
251
|
+
return isPng(bytes)
|
|
252
|
+
case 'image/jpeg':
|
|
253
|
+
return isJpeg(bytes)
|
|
254
|
+
case 'image/gif':
|
|
255
|
+
return isGif(bytes)
|
|
256
|
+
case 'image/webp':
|
|
257
|
+
return isWebp(bytes)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Admit an MCP image batch atomically before any member becomes model input.
|
|
263
|
+
*
|
|
264
|
+
* A remote server controls the declared MIME and bytes. Canonical base64 is
|
|
265
|
+
* only an encoding property; the decoded value must also be a complete,
|
|
266
|
+
* bounded raster container whose format agrees with that declaration. One bad
|
|
267
|
+
* member withholds the complete batch so a model is never shown a partial
|
|
268
|
+
* result while the host retains every raw block for inspection.
|
|
269
|
+
*/
|
|
270
|
+
export function admitMcpImageBatch(images: readonly MCPImageBlock[]): boolean {
|
|
271
|
+
return images.length === 0 || images.every(imageIsAdmitted)
|
|
272
|
+
}
|
|
@@ -95,6 +95,10 @@ export class StdioTransport implements MCPTransport {
|
|
|
95
95
|
private closeHandlers: Array<() => void> = []
|
|
96
96
|
private errorHandlers: Array<(error: Error) => void> = []
|
|
97
97
|
private connected = false
|
|
98
|
+
/** The response channel ended for this process generation. */
|
|
99
|
+
private retired = false
|
|
100
|
+
/** Prevents a late event from an old child retiring its replacement. */
|
|
101
|
+
private generation = 0
|
|
98
102
|
/** True between kill and the process's own `close` event. */
|
|
99
103
|
private exitPending = false
|
|
100
104
|
private buffer = ''
|
|
@@ -109,38 +113,47 @@ export class StdioTransport implements MCPTransport {
|
|
|
109
113
|
|
|
110
114
|
async connect(): Promise<void> {
|
|
111
115
|
if (this.connected) return
|
|
116
|
+
// A protocol channel can end while its process stays alive. Reconnect
|
|
117
|
+
// must reap that old owner before publishing a new generation; otherwise
|
|
118
|
+
// both children remain live and teardown owns only the newest one.
|
|
119
|
+
if (this.process) await this.close()
|
|
120
|
+
const generation = ++this.generation
|
|
121
|
+
this.retired = false
|
|
122
|
+
this.buffer = ''
|
|
112
123
|
|
|
113
|
-
|
|
124
|
+
const child = spawn(this.config.command, this.config.args ?? [], {
|
|
114
125
|
env: buildChildEnv(this.config),
|
|
115
126
|
cwd: this.config.cwd,
|
|
116
127
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
117
128
|
})
|
|
129
|
+
this.process = child
|
|
118
130
|
|
|
119
|
-
|
|
131
|
+
child.stdout?.on('data', (chunk: Buffer) => {
|
|
132
|
+
if (generation !== this.generation || this.retired) return
|
|
120
133
|
this.buffer += chunk.toString('utf-8')
|
|
121
134
|
this.processBuffer()
|
|
122
135
|
})
|
|
136
|
+
child.stdout?.once('end', () => this.retire(generation, 'close'))
|
|
137
|
+
child.stdout?.once('close', () => this.retire(generation, 'close'))
|
|
138
|
+
child.stdout?.once('error', (err) => this.retire(generation, 'error', err))
|
|
123
139
|
|
|
124
|
-
|
|
140
|
+
child.stderr?.on('data', (chunk: Buffer) => {
|
|
125
141
|
this.log.warn('MCP server stderr', {
|
|
126
142
|
'namzu.mcp.stderr': chunk.toString('utf-8').trim(),
|
|
127
143
|
})
|
|
128
144
|
})
|
|
129
145
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
// `MCPClient` the session ended.
|
|
146
|
+
child.stdin?.once('close', () => this.retire(generation, 'close'))
|
|
147
|
+
child.stdin?.once('error', (err) => this.retire(generation, 'error', err))
|
|
148
|
+
|
|
149
|
+
child.on('close', (code) => {
|
|
150
|
+
if (this.process === child) this.process = null
|
|
136
151
|
this.exitPending = false
|
|
137
|
-
this.
|
|
152
|
+
this.log.info('MCP server process exited', { 'namzu.mcp.exit_code': code })
|
|
153
|
+
this.retire(generation, 'close')
|
|
138
154
|
})
|
|
139
155
|
|
|
140
|
-
|
|
141
|
-
this.connected = false
|
|
142
|
-
for (const handler of this.errorHandlers) handler(err)
|
|
143
|
-
})
|
|
156
|
+
child.on('error', (err) => this.retire(generation, 'error', err))
|
|
144
157
|
|
|
145
158
|
this.connected = true
|
|
146
159
|
this.log.info('StdioTransport connected', {
|
|
@@ -175,7 +188,10 @@ export class StdioTransport implements MCPTransport {
|
|
|
175
188
|
// so both settle this, and two timers make a hang impossible: the
|
|
176
189
|
// first escalates to SIGKILL for a child ignoring SIGTERM, the second
|
|
177
190
|
// gives up waiting. Neither holds the event loop open.
|
|
178
|
-
if (child.pid === undefined)
|
|
191
|
+
if (child.pid === undefined) {
|
|
192
|
+
this.exitPending = false
|
|
193
|
+
return
|
|
194
|
+
}
|
|
179
195
|
await new Promise<void>((resolve) => {
|
|
180
196
|
let settled = false
|
|
181
197
|
const finish = (): void => {
|
|
@@ -185,18 +201,20 @@ export class StdioTransport implements MCPTransport {
|
|
|
185
201
|
clearTimeout(giveUp)
|
|
186
202
|
resolve()
|
|
187
203
|
}
|
|
188
|
-
child.once('
|
|
204
|
+
child.once('close', finish)
|
|
189
205
|
child.once('error', finish)
|
|
190
|
-
child.
|
|
206
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
207
|
+
child.kill('SIGTERM')
|
|
208
|
+
}
|
|
191
209
|
const escalate = setTimeout(() => child.kill('SIGKILL'), TERMINATE_GRACE_MS)
|
|
192
210
|
const giveUp = setTimeout(finish, TERMINATE_GRACE_MS * 2)
|
|
193
211
|
escalate.unref?.()
|
|
194
212
|
giveUp.unref?.()
|
|
195
213
|
})
|
|
196
|
-
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
// `
|
|
214
|
+
this.exitPending = false
|
|
215
|
+
// The active generation's terminal stream/process event owns handler
|
|
216
|
+
// retirement. A reconnect can register its handlers before `connect()`
|
|
217
|
+
// reaps an already-retired child, so `close()` must not clear them here.
|
|
200
218
|
}
|
|
201
219
|
|
|
202
220
|
/**
|
|
@@ -214,6 +232,30 @@ export class StdioTransport implements MCPTransport {
|
|
|
214
232
|
this.errorHandlers = []
|
|
215
233
|
}
|
|
216
234
|
|
|
235
|
+
/**
|
|
236
|
+
* Publish one terminal result for the current response channel.
|
|
237
|
+
*
|
|
238
|
+
* A child may close stdout and keep running. That is just as terminal for
|
|
239
|
+
* JSON-RPC as process exit: no pending or future request can receive a
|
|
240
|
+
* response. Keeping the child handle is intentional; `close()` still owns
|
|
241
|
+
* and reaps that process after the protocol session has been retired.
|
|
242
|
+
*/
|
|
243
|
+
private retire(generation: number, kind: 'close' | 'error', error?: Error): void {
|
|
244
|
+
if (generation !== this.generation || this.retired) return
|
|
245
|
+
this.retired = true
|
|
246
|
+
this.connected = false
|
|
247
|
+
this.buffer = ''
|
|
248
|
+
const closeHandlers = this.closeHandlers
|
|
249
|
+
const errorHandlers = this.errorHandlers
|
|
250
|
+
this.clearHandlers()
|
|
251
|
+
if (kind === 'error') {
|
|
252
|
+
const reason = error ?? new Error('MCP stdio transport failed')
|
|
253
|
+
for (const handler of errorHandlers) handler(reason)
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
for (const handler of closeHandlers) handler()
|
|
257
|
+
}
|
|
258
|
+
|
|
217
259
|
async send(message: MCPJsonRpcMessage, options?: MCPTransportSendOptions): Promise<void> {
|
|
218
260
|
options?.signal?.throwIfAborted()
|
|
219
261
|
if (!this.process?.stdin?.writable) {
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
import { SCOPE_ATTRIBUTE } from '../../utils/log/types.js'
|
|
8
8
|
import { type Logger, resolveLogger } from '../../utils/logger.js'
|
|
9
9
|
import { ConnectorHttpOperation, validateConnectorTimeoutMs } from '../http-operation.js'
|
|
10
|
+
import { refuseMcpHttpRedirect } from './http-redirect.js'
|
|
10
11
|
|
|
11
12
|
const DEFAULT_TIMEOUT_MS = 30_000
|
|
12
13
|
|
|
@@ -83,10 +84,12 @@ export class StreamableHttpTransport implements MCPTransport {
|
|
|
83
84
|
method: 'POST',
|
|
84
85
|
headers: this.buildHeaders(),
|
|
85
86
|
body: JSON.stringify(message),
|
|
87
|
+
redirect: 'manual',
|
|
86
88
|
signal: operation.signal,
|
|
87
89
|
}),
|
|
88
90
|
)
|
|
89
91
|
|
|
92
|
+
refuseMcpHttpRedirect(response, message.method)
|
|
90
93
|
if (!response.ok) {
|
|
91
94
|
throw new Error(`StreamableHttpTransport: HTTP ${response.status}: ${response.statusText}`)
|
|
92
95
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Patterns the
|
|
1
|
+
// Patterns the authorization gate's `deny_dangerous_patterns` rule
|
|
2
2
|
// matches against the JSON-serialised tool input. The list is
|
|
3
3
|
// intentionally short and high-signal: the goal is to catch the
|
|
4
4
|
// canonical "I will brick the host" mistakes (filesystem wipes,
|
|
@@ -143,6 +143,11 @@ export function deriveSupervisorOptions(
|
|
|
143
143
|
maxRequestRichContentBytes: manifest.config.maxRequestRichContentBytes,
|
|
144
144
|
}
|
|
145
145
|
: {}),
|
|
146
|
+
...(manifest.config.attachmentResolveTimeoutMs !== undefined
|
|
147
|
+
? {
|
|
148
|
+
attachmentResolveTimeoutMs: manifest.config.attachmentResolveTimeoutMs,
|
|
149
|
+
}
|
|
150
|
+
: {}),
|
|
146
151
|
...(manifest.config.temperature !== undefined
|
|
147
152
|
? { temperature: manifest.config.temperature }
|
|
148
153
|
: {}),
|
package/src/directory/derive.ts
CHANGED
|
@@ -72,6 +72,11 @@ export function deriveRunOptions(
|
|
|
72
72
|
maxRequestRichContentBytes: manifest.config.maxRequestRichContentBytes,
|
|
73
73
|
}
|
|
74
74
|
: {}),
|
|
75
|
+
...(manifest.config.attachmentResolveTimeoutMs !== undefined
|
|
76
|
+
? {
|
|
77
|
+
attachmentResolveTimeoutMs: manifest.config.attachmentResolveTimeoutMs,
|
|
78
|
+
}
|
|
79
|
+
: {}),
|
|
75
80
|
...(input.identity ?? {}),
|
|
76
81
|
...(input.overrides ?? {}),
|
|
77
82
|
}
|
package/src/directory/load.ts
CHANGED
|
@@ -187,6 +187,7 @@ function readConfig(
|
|
|
187
187
|
take('timeoutMs', isNumber, 'finite number')
|
|
188
188
|
take('streamIdleTimeoutMs', isNumber, 'finite number')
|
|
189
189
|
take('maxRequestRichContentBytes', isNumber, 'finite number')
|
|
190
|
+
take('attachmentResolveTimeoutMs', isNumber, 'finite number')
|
|
190
191
|
|
|
191
192
|
// `metadata` is typed `Record<string, string>` and was admitted on
|
|
192
193
|
// `typeof === 'object'` alone — which an array also satisfies, and which
|
package/src/directory/types.ts
CHANGED
|
@@ -170,6 +170,8 @@ export interface DirectoryConfig {
|
|
|
170
170
|
readonly streamIdleTimeoutMs?: number
|
|
171
171
|
/** Accumulated inline image/document bytes per provider request; `0` disables. */
|
|
172
172
|
readonly maxRequestRichContentBytes?: number
|
|
173
|
+
/** Maximum stored-attachment materialization time; `0` disables the bound. */
|
|
174
|
+
readonly attachmentResolveTimeoutMs?: number
|
|
173
175
|
/** Names the agent in traces and events when declared. */
|
|
174
176
|
readonly name?: string
|
|
175
177
|
/** Free-form labels for an inspector. Never interpreted by this package. */
|