@qvac/harness 0.4.0 → 0.6.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 +740 -141
- package/README.md +75 -7
- package/dist/harness.bundle +72 -50
- package/dist-lib/index.d.ts +49 -39
- package/dist-lib/index.js +448 -161
- package/dist-lib/lib/audio-transcode-bare.d.ts +2 -2
- package/dist-lib/lib/audio-transcode-bare.js +28 -18
- package/dist-lib/lib/audio-transcode-node.d.ts +2 -2
- package/dist-lib/lib/capabilities/audio-resample.d.ts +7 -6
- package/dist-lib/lib/capabilities/audio-resample.js +30 -18
- package/dist-lib/lib/capabilities/completion.d.ts +20 -19
- package/dist-lib/lib/capabilities/completion.js +193 -132
- package/dist-lib/lib/capabilities/download.d.ts +6 -6
- package/dist-lib/lib/capabilities/download.js +15 -8
- package/dist-lib/lib/capabilities/embed.js +2 -1
- package/dist-lib/lib/capabilities/enhance.d.ts +6 -1
- package/dist-lib/lib/capabilities/enhance.js +7 -4
- package/dist-lib/lib/capabilities/invoke.js +7 -4
- package/dist-lib/lib/capabilities/kv-cache.js +2 -1
- package/dist-lib/lib/capabilities/preview-model-source.d.ts +9 -7
- package/dist-lib/lib/capabilities/preview-model-source.js +80 -28
- package/dist-lib/lib/capabilities/speech-text.js +5 -4
- package/dist-lib/lib/capabilities/tool-selection.js +10 -9
- package/dist-lib/lib/capabilities/tool-turns.js +30 -16
- package/dist-lib/lib/capabilities/usage-breakdown.js +2 -1
- package/dist-lib/lib/capabilities/voice.d.ts +5 -1
- package/dist-lib/lib/capabilities/voice.js +22 -12
- package/dist-lib/lib/connect.d.ts +13 -32
- package/dist-lib/lib/connect.js +170 -112
- package/dist-lib/lib/context-window.js +25 -21
- package/dist-lib/lib/contract-types.d.ts +35 -15
- package/dist-lib/lib/diagram-repair.d.ts +4 -4
- package/dist-lib/lib/diagram-repair.js +6 -3
- package/dist-lib/lib/docx/index.d.ts +3 -2
- package/dist-lib/lib/{error-fields.d.ts → guards.d.ts} +1 -0
- package/dist-lib/lib/{error-fields.js → guards.js} +3 -0
- package/dist-lib/lib/hardware/capability-probe.js +20 -10
- package/dist-lib/lib/hardware/gpu-classify.js +6 -3
- package/dist-lib/lib/hardware/gpu-probe-addon.js +8 -4
- package/dist-lib/lib/hardware/gpu-probe.js +4 -2
- package/dist-lib/lib/hardware/gpu-stats.native.cjs +8 -9
- package/dist-lib/lib/hardware/parsers.js +8 -4
- package/dist-lib/lib/helpers/buffer-burst.js +4 -2
- package/dist-lib/lib/helpers/control-tokens.js +4 -2
- package/dist-lib/lib/helpers/join-guard.d.ts +2 -2
- package/dist-lib/lib/helpers/join-guard.js +4 -2
- package/dist-lib/lib/helpers/load-stall.js +4 -2
- package/dist-lib/lib/helpers/markup-stream-filter.d.ts +2 -2
- package/dist-lib/lib/helpers/markup-stream-filter.js +6 -3
- package/dist-lib/lib/helpers/race-abort.js +2 -1
- package/dist-lib/lib/helpers/race-timeout.d.ts +5 -0
- package/dist-lib/lib/helpers/race-timeout.js +14 -0
- package/dist-lib/lib/helpers/run-outcome.d.ts +6 -8
- package/dist-lib/lib/helpers/run-outcome.js +36 -20
- package/dist-lib/lib/helpers/spawn-path.js +6 -3
- package/dist-lib/lib/helpers/text-coalescer.js +6 -3
- package/dist-lib/lib/helpers/thinking.js +38 -45
- package/dist-lib/lib/helpers/tool-call-markup.d.ts +9 -7
- package/dist-lib/lib/helpers/tool-call-markup.js +36 -20
- package/dist-lib/lib/helpers/tool-intent.d.ts +3 -2
- package/dist-lib/lib/helpers/tool-intent.js +4 -2
- package/dist-lib/lib/helpers/tool-label.js +18 -9
- package/dist-lib/lib/hooks/built-in/arg-repair.js +23 -13
- package/dist-lib/lib/hooks/built-in/egress.js +2 -1
- package/dist-lib/lib/hooks/built-in/skill-catalog.js +2 -1
- package/dist-lib/lib/hooks/pipeline.d.ts +5 -4
- package/dist-lib/lib/hooks/pipeline.js +6 -3
- package/dist-lib/lib/hooks/run.js +12 -8
- package/dist-lib/lib/model-download-progress.js +10 -8
- package/dist-lib/lib/models/context.d.ts +1 -1
- package/dist-lib/lib/models/context.js +5 -1
- package/dist-lib/lib/models/custom-model-cache.d.ts +10 -1
- package/dist-lib/lib/models/custom-model-cache.js +43 -15
- package/dist-lib/lib/models/gguf-header.d.ts +3 -2
- package/dist-lib/lib/models/gguf-header.js +44 -22
- package/dist-lib/lib/models/idle-unload.d.ts +14 -0
- package/dist-lib/lib/models/idle-unload.js +51 -0
- package/dist-lib/lib/models/kv-cache.d.ts +3 -2
- package/dist-lib/lib/models/kv-cache.js +4 -3
- package/dist-lib/lib/models/llm-presets.d.ts +2 -2
- package/dist-lib/lib/models/llm-presets.js +4 -2
- package/dist-lib/lib/models/model-catalog-constants.js +3 -2
- package/dist-lib/lib/models/model-catalog.d.ts +15 -20
- package/dist-lib/lib/models/model-catalog.js +66 -31
- package/dist-lib/lib/models/model-delete.js +18 -8
- package/dist-lib/lib/models/model-handle.js +2 -1
- package/dist-lib/lib/models/model-list.d.ts +8 -3
- package/dist-lib/lib/models/model-list.js +11 -6
- package/dist-lib/lib/models/model-pool.d.ts +48 -10
- package/dist-lib/lib/models/model-pool.js +106 -45
- package/dist-lib/lib/models/model-source.js +10 -5
- package/dist-lib/lib/models/music-catalog.d.ts +2 -2
- package/dist-lib/lib/models/nmt-catalog.d.ts +4 -3
- package/dist-lib/lib/models/nmt-catalog.js +10 -5
- package/dist-lib/lib/models/opencl-cache.d.ts +6 -1
- package/dist-lib/lib/models/opencl-cache.js +5 -3
- package/dist-lib/lib/models/sdk-faults.d.ts +11 -8
- package/dist-lib/lib/models/sdk-faults.js +9 -6
- package/dist-lib/lib/models/shards.d.ts +11 -0
- package/dist-lib/lib/models/shards.js +15 -0
- package/dist-lib/lib/models/tool-dialect.js +10 -5
- package/dist-lib/lib/no-redirect-fetch-bare.d.ts +3 -3
- package/dist-lib/lib/no-redirect-fetch-bare.js +1 -1
- package/dist-lib/lib/no-redirect-fetch-impl.js +6 -3
- package/dist-lib/lib/no-redirect-fetch-node.d.ts +1 -1
- package/dist-lib/lib/no-redirect-fetch-node.js +9 -9
- package/dist-lib/lib/office/index.d.ts +3 -2
- package/dist-lib/lib/office/python.d.ts +3 -2
- package/dist-lib/lib/office/python.js +2 -1
- package/dist-lib/lib/prompt.d.ts +5 -4
- package/dist-lib/lib/prompt.js +8 -4
- package/dist-lib/lib/queue.js +10 -5
- package/dist-lib/lib/resolution-detector.js +21 -11
- package/dist-lib/lib/rpc-session.js +2 -1
- package/dist-lib/lib/sdk-loader.js +2 -1
- package/dist-lib/lib/sdk-storage.js +4 -2
- package/dist-lib/lib/serve.js +122 -66
- package/dist-lib/lib/spawn.js +7 -4
- package/dist-lib/lib/tmp-path.d.ts +7 -6
- package/dist-lib/lib/tool-call-display.d.ts +2 -2
- package/dist-lib/lib/tools/create-documents.js +18 -10
- package/dist-lib/lib/tools/edit-documents.js +23 -13
- package/dist-lib/lib/tools/edit-image.d.ts +2 -2
- package/dist-lib/lib/tools/edit-image.js +60 -36
- package/dist-lib/lib/tools/exec/adapters/adapter.d.ts +10 -10
- package/dist-lib/lib/tools/exec/adapters/python/attachments.d.ts +17 -11
- package/dist-lib/lib/tools/exec/adapters/python/attachments.js +53 -40
- package/dist-lib/lib/tools/exec/adapters/python/create-effect.d.ts +2 -2
- package/dist-lib/lib/tools/exec/adapters/python/create-effect.js +60 -32
- package/dist-lib/lib/tools/exec/adapters/python/guarded-fetch.d.ts +33 -22
- package/dist-lib/lib/tools/exec/adapters/python/guarded-fetch.js +58 -29
- package/dist-lib/lib/tools/exec/adapters/python/interrupt-timer.d.ts +2 -2
- package/dist-lib/lib/tools/exec/adapters/python/loader.js +23 -12
- package/dist-lib/lib/tools/exec/adapters/python/memfs.d.ts +13 -11
- package/dist-lib/lib/tools/exec/adapters/python/memfs.js +10 -5
- package/dist-lib/lib/tools/exec/adapters/python/net-shim.js +3 -1
- package/dist-lib/lib/tools/exec/adapters/python/output-policy.d.ts +6 -4
- package/dist-lib/lib/tools/exec/adapters/python/output-policy.js +20 -10
- package/dist-lib/lib/tools/exec/adapters/python/pyodide.d.ts +2 -2
- package/dist-lib/lib/tools/exec/adapters/python/pyodide.js +14 -7
- package/dist-lib/lib/tools/exec/adapters/python/python-adapter.d.ts +41 -34
- package/dist-lib/lib/tools/exec/adapters/python/python-adapter.js +316 -166
- package/dist-lib/lib/tools/exec/adapters/python/run-session.d.ts +9 -8
- package/dist-lib/lib/tools/exec/adapters/python/run-session.js +56 -28
- package/dist-lib/lib/tools/exec/adapters/python/wheels.d.ts +8 -10
- package/dist-lib/lib/tools/exec/adapters/python/wheels.js +22 -11
- package/dist-lib/lib/tools/exec/adapters/python/worker/client.d.ts +3 -2
- package/dist-lib/lib/tools/exec/adapters/python/worker/client.js +41 -26
- package/dist-lib/lib/tools/exec/adapters/python/worker/entry.js +27 -18
- package/dist-lib/lib/tools/exec/adapters/python/worker/protocol.d.ts +2 -2
- package/dist-lib/lib/tools/exec/adapters/python/worker/protocol.js +14 -13
- package/dist-lib/lib/tools/exec/adapters/python/worker/spawn-node.d.ts +4 -4
- package/dist-lib/lib/tools/exec/adapters/python/worker/spawn-node.js +10 -5
- package/dist-lib/lib/tools/exec/adapters/shell/shell-adapter.d.ts +4 -4
- package/dist-lib/lib/tools/exec/adapters/shell/shell-adapter.js +2 -1
- package/dist-lib/lib/tools/exec/obsidian/cli-process.d.ts +6 -1
- package/dist-lib/lib/tools/exec/obsidian/cli-process.js +8 -5
- package/dist-lib/lib/tools/exec/obsidian/cli.d.ts +6 -6
- package/dist-lib/lib/tools/exec/obsidian/cli.js +4 -4
- package/dist-lib/lib/tools/exec/obsidian/command.d.ts +3 -2
- package/dist-lib/lib/tools/exec/obsidian/command.js +6 -3
- package/dist-lib/lib/tools/exec/obsidian/output.js +2 -1
- package/dist-lib/lib/tools/exec/obsidian/route.d.ts +4 -6
- package/dist-lib/lib/tools/exec/obsidian/route.js +17 -11
- package/dist-lib/lib/tools/exec/obsidian/safe-path.js +2 -1
- package/dist-lib/lib/tools/exec/obsidian/vault-create.js +15 -7
- package/dist-lib/lib/tools/exec/obsidian/vault-paths.d.ts +2 -2
- package/dist-lib/lib/tools/exec/obsidian/vault-paths.js +20 -10
- package/dist-lib/lib/tools/exec/obsidian/vault-read.js +20 -13
- package/dist-lib/lib/tools/exec/obsidian/vault-write.js +24 -12
- package/dist-lib/lib/tools/exec/runner.d.ts +14 -14
- package/dist-lib/lib/tools/exec/runner.js +5 -3
- package/dist-lib/lib/tools/exec/scope.js +52 -28
- package/dist-lib/lib/tools/exec/shell-argv.js +31 -18
- package/dist-lib/lib/tools/exec/shell-guidance.d.ts +6 -1
- package/dist-lib/lib/tools/exec/shell-guidance.js +35 -20
- package/dist-lib/lib/tools/exec/video-tools.js +2 -2
- package/dist-lib/lib/tools/exec.d.ts +6 -8
- package/dist-lib/lib/tools/exec.js +113 -54
- package/dist-lib/lib/tools/file-glob.d.ts +4 -5
- package/dist-lib/lib/tools/file-glob.js +16 -11
- package/dist-lib/lib/tools/fs/glob.js +13 -18
- package/dist-lib/lib/tools/fs/remote-protocol.d.ts +10 -10
- package/dist-lib/lib/tools/fs/remote.d.ts +3 -2
- package/dist-lib/lib/tools/fs/remote.js +13 -7
- package/dist-lib/lib/tools/fs/scope.d.ts +8 -3
- package/dist-lib/lib/tools/fs/scope.js +20 -12
- package/dist-lib/lib/tools/fs/walk.js +6 -3
- package/dist-lib/lib/tools/fs/write.d.ts +3 -2
- package/dist-lib/lib/tools/generate-image.d.ts +11 -9
- package/dist-lib/lib/tools/generate-image.js +73 -37
- package/dist-lib/lib/tools/generate-music.d.ts +22 -11
- package/dist-lib/lib/tools/generate-music.js +79 -48
- package/dist-lib/lib/tools/grep.js +66 -41
- package/dist-lib/lib/tools/http-request/encode-fields.js +18 -9
- package/dist-lib/lib/tools/http-request/{utils.js → request.js} +20 -11
- package/dist-lib/lib/tools/http-request/truncate-response.js +57 -46
- package/dist-lib/lib/tools/http-request.d.ts +1 -1
- package/dist-lib/lib/tools/http-request.js +33 -18
- package/dist-lib/lib/tools/mcp/protocol.d.ts +53 -0
- package/dist-lib/lib/tools/mcp/{utils.js → protocol.js} +45 -23
- package/dist-lib/lib/tools/mcp-call.d.ts +1 -1
- package/dist-lib/lib/tools/mcp-call.js +37 -16
- package/dist-lib/lib/tools/operations/builders.js +10 -6
- package/dist-lib/lib/tools/operations/compile.js +20 -10
- package/dist-lib/lib/tools/operations/decoders.js +12 -6
- package/dist-lib/lib/tools/operations/gmail-message.js +3 -2
- package/dist-lib/lib/tools/outbound-guard.d.ts +4 -3
- package/dist-lib/lib/tools/outbound-guard.js +14 -8
- package/dist-lib/lib/tools/private-ip.js +8 -7
- package/dist-lib/lib/tools/rag-search.js +14 -7
- package/dist-lib/lib/tools/read-files.js +23 -20
- package/dist-lib/lib/tools/skill.js +19 -10
- package/dist-lib/lib/tools/skills/auth-resolver.d.ts +11 -18
- package/dist-lib/lib/tools/skills/auth-resolver.js +30 -18
- package/dist-lib/lib/tools/skills/bin-check.d.ts +3 -2
- package/dist-lib/lib/tools/skills/bin-check.js +10 -3
- package/dist-lib/lib/tools/skills/bundled-dir.js +20 -10
- package/dist-lib/lib/tools/skills/bundled-hash.d.ts +3 -2
- package/dist-lib/lib/tools/skills/bundled-hash.js +6 -1
- package/dist-lib/lib/tools/skills/catalog.d.ts +3 -2
- package/dist-lib/lib/tools/skills/catalog.js +33 -28
- package/dist-lib/lib/tools/skills/cli-argv.d.ts +3 -2
- package/dist-lib/lib/tools/skills/cli-argv.js +55 -52
- package/dist-lib/lib/tools/skills/cli-schema-check.js +21 -10
- package/dist-lib/lib/tools/skills/cli-schema-load.js +28 -20
- package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +6 -6
- package/dist-lib/lib/tools/skills/cli-schema.js +2 -1
- package/dist-lib/lib/tools/skills/credential-check.d.ts +6 -3
- package/dist-lib/lib/tools/skills/credential-check.js +24 -12
- package/dist-lib/lib/tools/skills/credential-grants.js +1 -6
- package/dist-lib/lib/tools/skills/credential-health.d.ts +5 -1
- package/dist-lib/lib/tools/skills/credential-health.js +5 -3
- package/dist-lib/lib/tools/skills/frontmatter-parser.js +10 -5
- package/dist-lib/lib/tools/skills/manifest-parse.js +84 -64
- package/dist-lib/lib/tools/skills/mention.js +7 -6
- package/dist-lib/lib/tools/skills/obsidian-readiness.d.ts +2 -2
- package/dist-lib/lib/tools/skills/obsidian-readiness.js +11 -3
- package/dist-lib/lib/tools/skills/overlay-crud.d.ts +6 -4
- package/dist-lib/lib/tools/skills/overlay-crud.js +12 -6
- package/dist-lib/lib/tools/skills/overlay-dir.js +4 -2
- package/dist-lib/lib/tools/skills/overlay-watch.d.ts +3 -2
- package/dist-lib/lib/tools/skills/overlay-watch.js +8 -4
- package/dist-lib/lib/tools/skills/setup-routes.js +1 -3
- package/dist-lib/lib/tools/skills/skill-settings.js +4 -1
- package/dist-lib/lib/tools/skills/skill-state.d.ts +6 -11
- package/dist-lib/lib/tools/skills/skill-state.js +48 -29
- package/dist-lib/lib/tools/skills/tool-grants.js +6 -3
- package/dist-lib/lib/tools/toolsets.js +2 -1
- package/dist-lib/lib/tools/translate.d.ts +4 -5
- package/dist-lib/lib/tools/translate.js +31 -14
- package/dist-lib/lib/tools/url-guard.js +2 -1
- package/dist-lib/lib/tools/weather-lookup/place.js +8 -4
- package/dist-lib/lib/tools/weather-lookup/report.d.ts +7 -1
- package/dist-lib/lib/tools/weather-lookup/report.js +46 -9
- package/dist-lib/lib/tools/weather-lookup.js +18 -12
- package/dist-lib/lib/tools/web-fetch.js +47 -24
- package/dist-lib/lib/tools/web-search.js +8 -3
- package/dist-lib/lib/validating-lookup.d.ts +4 -4
- package/dist-lib/lib/wav-encode.d.ts +1 -1
- package/dist-lib/lib/wav-encode.js +11 -10
- package/dist-lib/lib/web-fetch/embedded-state.js +24 -12
- package/dist-lib/lib/web-fetch/extract.js +4 -2
- package/dist-lib/lib/web-fetch/images.js +8 -4
- package/dist-lib/lib/web-fetch/video.d.ts +2 -2
- package/dist-lib/lib/web-fetch/video.js +8 -4
- package/dist-lib/lib/web-search/engine-registry.d.ts +2 -2
- package/dist-lib/lib/web-search/engines/aol.js +1 -1
- package/dist-lib/lib/web-search/engines/bing.js +1 -1
- package/dist-lib/lib/web-search/engines/brave.js +4 -1
- package/dist-lib/lib/web-search/engines/duckduckgo.js +3 -2
- package/dist-lib/lib/web-search/engines/google.js +7 -3
- package/dist-lib/lib/web-search/engines/wikipedia.js +5 -4
- package/dist-lib/lib/web-search/engines/yahoo.js +1 -1
- package/dist-lib/lib/web-search/fetch-html.d.ts +5 -4
- package/dist-lib/lib/web-search/fetch-html.js +13 -7
- package/dist-lib/lib/web-search/orchestrator.d.ts +9 -5
- package/dist-lib/lib/web-search/orchestrator.js +3 -3
- package/dist-lib/lib/wire.d.ts +3 -2
- package/dist-lib/lib/wire.js +23 -15
- package/dist-lib/schema/baseline.js +10 -5
- package/dist-lib/schema/build.js +2 -1
- package/dist-lib/schema/harness.d.ts +15 -2
- package/dist-lib/schema/harness.js +39 -2
- package/dist-lib/schema/release-tags.d.ts +3 -2
- package/dist-lib/schema/release-tags.js +6 -3
- package/dist-lib/spec/bind.js +9 -0
- package/dist-lib/spec/calls.js +3 -0
- package/dist-lib/spec/hrpc/hrpc.json +39 -0
- package/dist-lib/spec/hrpc/index.js +39 -3
- package/dist-lib/spec/hrpc/messages.js +105 -8
- package/dist-lib/spec/hyperschema/index.js +105 -8
- package/dist-lib/spec/hyperschema/schema.json +77 -1
- package/dist-lib/spec/hyperschema/types.d.ts +25 -1
- package/package.json +14 -14
- package/worker/harness-sidecar.mjs +15 -22
- package/worker/stow-entry.mjs +7 -4
- package/dist-lib/lib/tools/mcp/utils.d.ts +0 -47
- /package/dist-lib/lib/tools/http-request/{utils.d.ts → request.d.ts} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { MAX_RESPONSE_CHARS } from './
|
|
1
|
+
import { MAX_RESPONSE_CHARS } from './request.js';
|
|
2
|
+
import { isRecord } from '../../guards.js';
|
|
2
3
|
export function truncateResponseBody(body) {
|
|
3
4
|
if (body.length <= MAX_RESPONSE_CHARS)
|
|
4
5
|
return body;
|
|
@@ -18,63 +19,73 @@ const MARKER_CHARS = 64;
|
|
|
18
19
|
function shrinkable(value) {
|
|
19
20
|
if (typeof value === 'string')
|
|
20
21
|
return true;
|
|
21
|
-
return
|
|
22
|
+
return isRecord(value);
|
|
22
23
|
}
|
|
23
24
|
export const ELISION = '… [truncated]';
|
|
24
25
|
function fitString(value, budget) {
|
|
25
26
|
const room = budget - ELISION.length - 2;
|
|
26
|
-
if (room <= 0)
|
|
27
|
+
if (room <= 0) {
|
|
27
28
|
return ELISION;
|
|
29
|
+
}
|
|
28
30
|
return `${value.slice(0, room)}${ELISION}`;
|
|
29
31
|
}
|
|
30
|
-
function
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
if (left > MARKER_CHARS && shrinkable(item))
|
|
40
|
-
kept.push(fit(item, left));
|
|
41
|
-
break;
|
|
32
|
+
function fitArray(value, room) {
|
|
33
|
+
const kept = [];
|
|
34
|
+
let used = 2;
|
|
35
|
+
for (const item of value) {
|
|
36
|
+
const size = JSON.stringify(item).length + 1;
|
|
37
|
+
if (used + size > room) {
|
|
38
|
+
const left = room - used;
|
|
39
|
+
if (left > MARKER_CHARS && shrinkable(item)) {
|
|
40
|
+
kept.push(fit(item, left));
|
|
42
41
|
}
|
|
43
|
-
|
|
44
|
-
used += size;
|
|
42
|
+
break;
|
|
45
43
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
kept.push(`… [${dropped} more items truncated]`);
|
|
49
|
-
return kept;
|
|
44
|
+
kept.push(item);
|
|
45
|
+
used += size;
|
|
50
46
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
47
|
+
const dropped = value.length - kept.length;
|
|
48
|
+
if (dropped > 0) {
|
|
49
|
+
kept.push(`… [${dropped} more items truncated]`);
|
|
50
|
+
}
|
|
51
|
+
return kept;
|
|
52
|
+
}
|
|
53
|
+
function fitObject(value, room) {
|
|
54
|
+
const entries = Object.entries(value);
|
|
55
|
+
const out = {};
|
|
56
|
+
let used = 2;
|
|
57
|
+
let kept = 0;
|
|
58
|
+
let refitted = false;
|
|
59
|
+
for (const [key, item] of entries) {
|
|
60
|
+
const keySize = JSON.stringify(key).length + 2;
|
|
61
|
+
const size = keySize + JSON.stringify(item).length;
|
|
62
|
+
if (used + size > room) {
|
|
63
|
+
const left = room - used - keySize;
|
|
64
|
+
if (!refitted && left > MARKER_CHARS && shrinkable(item)) {
|
|
65
|
+
const shrunk = fit(item, left);
|
|
66
|
+
out[key] = shrunk;
|
|
67
|
+
used += keySize + JSON.stringify(shrunk).length;
|
|
68
|
+
refitted = true;
|
|
69
|
+
kept++;
|
|
70
70
|
}
|
|
71
|
-
|
|
72
|
-
used += size;
|
|
73
|
-
kept++;
|
|
71
|
+
continue;
|
|
74
72
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
out[key] = item;
|
|
74
|
+
used += size;
|
|
75
|
+
kept++;
|
|
76
|
+
}
|
|
77
|
+
if (kept < entries.length) {
|
|
78
|
+
out['…'] = `[${entries.length - kept} more fields truncated]`;
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
function fit(value, budget) {
|
|
83
|
+
const room = budget - MARKER_CHARS;
|
|
84
|
+
if (Array.isArray(value)) {
|
|
85
|
+
return fitArray(value, room);
|
|
86
|
+
}
|
|
87
|
+
if (isRecord(value)) {
|
|
88
|
+
return fitObject(value, room);
|
|
78
89
|
}
|
|
79
90
|
if (typeof value === 'string' && JSON.stringify(value).length > budget) {
|
|
80
91
|
return fitString(value, budget);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ToolDefinition } from '../capabilities/completion.ts';
|
|
2
|
-
import { type FetchInit } from './http-request/
|
|
2
|
+
import { type FetchInit } from './http-request/request.ts';
|
|
3
3
|
import { type CredentialAllowListFn, type CredentialStoreFn } from './skills/auth-resolver.ts';
|
|
4
4
|
import { type HostResolver } from './url-guard.ts';
|
|
5
5
|
export type FetchFn = (url: URL, init: FetchInit) => Promise<{
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { noRedirectFetch } from '#no-redirect-fetch';
|
|
2
2
|
import { AbortController } from 'bare-abort-controller';
|
|
3
3
|
import { lookupHost } from '../dns-lookup.js';
|
|
4
4
|
import { READ_METHODS } from './http-methods.js';
|
|
5
5
|
import { encodeDeclaredFields } from './http-request/encode-fields.js';
|
|
6
6
|
import { truncateResponseBody } from './http-request/truncate-response.js';
|
|
7
7
|
import { AbortedError } from '../helpers/race-abort.js';
|
|
8
|
-
import { applyQuery, buildHeaders, contentTypeOf, encodeBody, isRedirectStatus, parseJsonBody, parseObjectArg, TIMEOUT_MS } from './http-request/
|
|
8
|
+
import { applyQuery, buildHeaders, contentTypeOf, encodeBody, isRedirectStatus, parseJsonBody, parseObjectArg, TIMEOUT_MS } from './http-request/request.js';
|
|
9
9
|
import { parseAuth, resolveBearerHeader, resolveRedirectBearerHeader } from './skills/auth-resolver.js';
|
|
10
10
|
import { recordCredentialHealth } from './skills/credential-health.js';
|
|
11
11
|
import { resolveOutboundUrl } from './url-guard.js';
|
|
@@ -47,8 +47,9 @@ export function createHttpRequestTool({ fetch: fetchImpl, credentialStore, crede
|
|
|
47
47
|
autoApprove: false,
|
|
48
48
|
effect(args) {
|
|
49
49
|
const method = typeof args.method === 'string' ? args.method.toUpperCase() : undefined;
|
|
50
|
-
if (method === undefined || !READ_METHODS.has(method))
|
|
50
|
+
if (method === undefined || !READ_METHODS.has(method)) {
|
|
51
51
|
return 'write';
|
|
52
|
+
}
|
|
52
53
|
return args.body === undefined && args.encode === undefined ? 'read' : 'write';
|
|
53
54
|
},
|
|
54
55
|
resource(args) {
|
|
@@ -72,39 +73,47 @@ export function createHttpRequestTool({ fetch: fetchImpl, credentialStore, crede
|
|
|
72
73
|
return { error: `unsupported protocol: ${url.protocol}` };
|
|
73
74
|
}
|
|
74
75
|
const query = parseObjectArg(args.query, 'query');
|
|
75
|
-
if (!query.ok)
|
|
76
|
+
if (!query.ok) {
|
|
76
77
|
return { error: query.error };
|
|
78
|
+
}
|
|
77
79
|
const encodeArgs = parseObjectArg(args.encode, 'encode');
|
|
78
|
-
if (!encodeArgs.ok)
|
|
80
|
+
if (!encodeArgs.ok) {
|
|
79
81
|
return { error: encodeArgs.error };
|
|
82
|
+
}
|
|
80
83
|
const headerArgs = parseObjectArg(args.headers, 'headers');
|
|
81
|
-
if (!headerArgs.ok)
|
|
84
|
+
if (!headerArgs.ok) {
|
|
82
85
|
return { error: headerArgs.error };
|
|
86
|
+
}
|
|
83
87
|
const headers = headerArgs.value ? buildHeaders(headerArgs.value) : {};
|
|
84
88
|
const parsedBody = parseJsonBody(args.body, contentTypeOf(headers));
|
|
85
|
-
if (!parsedBody.ok)
|
|
89
|
+
if (!parsedBody.ok) {
|
|
86
90
|
return { error: parsedBody.error };
|
|
87
|
-
|
|
91
|
+
}
|
|
92
|
+
if (query.value) {
|
|
88
93
|
applyQuery(url, query.value);
|
|
94
|
+
}
|
|
89
95
|
const auth = parseAuth(args.auth);
|
|
90
|
-
if (!auth.ok)
|
|
96
|
+
if (!auth.ok) {
|
|
91
97
|
return { error: auth.error };
|
|
98
|
+
}
|
|
92
99
|
const allowList = credentialAllowList?.(ctx);
|
|
93
100
|
const controller = new AbortController();
|
|
94
101
|
const timeout = setTimeout(() => controller.abort(undefined), TIMEOUT_MS);
|
|
95
102
|
try {
|
|
96
103
|
const initialCheck = await resolveOutboundUrl(url, resolveHost, controller.signal);
|
|
97
|
-
if (initialCheck.blocked)
|
|
104
|
+
if (initialCheck.blocked) {
|
|
98
105
|
return { error: initialCheck.blocked };
|
|
106
|
+
}
|
|
99
107
|
const bearer = await resolveBearerHeader(auth.value, url, credentialStore, allowList, ctx, controller.signal);
|
|
100
|
-
if ('error' in bearer)
|
|
108
|
+
if ('error' in bearer) {
|
|
101
109
|
return { error: bearer.error };
|
|
110
|
+
}
|
|
102
111
|
const redirectAuth = bearer.credentialKey
|
|
103
112
|
? { type: 'bearer', tokenCredentialKey: bearer.credentialKey }
|
|
104
113
|
: undefined;
|
|
105
114
|
const encoded = encodeDeclaredFields(parsedBody.value, encodeArgs.value);
|
|
106
115
|
const requestBody = encodeBody(encoded, headers);
|
|
107
|
-
const fetch = fetchImpl ??
|
|
116
|
+
const fetch = fetchImpl ?? noRedirectFetch;
|
|
108
117
|
let currentUrl = url;
|
|
109
118
|
let method = args.method ?? 'GET';
|
|
110
119
|
let body = requestBody;
|
|
@@ -115,8 +124,9 @@ export function createHttpRequestTool({ fetch: fetchImpl, credentialStore, crede
|
|
|
115
124
|
for (let redirects = 0;; redirects++) {
|
|
116
125
|
const hopHeaders = { ...headers };
|
|
117
126
|
delete hopHeaders['Authorization'];
|
|
118
|
-
if (header)
|
|
127
|
+
if (header) {
|
|
119
128
|
hopHeaders['Authorization'] = header;
|
|
129
|
+
}
|
|
120
130
|
response = await fetch(currentUrl, {
|
|
121
131
|
method,
|
|
122
132
|
headers: hopHeaders,
|
|
@@ -128,11 +138,13 @@ export function createHttpRequestTool({ fetch: fetchImpl, credentialStore, crede
|
|
|
128
138
|
if (credentialKey) {
|
|
129
139
|
recordCredentialHealth(credentialKey, response.status, ctx?.agentId);
|
|
130
140
|
}
|
|
131
|
-
if (!isRedirectStatus(response.status))
|
|
141
|
+
if (!isRedirectStatus(response.status)) {
|
|
132
142
|
break;
|
|
143
|
+
}
|
|
133
144
|
const location = response.headers?.get('location');
|
|
134
|
-
if (!location)
|
|
145
|
+
if (!location) {
|
|
135
146
|
break;
|
|
147
|
+
}
|
|
136
148
|
let nextUrl;
|
|
137
149
|
try {
|
|
138
150
|
nextUrl = new URL(location, currentUrl);
|
|
@@ -140,13 +152,16 @@ export function createHttpRequestTool({ fetch: fetchImpl, credentialStore, crede
|
|
|
140
152
|
catch {
|
|
141
153
|
break;
|
|
142
154
|
}
|
|
143
|
-
if (nextUrl.protocol !== 'http:' && nextUrl.protocol !== 'https:')
|
|
155
|
+
if (nextUrl.protocol !== 'http:' && nextUrl.protocol !== 'https:') {
|
|
144
156
|
break;
|
|
145
|
-
|
|
157
|
+
}
|
|
158
|
+
if (redirects >= MAX_REDIRECTS) {
|
|
146
159
|
return { error: 'too many redirects' };
|
|
160
|
+
}
|
|
147
161
|
const hopCheck = await resolveOutboundUrl(nextUrl, resolveHost, controller.signal);
|
|
148
|
-
if (hopCheck.blocked)
|
|
162
|
+
if (hopCheck.blocked) {
|
|
149
163
|
return { error: hopCheck.blocked };
|
|
164
|
+
}
|
|
150
165
|
if (response.status === 303 ||
|
|
151
166
|
((response.status === 301 || response.status === 302) && method === 'POST')) {
|
|
152
167
|
method = 'GET';
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { AbortSignal as BareAbortSignal } from 'bare-abort-controller';
|
|
2
|
+
import type { ToolExecuteContext } from '../../capabilities/completion.ts';
|
|
3
|
+
import { type FetchInit } from '../http-request/request.ts';
|
|
4
|
+
export declare const MCP_PROTOCOL_VERSION = "2025-03-26";
|
|
5
|
+
export declare const TIMEOUT_MS = 30000;
|
|
6
|
+
export interface McpHeaders {
|
|
7
|
+
get(name: string): string | null;
|
|
8
|
+
}
|
|
9
|
+
export interface McpResponseBody {
|
|
10
|
+
headers: McpHeaders;
|
|
11
|
+
text(): Promise<string> | string;
|
|
12
|
+
}
|
|
13
|
+
export interface McpResponse extends McpResponseBody {
|
|
14
|
+
status: number;
|
|
15
|
+
statusText?: string;
|
|
16
|
+
}
|
|
17
|
+
export type McpFetchFn = (url: URL, init: FetchInit) => Promise<McpResponse>;
|
|
18
|
+
export declare function isNotification(method: string): boolean;
|
|
19
|
+
export declare function isMetadataMethod(method: unknown): boolean;
|
|
20
|
+
export interface McpRequest {
|
|
21
|
+
method: string;
|
|
22
|
+
params: Record<string, unknown> | undefined;
|
|
23
|
+
}
|
|
24
|
+
export declare function normalizeMethod(method: string, params: Record<string, unknown> | undefined): McpRequest;
|
|
25
|
+
export interface McpError {
|
|
26
|
+
error: string;
|
|
27
|
+
}
|
|
28
|
+
export interface McpBody {
|
|
29
|
+
body: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function flattenMcpResponse(body: string): McpError | McpBody;
|
|
32
|
+
export declare function sessionKey(url: string, ctx: ToolExecuteContext | undefined, credentialKey?: string): string;
|
|
33
|
+
export declare function getSession(key: string): string;
|
|
34
|
+
export declare function deleteSession(key: string): void;
|
|
35
|
+
export declare function cacheSession(key: string, sessionId: string): void;
|
|
36
|
+
export interface McpPostOptions {
|
|
37
|
+
readonly method: string;
|
|
38
|
+
readonly params: Record<string, unknown> | undefined;
|
|
39
|
+
readonly authHeader: string | null;
|
|
40
|
+
readonly sessionId: string | undefined;
|
|
41
|
+
readonly signal: BareAbortSignal;
|
|
42
|
+
}
|
|
43
|
+
export declare function post(fetch: McpFetchFn, url: URL, { method, params, authHeader, sessionId, signal }: McpPostOptions): ReturnType<McpFetchFn>;
|
|
44
|
+
export declare function readBody(response: McpResponseBody): Promise<string>;
|
|
45
|
+
export declare function needsSession(status: number, cached: string): boolean;
|
|
46
|
+
export interface McpSessionOptions {
|
|
47
|
+
readonly authHeader: string | null;
|
|
48
|
+
readonly signal: BareAbortSignal;
|
|
49
|
+
}
|
|
50
|
+
export interface McpSessionError extends McpError {
|
|
51
|
+
stateless?: true;
|
|
52
|
+
}
|
|
53
|
+
export declare function establishSession(fetch: McpFetchFn, url: URL, { authHeader, signal }: McpSessionOptions): Promise<string | McpSessionError>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isRecord } from '../../guards.js';
|
|
2
|
+
import { MAX_RESPONSE_CHARS } from '../http-request/request.js';
|
|
2
3
|
export const MCP_PROTOCOL_VERSION = '2025-03-26';
|
|
3
4
|
export const TIMEOUT_MS = 30_000;
|
|
4
5
|
const METADATA_METHODS = new Set([
|
|
@@ -24,17 +25,20 @@ export function normalizeMethod(method, params) {
|
|
|
24
25
|
const p = params ?? {};
|
|
25
26
|
return { method, params: p.arguments === undefined ? { ...p, arguments: {} } : p };
|
|
26
27
|
}
|
|
27
|
-
if (PROTOCOL_METHODS.has(method) || isNotification(method))
|
|
28
|
+
if (PROTOCOL_METHODS.has(method) || isNotification(method)) {
|
|
28
29
|
return { method, params };
|
|
30
|
+
}
|
|
29
31
|
return { method: 'tools/call', params: { name: method, arguments: params ?? {} } };
|
|
30
32
|
}
|
|
31
33
|
let nextId = 1;
|
|
32
34
|
function buildEnvelope(method, params) {
|
|
33
35
|
const envelope = { jsonrpc: '2.0', method };
|
|
34
|
-
if (params !== undefined)
|
|
36
|
+
if (params !== undefined) {
|
|
35
37
|
envelope.params = params;
|
|
36
|
-
|
|
38
|
+
}
|
|
39
|
+
if (!isNotification(method)) {
|
|
37
40
|
envelope.id = String(nextId++);
|
|
41
|
+
}
|
|
38
42
|
return JSON.stringify(envelope);
|
|
39
43
|
}
|
|
40
44
|
function headers(authHeader, sessionId) {
|
|
@@ -42,10 +46,12 @@ function headers(authHeader, sessionId) {
|
|
|
42
46
|
'content-type': 'application/json',
|
|
43
47
|
accept: 'application/json, text/event-stream'
|
|
44
48
|
};
|
|
45
|
-
if (authHeader)
|
|
49
|
+
if (authHeader) {
|
|
46
50
|
h.authorization = authHeader;
|
|
47
|
-
|
|
51
|
+
}
|
|
52
|
+
if (sessionId) {
|
|
48
53
|
h['mcp-session-id'] = sessionId;
|
|
54
|
+
}
|
|
49
55
|
return h;
|
|
50
56
|
}
|
|
51
57
|
function eventData(event) {
|
|
@@ -75,14 +81,13 @@ function decodePayload(body, contentType) {
|
|
|
75
81
|
}
|
|
76
82
|
}
|
|
77
83
|
function asRecord(value) {
|
|
78
|
-
return value &&
|
|
79
|
-
? value
|
|
80
|
-
: null;
|
|
84
|
+
return isRecord(value) && !Array.isArray(value) ? value : null;
|
|
81
85
|
}
|
|
82
86
|
function contentText(result) {
|
|
83
87
|
const content = result?.content;
|
|
84
|
-
if (!Array.isArray(content))
|
|
88
|
+
if (!Array.isArray(content)) {
|
|
85
89
|
return null;
|
|
90
|
+
}
|
|
86
91
|
const texts = content.flatMap((entry) => {
|
|
87
92
|
const item = asRecord(entry);
|
|
88
93
|
return item?.type === 'text' && typeof item.text === 'string' ? [item.text] : [];
|
|
@@ -91,8 +96,9 @@ function contentText(result) {
|
|
|
91
96
|
}
|
|
92
97
|
function rpcErrorText(error) {
|
|
93
98
|
const message = typeof error.message === 'string' ? error.message.trim() : '';
|
|
94
|
-
if (error.data === undefined)
|
|
99
|
+
if (error.data === undefined) {
|
|
95
100
|
return message;
|
|
101
|
+
}
|
|
96
102
|
return `${message} ${JSON.stringify(error.data)}`.trim();
|
|
97
103
|
}
|
|
98
104
|
export function flattenMcpResponse(body) {
|
|
@@ -104,15 +110,18 @@ export function flattenMcpResponse(body) {
|
|
|
104
110
|
return { body: truncate(body) };
|
|
105
111
|
}
|
|
106
112
|
const record = asRecord(parsed);
|
|
107
|
-
if (!record)
|
|
113
|
+
if (!record) {
|
|
108
114
|
return { body: truncate(body) };
|
|
115
|
+
}
|
|
109
116
|
const rpcError = asRecord(record.error);
|
|
110
|
-
if (rpcError)
|
|
117
|
+
if (rpcError) {
|
|
111
118
|
return { error: truncate(rpcErrorText(rpcError) || body.trim()) };
|
|
119
|
+
}
|
|
112
120
|
const result = asRecord(record.result);
|
|
113
121
|
const text = contentText(result)?.trim();
|
|
114
|
-
if (result?.isError === true)
|
|
122
|
+
if (result?.isError === true) {
|
|
115
123
|
return { error: truncate(text || body.trim()) };
|
|
124
|
+
}
|
|
116
125
|
return { body: truncate(text || body) };
|
|
117
126
|
}
|
|
118
127
|
function truncate(text) {
|
|
@@ -135,11 +144,12 @@ export function cacheSession(key, sessionId) {
|
|
|
135
144
|
SESSION_CACHE.set(key, sessionId);
|
|
136
145
|
if (SESSION_CACHE.size > SESSION_CACHE_MAX) {
|
|
137
146
|
const oldest = SESSION_CACHE.keys().next().value;
|
|
138
|
-
if (oldest !== undefined)
|
|
147
|
+
if (oldest !== undefined) {
|
|
139
148
|
SESSION_CACHE.delete(oldest);
|
|
149
|
+
}
|
|
140
150
|
}
|
|
141
151
|
}
|
|
142
|
-
export function post(fetch, url, method, params, authHeader, sessionId, signal) {
|
|
152
|
+
export function post(fetch, url, { method, params, authHeader, sessionId, signal }) {
|
|
143
153
|
return fetch(url, {
|
|
144
154
|
method: 'POST',
|
|
145
155
|
headers: headers(authHeader, sessionId),
|
|
@@ -154,12 +164,18 @@ export async function readBody(response) {
|
|
|
154
164
|
export function needsSession(status, cached) {
|
|
155
165
|
return status === 400 || (status === 404 && cached !== '');
|
|
156
166
|
}
|
|
157
|
-
export async function establishSession(fetch, url, authHeader, signal) {
|
|
158
|
-
const init = await post(fetch, url,
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
167
|
+
export async function establishSession(fetch, url, { authHeader, signal }) {
|
|
168
|
+
const init = await post(fetch, url, {
|
|
169
|
+
method: 'initialize',
|
|
170
|
+
params: {
|
|
171
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
172
|
+
capabilities: {},
|
|
173
|
+
clientInfo: { name: 'QV.AC', version: '0.8.x' }
|
|
174
|
+
},
|
|
175
|
+
authHeader,
|
|
176
|
+
sessionId: undefined,
|
|
177
|
+
signal
|
|
178
|
+
});
|
|
163
179
|
const sessionId = init.headers.get('mcp-session-id');
|
|
164
180
|
if (init.status < 200 || init.status >= 300) {
|
|
165
181
|
return { error: `initialize failed: ${init.status}\n${await readBody(init)}` };
|
|
@@ -167,6 +183,12 @@ export async function establishSession(fetch, url, authHeader, signal) {
|
|
|
167
183
|
if (!sessionId) {
|
|
168
184
|
return { error: 'initialize returned no Mcp-Session-Id header', stateless: true };
|
|
169
185
|
}
|
|
170
|
-
await post(fetch, url,
|
|
186
|
+
await post(fetch, url, {
|
|
187
|
+
method: 'notifications/initialized',
|
|
188
|
+
params: undefined,
|
|
189
|
+
authHeader,
|
|
190
|
+
sessionId,
|
|
191
|
+
signal
|
|
192
|
+
});
|
|
171
193
|
return sessionId;
|
|
172
194
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ToolDefinition } from '../capabilities/completion.ts';
|
|
2
|
-
import { type McpFetchFn } from './mcp/
|
|
2
|
+
import { type McpFetchFn } from './mcp/protocol.ts';
|
|
3
3
|
import { type CredentialAllowListFn, type CredentialStoreFn } from './skills/auth-resolver.ts';
|
|
4
4
|
import { type HostResolver } from './url-guard.ts';
|
|
5
5
|
export type { McpFetchFn };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { noRedirectFetch } from '#no-redirect-fetch';
|
|
2
2
|
import { AbortController } from 'bare-abort-controller';
|
|
3
3
|
import { MCP_CALL_TOOL_NAME } from '../capabilities/tool-names.js';
|
|
4
4
|
import { lookupHost } from '../dns-lookup.js';
|
|
5
5
|
import { AbortedError } from '../helpers/race-abort.js';
|
|
6
|
-
import { isRedirectStatus, parseObjectArg } from './http-request/
|
|
7
|
-
import { cacheSession, deleteSession, establishSession, flattenMcpResponse, getSession, isNotification, needsSession, normalizeMethod, post, readBody, sessionKey, TIMEOUT_MS } from './mcp/
|
|
6
|
+
import { isRedirectStatus, parseObjectArg } from './http-request/request.js';
|
|
7
|
+
import { cacheSession, deleteSession, establishSession, flattenMcpResponse, getSession, isNotification, needsSession, normalizeMethod, post, readBody, sessionKey, TIMEOUT_MS } from './mcp/protocol.js';
|
|
8
8
|
import { parseAuth, resolveBearerHeader } from './skills/auth-resolver.js';
|
|
9
9
|
import { recordCredentialHealth } from './skills/credential-health.js';
|
|
10
10
|
import { resolveOutboundUrl } from './url-guard.js';
|
|
@@ -13,8 +13,9 @@ function nestedUrl(params) {
|
|
|
13
13
|
}
|
|
14
14
|
function parseMcpParams(value) {
|
|
15
15
|
const parsed = parseObjectArg(value, 'params');
|
|
16
|
-
if (parsed.ok || typeof value !== 'string')
|
|
16
|
+
if (parsed.ok || typeof value !== 'string') {
|
|
17
17
|
return parsed;
|
|
18
|
+
}
|
|
18
19
|
return parseObjectArg(value.replace(/[“”]/g, '"'), 'params');
|
|
19
20
|
}
|
|
20
21
|
function toolParams(params, liftedNestedUrl) {
|
|
@@ -74,11 +75,11 @@ export function createMcpCallTool({ fetch: fetchImpl, credentialStore, credentia
|
|
|
74
75
|
return null;
|
|
75
76
|
}
|
|
76
77
|
},
|
|
77
|
-
async execute(
|
|
78
|
-
const args = rawArgs;
|
|
78
|
+
async execute(args, ctx) {
|
|
79
79
|
const rawParams = parseMcpParams(args.params);
|
|
80
|
-
if (!rawParams.ok)
|
|
80
|
+
if (!rawParams.ok) {
|
|
81
81
|
return { error: rawParams.error };
|
|
82
|
+
}
|
|
82
83
|
const topLevelUrl = typeof args.url === 'string' && args.url.length > 0 ? args.url : undefined;
|
|
83
84
|
const urlValue = topLevelUrl ?? nestedUrl(rawParams.value);
|
|
84
85
|
let url;
|
|
@@ -95,30 +96,48 @@ export function createMcpCallTool({ fetch: fetchImpl, credentialStore, credentia
|
|
|
95
96
|
return { error: 'method must be a non-empty string' };
|
|
96
97
|
}
|
|
97
98
|
const parsedAuth = parseAuth(args.auth);
|
|
98
|
-
if (!parsedAuth.ok)
|
|
99
|
+
if (!parsedAuth.ok) {
|
|
99
100
|
return { error: parsedAuth.error };
|
|
101
|
+
}
|
|
100
102
|
const { method, params } = normalizeMethod(args.method, toolParams(rawParams.value, topLevelUrl === undefined && urlValue !== undefined));
|
|
101
103
|
const controller = new AbortController();
|
|
102
104
|
const timeout = setTimeout(() => controller.abort(undefined), TIMEOUT_MS);
|
|
103
105
|
try {
|
|
104
106
|
const urlCheck = await resolveOutboundUrl(url, resolveHost, controller.signal);
|
|
105
|
-
if (urlCheck.blocked)
|
|
107
|
+
if (urlCheck.blocked) {
|
|
106
108
|
return { error: urlCheck.blocked };
|
|
109
|
+
}
|
|
107
110
|
const bearer = await resolveBearerHeader(parsedAuth.value, url, credentialStore, credentialAllowList?.(ctx), ctx, controller.signal);
|
|
108
|
-
if ('error' in bearer)
|
|
111
|
+
if ('error' in bearer) {
|
|
109
112
|
return { error: bearer.error };
|
|
113
|
+
}
|
|
110
114
|
const authHeader = bearer.header;
|
|
111
|
-
const fetch = fetchImpl ??
|
|
115
|
+
const fetch = fetchImpl ?? noRedirectFetch;
|
|
112
116
|
const guardedFetch = (target, init) => fetch(target, { ...init, validatedAddresses: urlCheck.addresses });
|
|
113
117
|
const key = sessionKey(url.href, ctx, bearer.credentialKey);
|
|
114
118
|
const cached = getSession(key);
|
|
115
|
-
let response = await post(guardedFetch, url,
|
|
119
|
+
let response = await post(guardedFetch, url, {
|
|
120
|
+
method,
|
|
121
|
+
params,
|
|
122
|
+
authHeader,
|
|
123
|
+
sessionId: cached || undefined,
|
|
124
|
+
signal: controller.signal
|
|
125
|
+
});
|
|
116
126
|
if (needsSession(response.status, cached)) {
|
|
117
127
|
deleteSession(key);
|
|
118
|
-
const session = await establishSession(guardedFetch, url,
|
|
128
|
+
const session = await establishSession(guardedFetch, url, {
|
|
129
|
+
authHeader,
|
|
130
|
+
signal: controller.signal
|
|
131
|
+
});
|
|
119
132
|
if (typeof session === 'string') {
|
|
120
133
|
cacheSession(key, session);
|
|
121
|
-
response = await post(guardedFetch, url,
|
|
134
|
+
response = await post(guardedFetch, url, {
|
|
135
|
+
method,
|
|
136
|
+
params,
|
|
137
|
+
authHeader,
|
|
138
|
+
sessionId: session,
|
|
139
|
+
signal: controller.signal
|
|
140
|
+
});
|
|
122
141
|
}
|
|
123
142
|
else if (!session.stateless) {
|
|
124
143
|
return { error: session.error };
|
|
@@ -126,8 +145,9 @@ export function createMcpCallTool({ fetch: fetchImpl, credentialStore, credentia
|
|
|
126
145
|
}
|
|
127
146
|
else {
|
|
128
147
|
const issued = response.headers.get('mcp-session-id');
|
|
129
|
-
if (issued)
|
|
148
|
+
if (issued) {
|
|
130
149
|
cacheSession(key, issued);
|
|
150
|
+
}
|
|
131
151
|
}
|
|
132
152
|
if (isRedirectStatus(response.status)) {
|
|
133
153
|
return { error: `MCP server responded with an unexpected redirect (${response.status})` };
|
|
@@ -135,8 +155,9 @@ export function createMcpCallTool({ fetch: fetchImpl, credentialStore, credentia
|
|
|
135
155
|
if (bearer.credentialKey) {
|
|
136
156
|
recordCredentialHealth(bearer.credentialKey, response.status, ctx?.agentId);
|
|
137
157
|
}
|
|
138
|
-
if (isNotification(method))
|
|
158
|
+
if (isNotification(method)) {
|
|
139
159
|
return { status: response.status, body: '' };
|
|
160
|
+
}
|
|
140
161
|
return { status: response.status, ...flattenMcpResponse(await readBody(response)) };
|
|
141
162
|
}
|
|
142
163
|
catch (err) {
|
|
@@ -22,15 +22,16 @@ function builder(schema, build) {
|
|
|
22
22
|
return {
|
|
23
23
|
build(args) {
|
|
24
24
|
const parsed = schema.safeParse(args);
|
|
25
|
-
if (!parsed.success)
|
|
25
|
+
if (!parsed.success) {
|
|
26
26
|
return { error: parsed.error.issues[0]?.message ?? 'invalid arguments' };
|
|
27
|
+
}
|
|
27
28
|
return { request: build(parsed.data) };
|
|
28
29
|
}
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
32
|
const GmailMessageSchema = z
|
|
32
33
|
.object({ ...GmailEnvelopeSchema.shape, threadId: z.string().optional() })
|
|
33
|
-
.refine((value) =>
|
|
34
|
+
.refine((value) => !!value.text?.length || !!value.html?.length, {
|
|
34
35
|
message: 'either text or html is required'
|
|
35
36
|
});
|
|
36
37
|
const gmailSend = builder(GmailMessageSchema, (args) => ({
|
|
@@ -51,7 +52,7 @@ const gmailGet = builder(GmailGetSchema, (args) => ({
|
|
|
51
52
|
function createRequest() {
|
|
52
53
|
return {
|
|
53
54
|
createRequest: {
|
|
54
|
-
requestId: b4a.
|
|
55
|
+
requestId: b4a.toHex(randomBytes(16)),
|
|
55
56
|
conferenceSolutionKey: { type: 'hangoutsMeet' }
|
|
56
57
|
}
|
|
57
58
|
};
|
|
@@ -98,8 +99,9 @@ const docsAppendText = builder(DocsAppendSchema, (args) => {
|
|
|
98
99
|
const requests = [
|
|
99
100
|
{ insertText: { endOfSegmentLocation: {}, text: args.text } }
|
|
100
101
|
];
|
|
101
|
-
if (args.pageBreak)
|
|
102
|
+
if (args.pageBreak) {
|
|
102
103
|
requests.push({ insertPageBreak: { endOfSegmentLocation: {} } });
|
|
104
|
+
}
|
|
103
105
|
return { body: { requests } };
|
|
104
106
|
});
|
|
105
107
|
const sheetsCreate = builder(z.object({ name: z.string().min(1) }), (args) => ({
|
|
@@ -145,10 +147,12 @@ const NotionCreatePageSchema = z
|
|
|
145
147
|
message: 'pass parentPageId or parentDatabaseId, not both'
|
|
146
148
|
});
|
|
147
149
|
function notionParent(args) {
|
|
148
|
-
if (args.parentPageId)
|
|
150
|
+
if (args.parentPageId) {
|
|
149
151
|
return { page_id: args.parentPageId };
|
|
150
|
-
|
|
152
|
+
}
|
|
153
|
+
if (args.parentDatabaseId) {
|
|
151
154
|
return { database_id: args.parentDatabaseId };
|
|
155
|
+
}
|
|
152
156
|
return undefined;
|
|
153
157
|
}
|
|
154
158
|
const notionCreatePage = builder(NotionCreatePageSchema, (args) => ({
|