stitchkit 0.93.0 → 0.94.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 +134 -0
- package/README.md +5 -2
- package/dist/agent-runtime/children-control.d.ts +6 -0
- package/dist/agent-runtime/children-control.d.ts.map +1 -0
- package/dist/agent-runtime/children-spawn.d.ts +6 -0
- package/dist/agent-runtime/children-spawn.d.ts.map +1 -0
- package/dist/agent-runtime/children-state.d.ts +46 -0
- package/dist/agent-runtime/children-state.d.ts.map +1 -0
- package/dist/agent-runtime/children-usage.d.ts +8 -0
- package/dist/agent-runtime/children-usage.d.ts.map +1 -0
- package/dist/agent-runtime/children.d.ts +11 -14
- package/dist/agent-runtime/children.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tool-contract.d.ts +2 -1
- package/dist/agent-runtime/coding-tool-contract.d.ts.map +1 -1
- package/dist/agent-runtime/deferred-tool-types.d.ts +2 -2
- package/dist/agent-runtime/deferred-tool-types.d.ts.map +1 -1
- package/dist/agent-runtime/events.d.ts +1 -1
- package/dist/agent-runtime/events.d.ts.map +1 -1
- package/dist/agent-runtime/managed-tools.d.ts +1 -1
- package/dist/agent-runtime/managed-tools.d.ts.map +1 -1
- package/dist/agent-runtime/observability.d.ts +1 -1
- package/dist/agent-runtime/observability.d.ts.map +1 -1
- package/dist/{agent-runtime-openrouter.d.ts → agent-runtime/openrouter.d.ts} +3 -3
- package/dist/agent-runtime/openrouter.d.ts.map +1 -0
- package/dist/agent-runtime/projections.d.ts +1 -1
- package/dist/agent-runtime/projections.d.ts.map +1 -1
- package/dist/agent-runtime/run-acquisition.d.ts +17 -0
- package/dist/agent-runtime/run-acquisition.d.ts.map +1 -0
- package/dist/agent-runtime/run-execution-state.d.ts +133 -0
- package/dist/agent-runtime/run-execution-state.d.ts.map +1 -0
- package/dist/agent-runtime/run-execution.d.ts +17 -33
- package/dist/agent-runtime/run-execution.d.ts.map +1 -1
- package/dist/agent-runtime/run-failure.d.ts +18 -0
- package/dist/agent-runtime/run-failure.d.ts.map +1 -0
- package/dist/agent-runtime/run-model-attempts.d.ts +6 -0
- package/dist/agent-runtime/run-model-attempts.d.ts.map +1 -0
- package/dist/agent-runtime/run-prompt-history.d.ts +58 -0
- package/dist/agent-runtime/run-prompt-history.d.ts.map +1 -0
- package/dist/agent-runtime/run-provider-ledger.d.ts +26 -0
- package/dist/agent-runtime/run-provider-ledger.d.ts.map +1 -0
- package/dist/agent-runtime/run-stream-parts.d.ts +17 -0
- package/dist/agent-runtime/run-stream-parts.d.ts.map +1 -0
- package/dist/agent-runtime/run-terminal.d.ts +11 -0
- package/dist/agent-runtime/run-terminal.d.ts.map +1 -0
- package/dist/agent-runtime/run-turn-preparation.d.ts +22 -0
- package/dist/agent-runtime/run-turn-preparation.d.ts.map +1 -0
- package/dist/agent-runtime/runtime-admission-gate.d.ts +29 -0
- package/dist/agent-runtime/runtime-admission-gate.d.ts.map +1 -0
- package/dist/agent-runtime/runtime-control.d.ts +9 -0
- package/dist/agent-runtime/runtime-control.d.ts.map +1 -0
- package/dist/agent-runtime/runtime-recovery.d.ts +10 -0
- package/dist/agent-runtime/runtime-recovery.d.ts.map +1 -0
- package/dist/agent-runtime/runtime-state.d.ts +36 -0
- package/dist/agent-runtime/runtime-state.d.ts.map +1 -0
- package/dist/agent-runtime/runtime-submit.d.ts +5 -0
- package/dist/agent-runtime/runtime-submit.d.ts.map +1 -0
- package/dist/agent-runtime/runtime.d.ts +9 -4
- package/dist/agent-runtime/runtime.d.ts.map +1 -1
- package/dist/{agent-runtime-sqlite-bun.d.ts → agent-runtime/sqlite-bun.d.ts} +5 -4
- package/dist/agent-runtime/sqlite-bun.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite-events.d.ts +5 -0
- package/dist/agent-runtime/sqlite-events.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite-history.d.ts +7 -0
- package/dist/agent-runtime/sqlite-history.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite-ledger.d.ts +7 -0
- package/dist/agent-runtime/sqlite-ledger.d.ts.map +1 -0
- package/dist/{agent-runtime-sqlite-node.d.ts → agent-runtime/sqlite-node.d.ts} +5 -4
- package/dist/agent-runtime/sqlite-node.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite-reads.d.ts +14 -0
- package/dist/agent-runtime/sqlite-reads.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite-rows.d.ts +178 -0
- package/dist/agent-runtime/sqlite-rows.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite-schema.d.ts +7 -0
- package/dist/agent-runtime/sqlite-schema.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite-transactions.d.ts +25 -0
- package/dist/agent-runtime/sqlite-transactions.d.ts.map +1 -0
- package/dist/agent-runtime/sqlite.d.ts +10 -6
- package/dist/agent-runtime/sqlite.d.ts.map +1 -1
- package/dist/agent-runtime/store-archive.d.ts +16 -0
- package/dist/agent-runtime/store-archive.d.ts.map +1 -0
- package/dist/{testing/agent-store-conformance.d.ts → agent-runtime/store-conformance.d.ts} +2 -2
- package/dist/agent-runtime/store-conformance.d.ts.map +1 -0
- package/dist/agent-runtime/store-create.d.ts +11 -0
- package/dist/agent-runtime/store-create.d.ts.map +1 -0
- package/dist/agent-runtime/{store-driver.d.ts → store-driver-contract.d.ts} +7 -7
- package/dist/agent-runtime/store-driver-contract.d.ts.map +1 -0
- package/dist/agent-runtime/store-events.d.ts +7 -89
- package/dist/agent-runtime/store-events.d.ts.map +1 -1
- package/dist/agent-runtime/store-memory.d.ts +4 -0
- package/dist/agent-runtime/store-memory.d.ts.map +1 -0
- package/dist/agent-runtime/store-mutate.d.ts +13 -0
- package/dist/agent-runtime/store-mutate.d.ts.map +1 -0
- package/dist/agent-runtime/store-purge.d.ts +1 -1
- package/dist/agent-runtime/store-purge.d.ts.map +1 -1
- package/dist/agent-runtime/store-reads.d.ts +29 -0
- package/dist/agent-runtime/store-reads.d.ts.map +1 -0
- package/dist/agent-runtime/store-reduce-history.d.ts +10 -0
- package/dist/agent-runtime/store-reduce-history.d.ts.map +1 -0
- package/dist/agent-runtime/store-reduce-run.d.ts +14 -0
- package/dist/agent-runtime/store-reduce-run.d.ts.map +1 -0
- package/dist/agent-runtime/store-reduce-shared.d.ts +76 -0
- package/dist/agent-runtime/store-reduce-shared.d.ts.map +1 -0
- package/dist/agent-runtime/store-reduce-terminal.d.ts +6 -0
- package/dist/agent-runtime/store-reduce-terminal.d.ts.map +1 -0
- package/dist/agent-runtime/store-reducer.d.ts +24 -0
- package/dist/agent-runtime/store-reducer.d.ts.map +1 -0
- package/dist/agent-runtime/store-snapshot.d.ts +19 -0
- package/dist/agent-runtime/store-snapshot.d.ts.map +1 -0
- package/dist/agent-runtime/store.d.ts +1 -1
- package/dist/agent-runtime/store.d.ts.map +1 -1
- package/dist/application/admission.d.ts +1 -1
- package/dist/application/admission.d.ts.map +1 -1
- package/dist/application/events.d.ts +1 -1
- package/dist/application/events.d.ts.map +1 -1
- package/dist/{server → application}/file-state-store.d.ts +1 -1
- package/dist/application/file-state-store.d.ts.map +1 -0
- package/dist/application/health.d.ts +1 -1
- package/dist/application/health.d.ts.map +1 -1
- package/dist/application/kernel-admission.d.ts +8 -0
- package/dist/application/kernel-admission.d.ts.map +1 -0
- package/dist/application/kernel-contract.d.ts +142 -0
- package/dist/application/kernel-contract.d.ts.map +1 -0
- package/dist/application/kernel-resource-context.d.ts +24 -0
- package/dist/application/kernel-resource-context.d.ts.map +1 -0
- package/dist/application/kernel-restart.d.ts +5 -0
- package/dist/application/kernel-restart.d.ts.map +1 -0
- package/dist/application/kernel-rollback.d.ts +4 -0
- package/dist/application/kernel-rollback.d.ts.map +1 -0
- package/dist/application/kernel-shutdown.d.ts +6 -0
- package/dist/application/kernel-shutdown.d.ts.map +1 -0
- package/dist/application/kernel-start.d.ts +20 -0
- package/dist/application/kernel-start.d.ts.map +1 -0
- package/dist/application/kernel-state.d.ts +116 -0
- package/dist/application/kernel-state.d.ts.map +1 -0
- package/dist/application/kernel.d.ts +9 -140
- package/dist/application/kernel.d.ts.map +1 -1
- package/dist/application/latest-sink.d.ts +1 -1
- package/dist/application/latest-sink.d.ts.map +1 -1
- package/dist/application/opentelemetry.d.ts +1 -1
- package/dist/application/opentelemetry.d.ts.map +1 -1
- package/dist/application/watch-hub.d.ts +2 -2
- package/dist/application/watch-hub.d.ts.map +1 -1
- package/dist/browser/client-multipart.d.ts +1 -1
- package/dist/browser/client-multipart.d.ts.map +1 -1
- package/dist/browser/client-url.d.ts +1 -1
- package/dist/browser/client-url.d.ts.map +1 -1
- package/dist/browser/client.d.ts +1 -1
- package/dist/browser/client.d.ts.map +1 -1
- package/dist/browser/contract-stream.d.ts +1 -1
- package/dist/browser/contract-stream.d.ts.map +1 -1
- package/dist/browser/http.d.ts +1 -1
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/browser/resumable.d.ts +1 -28
- package/dist/browser/resumable.d.ts.map +1 -1
- package/dist/browser/retained.d.ts.map +1 -0
- package/dist/browser/socket-io.d.ts +23 -1
- package/dist/browser/socket-io.d.ts.map +1 -1
- package/dist/browser/stream.d.ts +14 -2
- package/dist/browser/stream.d.ts.map +1 -1
- package/dist/contract/define.d.ts +72 -65
- package/dist/contract/define.d.ts.map +1 -1
- package/dist/{internal/errors.d.ts → contract/normalize.d.ts} +3 -39
- package/dist/contract/normalize.d.ts.map +1 -0
- package/dist/contract/tool-view.d.ts +135 -0
- package/dist/contract/tool-view.d.ts.map +1 -0
- package/dist/declaration/declaration.d.ts.map +1 -0
- package/dist/{agent-runtime/durability-contract.d.ts → durability/contract.d.ts} +2 -2
- package/dist/durability/contract.d.ts.map +1 -0
- package/dist/{agent-runtime/durability.d.ts → durability/engine.d.ts} +4 -4
- package/dist/durability/engine.d.ts.map +1 -0
- package/dist/durability/events.d.ts +101 -0
- package/dist/durability/events.d.ts.map +1 -0
- package/dist/{agent-runtime/durability-json.d.ts → durability/json.d.ts} +1 -1
- package/dist/durability/json.d.ts.map +1 -0
- package/dist/{agent-runtime/durability-ledger.d.ts → durability/ledger.d.ts} +2 -2
- package/dist/durability/ledger.d.ts.map +1 -0
- package/dist/{agent-runtime/durability-scheduler.d.ts → durability/scheduler.d.ts} +2 -2
- package/dist/durability/scheduler.d.ts.map +1 -0
- package/dist/entrypoints/agent-runtime/browser.d.ts +5 -0
- package/dist/entrypoints/agent-runtime/browser.d.ts.map +1 -0
- package/dist/{agent-runtime-browser.js → entrypoints/agent-runtime/browser.js} +3 -3
- package/dist/{agent-runtime-coding-tools.d.ts → entrypoints/agent-runtime/coding-tools.d.ts} +2 -2
- package/dist/entrypoints/agent-runtime/coding-tools.d.ts.map +1 -0
- package/dist/entrypoints/agent-runtime/coding-tools.js +22 -0
- package/dist/{agent-runtime-harness.d.ts → entrypoints/agent-runtime/harness.d.ts} +2 -2
- package/dist/entrypoints/agent-runtime/harness.d.ts.map +1 -0
- package/dist/{agent-runtime-harness.js → entrypoints/agent-runtime/harness.js} +24 -25
- package/dist/entrypoints/agent-runtime/openrouter.d.ts +2 -0
- package/dist/entrypoints/agent-runtime/openrouter.d.ts.map +1 -0
- package/dist/{agent-runtime-openrouter.js → entrypoints/agent-runtime/openrouter.js} +3 -3
- package/dist/{agent-runtime-sandbox.d.ts → entrypoints/agent-runtime/sandbox.d.ts} +5 -5
- package/dist/entrypoints/agent-runtime/sandbox.d.ts.map +1 -0
- package/dist/{agent-runtime-sandbox.js → entrypoints/agent-runtime/sandbox.js} +11 -12
- package/dist/entrypoints/agent-runtime/sqlite/bun.d.ts +2 -0
- package/dist/entrypoints/agent-runtime/sqlite/bun.d.ts.map +1 -0
- package/dist/{agent-runtime-sqlite-bun.js → entrypoints/agent-runtime/sqlite/bun.js} +8 -6
- package/dist/entrypoints/agent-runtime/sqlite/node.d.ts +2 -0
- package/dist/entrypoints/agent-runtime/sqlite/node.d.ts.map +1 -0
- package/dist/{agent-runtime-sqlite-node.js → entrypoints/agent-runtime/sqlite/node.js} +8 -6
- package/dist/entrypoints/agent-runtime/testing.d.ts +4 -0
- package/dist/entrypoints/agent-runtime/testing.d.ts.map +1 -0
- package/dist/entrypoints/agent-runtime/testing.js +1084 -0
- package/dist/{agent-runtime.d.ts → entrypoints/agent-runtime.d.ts} +41 -37
- package/dist/entrypoints/agent-runtime.d.ts.map +1 -0
- package/dist/{agent-runtime.js → entrypoints/agent-runtime.js} +408 -366
- package/dist/{application-diagnostic-journal.d.ts → entrypoints/application/diagnostic-journal.d.ts} +2 -2
- package/dist/entrypoints/application/diagnostic-journal.d.ts.map +1 -0
- package/dist/{application-diagnostic-journal.js → entrypoints/application/diagnostic-journal.js} +3 -3
- package/dist/{application-grammy.d.ts → entrypoints/application/grammy.d.ts} +2 -2
- package/dist/entrypoints/application/grammy.d.ts.map +1 -0
- package/dist/{application-grammy.js → entrypoints/application/grammy.js} +2 -2
- package/dist/{application-opentelemetry.d.ts → entrypoints/application/opentelemetry.d.ts} +2 -2
- package/dist/entrypoints/application/opentelemetry.d.ts.map +1 -0
- package/dist/{application-opentelemetry.js → entrypoints/application/opentelemetry.js} +4 -4
- package/dist/{application-schemas.d.ts → entrypoints/application/schemas.d.ts} +2 -2
- package/dist/entrypoints/application/schemas.d.ts.map +1 -0
- package/dist/{application-schemas.js → entrypoints/application/schemas.js} +2 -2
- package/dist/{application.d.ts → entrypoints/application.d.ts} +22 -21
- package/dist/entrypoints/application.d.ts.map +1 -0
- package/dist/{application.js → entrypoints/application.js} +24 -22
- package/dist/entrypoints/bin/upgrade-cli.d.ts.map +1 -0
- package/dist/{upgrade-cli.js → entrypoints/bin/upgrade-cli.js} +3 -3
- package/dist/{cli.d.ts → entrypoints/cli.d.ts} +15 -15
- package/dist/entrypoints/cli.d.ts.map +1 -0
- package/dist/{cli.js → entrypoints/cli.js} +30 -32
- package/dist/entrypoints/contract.d.ts +9 -0
- package/dist/entrypoints/contract.d.ts.map +1 -0
- package/dist/{contract/index.js → entrypoints/contract.js} +14 -6
- package/dist/entrypoints/declaration.d.ts +2 -0
- package/dist/entrypoints/declaration.d.ts.map +1 -0
- package/dist/{declaration.js → entrypoints/declaration.js} +1 -1
- package/dist/{files.d.ts → entrypoints/files.d.ts} +2 -2
- package/dist/entrypoints/files.d.ts.map +1 -0
- package/dist/{files.js → entrypoints/files.js} +3 -3
- package/dist/{geo.d.ts → entrypoints/geo.d.ts} +1 -1
- package/dist/entrypoints/geo.d.ts.map +1 -0
- package/dist/{geo.js → entrypoints/geo.js} +4 -4
- package/dist/entrypoints/google.d.ts +2 -0
- package/dist/entrypoints/google.d.ts.map +1 -0
- package/dist/{google.js → entrypoints/google.js} +1 -1
- package/dist/{index.d.ts → entrypoints/index.d.ts} +14 -13
- package/dist/entrypoints/index.d.ts.map +1 -0
- package/dist/{index.js → entrypoints/index.js} +99 -33
- package/dist/{live.d.ts → entrypoints/live.d.ts} +4 -4
- package/dist/entrypoints/live.d.ts.map +1 -0
- package/dist/{live.js → entrypoints/live.js} +6 -4
- package/dist/{node.d.ts → entrypoints/node.d.ts} +11 -11
- package/dist/entrypoints/node.d.ts.map +1 -0
- package/dist/{node.js → entrypoints/node.js} +20 -16
- package/dist/entrypoints/oauth.d.ts +2 -0
- package/dist/entrypoints/oauth.d.ts.map +1 -0
- package/dist/{oauth.js → entrypoints/oauth.js} +3 -3
- package/dist/{observability/index.d.ts → entrypoints/observability.d.ts} +11 -11
- package/dist/entrypoints/observability.d.ts.map +1 -0
- package/dist/{observability/index.js → entrypoints/observability.js} +9 -11
- package/dist/entrypoints/primitives.d.ts +3 -0
- package/dist/entrypoints/primitives.d.ts.map +1 -0
- package/dist/{primitives.js → entrypoints/primitives.js} +1 -6
- package/dist/{react.d.ts → entrypoints/react.d.ts} +4 -4
- package/dist/entrypoints/react.d.ts.map +1 -0
- package/dist/{react.js → entrypoints/react.js} +3 -3
- package/dist/{release.d.ts → entrypoints/release.d.ts} +4 -4
- package/dist/entrypoints/release.d.ts.map +1 -0
- package/dist/{release.js → entrypoints/release.js} +1 -1
- package/dist/{remote.d.ts → entrypoints/remote.d.ts} +1 -1
- package/dist/entrypoints/remote.d.ts.map +1 -0
- package/dist/entrypoints/remote.js +66 -0
- package/dist/{server/index.d.ts → entrypoints/server.d.ts} +35 -34
- package/dist/entrypoints/server.d.ts.map +1 -0
- package/dist/{server/index.js → entrypoints/server.js} +294 -244
- package/dist/{telegram.d.ts → entrypoints/telegram.d.ts} +2 -2
- package/dist/entrypoints/telegram.d.ts.map +1 -0
- package/dist/{telegram.js → entrypoints/telegram.js} +1 -1
- package/dist/entrypoints/testing.d.ts +9 -0
- package/dist/entrypoints/testing.d.ts.map +1 -0
- package/dist/{testing.js → entrypoints/testing.js} +80 -919
- package/dist/{tools/connections/index.d.ts → entrypoints/tools/connections.d.ts} +6 -7
- package/dist/entrypoints/tools/connections.d.ts.map +1 -0
- package/dist/{tools/connections/index.js → entrypoints/tools/connections.js} +7 -4
- package/dist/{tools-contract.d.ts → entrypoints/tools/contract.d.ts} +5 -5
- package/dist/entrypoints/tools/contract.d.ts.map +1 -0
- package/dist/{tools-contract.js → entrypoints/tools/contract.js} +4 -4
- package/dist/{tool-invoker.d.ts → entrypoints/tools/invoker.d.ts} +2 -2
- package/dist/entrypoints/tools/invoker.d.ts.map +1 -0
- package/dist/entrypoints/tools/invoker.js +20 -0
- package/dist/entrypoints/tools.d.ts +51 -0
- package/dist/entrypoints/tools.d.ts.map +1 -0
- package/dist/{tools.js → entrypoints/tools.js} +1787 -1751
- package/dist/{tracking-server.d.ts → entrypoints/tracking/server.d.ts} +8 -8
- package/dist/entrypoints/tracking/server.d.ts.map +1 -0
- package/dist/{tracking.d.ts → entrypoints/tracking.d.ts} +15 -15
- package/dist/entrypoints/tracking.d.ts.map +1 -0
- package/dist/{tracking.js → entrypoints/tracking.js} +5 -8
- package/dist/google/google.d.ts.map +1 -0
- package/dist/{index-a916km3r.js → index-0b1ess5e.js} +6 -13
- package/dist/{index-zwkgfq1s.js → index-1nd48tm9.js} +113 -118
- package/dist/{index-7zbps32p.js → index-1zbbzgfh.js} +2 -2
- package/dist/{index-mzvgxp0n.js → index-2cr1cqhn.js} +16 -4
- package/dist/{index-f9mb610r.js → index-35mh5bhj.js} +2 -2
- package/dist/index-48ffdxgk.js +6 -0
- package/dist/index-58k4fp34.js +1023 -0
- package/dist/{index-p13mwz16.js → index-83bdg97s.js} +1 -1
- package/dist/index-97ac4gph.js +3618 -0
- package/dist/{index-kw9t5ayn.js → index-cq4qfycf.js} +5 -5
- package/dist/{index-7gnkny4z.js → index-d9pk3td4.js} +1 -1
- package/dist/{index-e42wzzz5.js → index-dcpdvjsc.js} +7 -7
- package/dist/{index-h9etkkyk.js → index-dydjcp8z.js} +102 -25
- package/dist/{index-sr93cwf9.js → index-e2z3ahw9.js} +2 -2
- package/dist/{index-yewwh1w4.js → index-e87kakm4.js} +96 -11
- package/dist/{index-cby4ar3v.js → index-eqqvgdb3.js} +1 -1
- package/dist/index-fdp6hdxn.js +942 -0
- package/dist/{index-0a349c33.js → index-gyr48nzg.js} +125 -198
- package/dist/{index-wnfk50r0.js → index-h6eeys1v.js} +1 -1
- package/dist/{index-77fekveh.js → index-hffjpmq9.js} +1 -15
- package/dist/{index-yn8remb1.js → index-j05z5gn4.js} +50 -14
- package/dist/index-k5hsfc3j.js +31 -0
- package/dist/{index-fq77yfzc.js → index-kj2wbeta.js} +1 -1
- package/dist/index-kvj7f4hq.js +39 -0
- package/dist/{index-4fpa9dy2.js → index-m9zn6cpq.js} +2 -2
- package/dist/{index-2na0qrfn.js → index-mdzh02yc.js} +7 -7
- package/dist/{index-ywd0y6m1.js → index-mkm15s49.js} +1 -1
- package/dist/index-mp5sn43f.js +1084 -0
- package/dist/{index-3bmnp41g.js → index-n6hxg8as.js} +507 -419
- package/dist/index-ngfhvfpd.js +30 -0
- package/dist/{index-e0znv5gn.js → index-p5r7qpdd.js} +2 -93
- package/dist/{index-n0f51dfn.js → index-p81267c2.js} +608 -549
- package/dist/{index-wc4x00bn.js → index-qwhynv6x.js} +1 -1
- package/dist/index-r7nmqpsg.js +154 -0
- package/dist/{index-bx75zwt7.js → index-rewkc2hj.js} +5 -4
- package/dist/{index-9f8ztey6.js → index-sees8yf8.js} +2 -2
- package/dist/{index-vjpvjqpn.js → index-v38qyerb.js} +16 -2
- package/dist/{index-2c2bjw3s.js → index-vcvkq30w.js} +3 -3
- package/dist/index-wqgjfstn.js +18 -0
- package/dist/{index-f477k430.js → index-x4799c19.js} +1 -1
- package/dist/{index-cxqhvq21.js → index-xnj9bgxe.js} +142 -129
- package/dist/index-xsqjkha9.js +281 -0
- package/dist/{index-6kyksz9y.js → index-yw4fzmdz.js} +1 -1
- package/dist/{index-3xwxfj4z.js → index-zeemqhqx.js} +1 -1
- package/dist/{index-tmwcrptx.js → index-zfrhfn82.js} +1 -1
- package/dist/index-znfs20ss.js +98 -0
- package/dist/internal/backoff.d.ts +29 -0
- package/dist/internal/backoff.d.ts.map +1 -0
- package/dist/internal/canonical-json.d.ts +21 -11
- package/dist/internal/canonical-json.d.ts.map +1 -1
- package/dist/internal/channel.d.ts.map +1 -0
- package/dist/internal/logger.d.ts.map +1 -0
- package/dist/internal/request.d.ts.map +1 -0
- package/dist/internal/stable-digest.d.ts +0 -43
- package/dist/internal/stable-digest.d.ts.map +1 -1
- package/dist/internal/typed.d.ts +0 -8
- package/dist/internal/typed.d.ts.map +1 -1
- package/dist/internal/zod-issues.d.ts +38 -0
- package/dist/internal/zod-issues.d.ts.map +1 -0
- package/dist/{tools/json-schema-dialect.d.ts → json-schema/dialect.d.ts} +1 -1
- package/dist/json-schema/dialect.d.ts.map +1 -0
- package/dist/json-schema/json-schema.d.ts.map +1 -0
- package/dist/live/watch-delta.d.ts.map +1 -1
- package/dist/{oauth.d.ts → oauth/authorization-code.d.ts} +1 -1
- package/dist/oauth/authorization-code.d.ts.map +1 -0
- package/dist/observability/audit.d.ts +2 -2
- package/dist/observability/audit.d.ts.map +1 -1
- package/dist/observability/bounded-logger.d.ts +1 -1
- package/dist/observability/bounded-logger.d.ts.map +1 -1
- package/dist/{internal/observability-sink.d.ts → observability/bounded-sink.d.ts} +2 -2
- package/dist/observability/bounded-sink.d.ts.map +1 -0
- package/dist/observability/context.d.ts +1 -1
- package/dist/observability/context.d.ts.map +1 -1
- package/dist/observability/event.d.ts +1 -1
- package/dist/observability/event.d.ts.map +1 -1
- package/dist/observability/work.d.ts +1 -1
- package/dist/observability/work.d.ts.map +1 -1
- package/dist/primitives/audit.d.ts +1 -1
- package/dist/primitives/audit.d.ts.map +1 -1
- package/dist/primitives/export-operation.d.ts +4 -2
- package/dist/primitives/export-operation.d.ts.map +1 -1
- package/dist/realtime/socket.d.ts +1 -1
- package/dist/realtime/socket.d.ts.map +1 -1
- package/dist/server/context-contribution.d.ts +1 -1
- package/dist/server/context-contribution.d.ts.map +1 -1
- package/dist/server/context.d.ts +2 -2
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/contract-method.d.ts +15 -0
- package/dist/server/contract-method.d.ts.map +1 -0
- package/dist/server/contract-stream.d.ts +1 -1
- package/dist/server/contract-stream.d.ts.map +1 -1
- package/dist/server/create.d.ts +8 -0
- package/dist/server/create.d.ts.map +1 -1
- package/dist/server/endpoint-dispatch.d.ts +10 -0
- package/dist/server/endpoint-dispatch.d.ts.map +1 -0
- package/dist/server/endpoint-response.d.ts +18 -0
- package/dist/server/endpoint-response.d.ts.map +1 -0
- package/dist/server/error-dispatch.d.ts +17 -1
- package/dist/server/error-dispatch.d.ts.map +1 -1
- package/dist/server/error-hook.d.ts +2 -2
- package/dist/server/error-hook.d.ts.map +1 -1
- package/dist/server/handler-state.d.ts +44 -0
- package/dist/server/handler-state.d.ts.map +1 -0
- package/dist/server/implement.d.ts +1 -1
- package/dist/server/implement.d.ts.map +1 -1
- package/dist/server/middleware/auth.d.ts +1 -1
- package/dist/server/middleware/auth.d.ts.map +1 -1
- package/dist/server/middleware/cors.d.ts +17 -0
- package/dist/server/middleware/cors.d.ts.map +1 -1
- package/dist/server/middleware/trust-fence.d.ts +1 -1
- package/dist/server/middleware/trust-fence.d.ts.map +1 -1
- package/dist/server/multipart.d.ts +1 -1
- package/dist/server/multipart.d.ts.map +1 -1
- package/dist/{tools/oauth-metadata.d.ts → server/oauth/metadata.d.ts} +2 -2
- package/dist/server/oauth/metadata.d.ts.map +1 -0
- package/dist/{tools/oauth-provider.d.ts → server/oauth/provider.d.ts} +2 -2
- package/dist/server/oauth/provider.d.ts.map +1 -0
- package/dist/server/realtime.d.ts +1 -1
- package/dist/server/realtime.d.ts.map +1 -1
- package/dist/server/request-completion.d.ts +16 -0
- package/dist/server/request-completion.d.ts.map +1 -0
- package/dist/server/response-metadata.d.ts +1 -1
- package/dist/server/response-metadata.d.ts.map +1 -1
- package/dist/server/route-table.d.ts +9 -0
- package/dist/server/route-table.d.ts.map +1 -0
- package/dist/server/stream.d.ts +0 -13
- package/dist/server/stream.d.ts.map +1 -1
- package/dist/server/types.d.ts +7 -4
- package/dist/server/types.d.ts.map +1 -1
- package/dist/testing/handler-clients.d.ts +31 -0
- package/dist/testing/handler-clients.d.ts.map +1 -0
- package/dist/testing/managed-resource-conformance-scenarios.d.ts.map +1 -1
- package/dist/testing/surface-conformance.d.ts +2 -1
- package/dist/testing/surface-conformance.d.ts.map +1 -1
- package/dist/testing/surface-manifest.d.ts +30 -4
- package/dist/testing/surface-manifest.d.ts.map +1 -1
- package/dist/tools/{cli-args.d.ts → cli/args.d.ts} +1 -1
- package/dist/tools/cli/args.d.ts.map +1 -0
- package/dist/tools/{cli-checkpoint.d.ts → cli/checkpoint.d.ts} +1 -1
- package/dist/tools/cli/checkpoint.d.ts.map +1 -0
- package/dist/tools/{cli-command.d.ts → cli/command.d.ts} +4 -4
- package/dist/tools/cli/command.d.ts.map +1 -0
- package/dist/tools/{cli.d.ts → cli/config.d.ts} +5 -9
- package/dist/tools/cli/config.d.ts.map +1 -0
- package/dist/tools/cli/create-cli.d.ts +36 -0
- package/dist/tools/cli/create-cli.d.ts.map +1 -0
- package/dist/tools/cli/download.d.ts +8 -0
- package/dist/tools/cli/download.d.ts.map +1 -0
- package/dist/tools/{cli-format.d.ts → cli/format.d.ts} +2 -2
- package/dist/tools/cli/format.d.ts.map +1 -0
- package/dist/tools/cli/help.d.ts +24 -0
- package/dist/tools/cli/help.d.ts.map +1 -0
- package/dist/tools/{cli-installer.d.ts → cli/installer.d.ts} +2 -2
- package/dist/tools/cli/installer.d.ts.map +1 -0
- package/dist/tools/{cli-invoke.d.ts → cli/invoke.d.ts} +8 -8
- package/dist/tools/cli/invoke.d.ts.map +1 -0
- package/dist/tools/cli/io.d.ts +10 -0
- package/dist/tools/cli/io.d.ts.map +1 -0
- package/dist/tools/cli/managed-surface.d.ts +30 -0
- package/dist/tools/cli/managed-surface.d.ts.map +1 -0
- package/dist/tools/{cli-manifest.d.ts → cli/manifest.d.ts} +1 -1
- package/dist/tools/cli/manifest.d.ts.map +1 -0
- package/dist/tools/{cli-policy.d.ts → cli/policy.d.ts} +2 -2
- package/dist/tools/cli/policy.d.ts.map +1 -0
- package/dist/tools/cli/prepare.d.ts +14 -0
- package/dist/tools/cli/prepare.d.ts.map +1 -0
- package/dist/tools/{cli-profile.d.ts → cli/profile.d.ts} +2 -2
- package/dist/tools/cli/profile.d.ts.map +1 -0
- package/dist/tools/cli/run-managed.d.ts +5 -0
- package/dist/tools/cli/run-managed.d.ts.map +1 -0
- package/dist/tools/cli/run-native.d.ts +9 -0
- package/dist/tools/cli/run-native.d.ts.map +1 -0
- package/dist/tools/cli/session.d.ts +40 -0
- package/dist/tools/cli/session.d.ts.map +1 -0
- package/dist/tools/{cli-signature.d.ts → cli/signature.d.ts} +2 -2
- package/dist/tools/cli/signature.d.ts.map +1 -0
- package/dist/tools/{cli-stream.d.ts → cli/stream.d.ts} +4 -4
- package/dist/tools/cli/stream.d.ts.map +1 -0
- package/dist/tools/{cli-update.d.ts → cli/update.d.ts} +3 -3
- package/dist/tools/cli/update.d.ts.map +1 -0
- package/dist/tools/{cli-view.d.ts → cli/view.d.ts} +2 -2
- package/dist/tools/cli/view.d.ts.map +1 -0
- package/dist/tools/{cli-wait.d.ts → cli/wait.d.ts} +2 -2
- package/dist/tools/cli/wait.d.ts.map +1 -0
- package/dist/tools/connections/runtime.d.ts +1 -1
- package/dist/tools/connections/runtime.d.ts.map +1 -1
- package/dist/tools/connections/types.d.ts +3 -3
- package/dist/tools/connections/types.d.ts.map +1 -1
- package/dist/tools/durability-context.d.ts +1 -1
- package/dist/tools/durability-context.d.ts.map +1 -1
- package/dist/tools/execute.d.ts +30 -3
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools/internal/surface-projector.d.ts +8 -6
- package/dist/tools/internal/surface-projector.d.ts.map +1 -1
- package/dist/tools/internal/tool-view.d.ts +41 -0
- package/dist/tools/internal/tool-view.d.ts.map +1 -0
- package/dist/tools/invoker.d.ts +11 -3
- package/dist/tools/invoker.d.ts.map +1 -1
- package/dist/tools/list-names.d.ts +3 -3
- package/dist/tools/list-names.d.ts.map +1 -1
- package/dist/tools/manifest.d.ts +2 -2
- package/dist/tools/manifest.d.ts.map +1 -1
- package/dist/tools/{mcp-app.d.ts → mcp/app.d.ts} +1 -1
- package/dist/tools/mcp/app.d.ts.map +1 -0
- package/dist/tools/{mcp-catalog.d.ts → mcp/catalog.d.ts} +2 -2
- package/dist/tools/mcp/catalog.d.ts.map +1 -0
- package/dist/tools/{mcp-handler.d.ts → mcp/handler.d.ts} +5 -5
- package/dist/tools/mcp/handler.d.ts.map +1 -0
- package/dist/tools/{mcp.d.ts → mcp/mount.d.ts} +9 -9
- package/dist/tools/mcp/mount.d.ts.map +1 -0
- package/dist/tools/{mcp-prepare.d.ts → mcp/prepare.d.ts} +9 -9
- package/dist/tools/mcp/prepare.d.ts.map +1 -0
- package/dist/tools/{mcp-progress.d.ts → mcp/progress.d.ts} +2 -2
- package/dist/tools/mcp/progress.d.ts.map +1 -0
- package/dist/tools/mcp/register.d.ts +35 -0
- package/dist/tools/mcp/register.d.ts.map +1 -0
- package/dist/tools/{mcp-round-policy.d.ts → mcp/round-policy.d.ts} +2 -2
- package/dist/tools/mcp/round-policy.d.ts.map +1 -0
- package/dist/tools/{mcp-round.d.ts → mcp/round.d.ts} +4 -4
- package/dist/tools/mcp/round.d.ts.map +1 -0
- package/dist/tools/mcp/runtime-tools.d.ts +24 -0
- package/dist/tools/mcp/runtime-tools.d.ts.map +1 -0
- package/dist/tools/{mcp-stdio-signals.d.ts → mcp/stdio-signals.d.ts} +3 -3
- package/dist/tools/mcp/stdio-signals.d.ts.map +1 -0
- package/dist/tools/{mcp-stdio.d.ts → mcp/stdio.d.ts} +4 -4
- package/dist/tools/mcp/stdio.d.ts.map +1 -0
- package/dist/tools/{mcp-trace.d.ts → mcp/trace.d.ts} +1 -1
- package/dist/tools/mcp/trace.d.ts.map +1 -0
- package/dist/tools/mount.d.ts +12 -7
- package/dist/tools/mount.d.ts.map +1 -1
- package/dist/tools/native-definition.d.ts +3 -3
- package/dist/tools/native-definition.d.ts.map +1 -1
- package/dist/tools/operations/async-operation-binding-types.d.ts +127 -0
- package/dist/tools/operations/async-operation-binding-types.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-binding.d.ts +28 -0
- package/dist/tools/operations/async-operation-binding.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-canonical-types.d.ts +110 -0
- package/dist/tools/operations/async-operation-canonical-types.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-canonical.d.ts +7 -0
- package/dist/tools/operations/async-operation-canonical.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-contract.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-id.d.ts +23 -0
- package/dist/tools/operations/async-operation-id.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-runtime-tools.d.ts +40 -0
- package/dist/tools/operations/async-operation-runtime-tools.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-runtime-types.d.ts +68 -0
- package/dist/tools/operations/async-operation-runtime-types.d.ts.map +1 -0
- package/dist/tools/operations/async-operation-runtime.d.ts +5 -0
- package/dist/tools/operations/async-operation-runtime.d.ts.map +1 -0
- package/dist/tools/{define-wait-tool.d.ts → operations/define-wait-tool.d.ts} +2 -2
- package/dist/tools/operations/define-wait-tool.d.ts.map +1 -0
- package/dist/tools/operations/mount-wait.d.ts.map +1 -0
- package/dist/tools/operations/wait-core.d.ts.map +1 -0
- package/dist/tools/remote.d.ts +1 -1
- package/dist/tools/remote.d.ts.map +1 -1
- package/dist/tools/runtime-tool.d.ts +11 -4
- package/dist/tools/runtime-tool.d.ts.map +1 -1
- package/dist/tools/schema/coerce.d.ts.map +1 -0
- package/dist/tools/schema/flatten-join.d.ts.map +1 -0
- package/dist/tools/schema/flatten.d.ts.map +1 -0
- package/dist/tools/schema/portable-formats.d.ts.map +1 -0
- package/dist/tools/{presentation.d.ts → schema/presentation.d.ts} +2 -0
- package/dist/tools/schema/presentation.d.ts.map +1 -0
- package/dist/tools/schema/schema.d.ts.map +1 -0
- package/dist/tools/schema/untyped-properties.d.ts.map +1 -0
- package/dist/tools/surface.d.ts +2 -3
- package/dist/tools/surface.d.ts.map +1 -1
- package/dist/tools/toolkit.d.ts +4 -4
- package/dist/tools/toolkit.d.ts.map +1 -1
- package/dist/tools/{define-download-tool.d.ts → transfer/define-download-tool.d.ts} +4 -4
- package/dist/tools/transfer/define-download-tool.d.ts.map +1 -0
- package/dist/tools/{define-upload-tool.d.ts → transfer/define-upload-tool.d.ts} +3 -3
- package/dist/tools/transfer/define-upload-tool.d.ts.map +1 -0
- package/dist/tools/{define-view-file-tool.d.ts → transfer/define-view-file-tool.d.ts} +2 -2
- package/dist/tools/transfer/define-view-file-tool.d.ts.map +1 -0
- package/dist/tools/{download-core.d.ts → transfer/download-core.d.ts} +2 -2
- package/dist/tools/transfer/download-core.d.ts.map +1 -0
- package/dist/tools/{managed-file-error.d.ts → transfer/managed-file-error.d.ts} +1 -1
- package/dist/tools/transfer/managed-file-error.d.ts.map +1 -0
- package/dist/tools/{mount-download.d.ts → transfer/mount-download.d.ts} +1 -1
- package/dist/tools/transfer/mount-download.d.ts.map +1 -0
- package/dist/tools/{mount-upload.d.ts → transfer/mount-upload.d.ts} +1 -1
- package/dist/tools/transfer/mount-upload.d.ts.map +1 -0
- package/dist/tools/transfer/native-result.d.ts.map +1 -0
- package/dist/tools/{upload-core.d.ts → transfer/upload-core.d.ts} +1 -1
- package/dist/tools/transfer/upload-core.d.ts.map +1 -0
- package/dist/tools/transfer/view-file-contract.d.ts.map +1 -0
- package/dist/tools/{view-file.d.ts → transfer/view-file.d.ts} +1 -1
- package/dist/tools/transfer/view-file.d.ts.map +1 -0
- package/dist/tools/transports.d.ts +1 -1
- package/dist/tools/transports.d.ts.map +1 -1
- package/dist/tracking/contract.d.ts +1 -1
- package/dist/tracking/contract.d.ts.map +1 -1
- package/llms/agent-runtime-browser.txt +33 -0
- package/llms/agent-runtime-coding-tools.txt +58 -0
- package/llms/agent-runtime-harness.txt +45 -0
- package/llms/agent-runtime-openrouter.txt +22 -0
- package/llms/agent-runtime-sandbox.txt +159 -0
- package/llms/agent-runtime-sqlite-bun.txt +29 -0
- package/llms/agent-runtime-sqlite-node.txt +26 -0
- package/llms/agent-runtime-testing.txt +30 -0
- package/llms/agent-runtime.2.txt +550 -0
- package/llms/agent-runtime.3.txt +311 -0
- package/llms/agent-runtime.txt +893 -0
- package/llms/application-diagnostic-journal.txt +30 -0
- package/llms/application-grammy.txt +27 -0
- package/llms/application-opentelemetry.txt +27 -0
- package/llms/application-schemas.txt +42 -0
- package/llms/application.2.txt +223 -0
- package/llms/application.txt +1144 -0
- package/llms/cli.txt +977 -0
- package/llms/contract.txt +606 -0
- package/llms/declaration.txt +286 -0
- package/llms/files.txt +30 -0
- package/llms/geo.txt +114 -0
- package/llms/google.txt +27 -0
- package/llms/live.txt +471 -0
- package/llms/node.txt +43 -0
- package/llms/oauth.txt +136 -0
- package/llms/observability.txt +893 -0
- package/llms/primitives.txt +158 -0
- package/llms/react.txt +184 -0
- package/llms/release.txt +176 -0
- package/llms/remote.txt +25 -0
- package/llms/server.2.txt +361 -0
- package/llms/server.3.txt +1031 -0
- package/llms/server.4.txt +905 -0
- package/llms/server.5.txt +250 -0
- package/llms/server.txt +996 -0
- package/llms/stitchkit.2.txt +836 -0
- package/llms/stitchkit.3.txt +147 -0
- package/llms/stitchkit.txt +260 -0
- package/llms/telegram.txt +30 -0
- package/llms/testing.txt +676 -0
- package/llms/tools-connections.txt +45 -0
- package/llms/tools-contract.txt +9 -0
- package/llms/tools-invoker.txt +9 -0
- package/llms/tools.2.txt +838 -0
- package/llms/tools.txt +1004 -0
- package/llms/tracking-server.txt +32 -0
- package/llms/tracking.txt +263 -0
- package/llms/upgrading-0.37.0-0.50.0.txt +947 -0
- package/llms/upgrading-0.53.0-0.67.0.txt +1174 -0
- package/llms/upgrading-0.68.0-0.94.0.txt +1159 -0
- package/llms/upgrading.txt +215 -0
- package/llms-full.txt +214 -75
- package/llms.txt +54 -5
- package/package.json +84 -83
- package/dist/agent-runtime/durability-contract.d.ts.map +0 -1
- package/dist/agent-runtime/durability-json.d.ts.map +0 -1
- package/dist/agent-runtime/durability-ledger.d.ts.map +0 -1
- package/dist/agent-runtime/durability-scheduler.d.ts.map +0 -1
- package/dist/agent-runtime/durability.d.ts.map +0 -1
- package/dist/agent-runtime/store-driver.d.ts.map +0 -1
- package/dist/agent-runtime-browser.d.ts +0 -5
- package/dist/agent-runtime-browser.d.ts.map +0 -1
- package/dist/agent-runtime-coding-tools.d.ts.map +0 -1
- package/dist/agent-runtime-coding-tools.js +0 -23
- package/dist/agent-runtime-harness.d.ts.map +0 -1
- package/dist/agent-runtime-openrouter.d.ts.map +0 -1
- package/dist/agent-runtime-sandbox.d.ts.map +0 -1
- package/dist/agent-runtime-sqlite-bun.d.ts.map +0 -1
- package/dist/agent-runtime-sqlite-node.d.ts.map +0 -1
- package/dist/agent-runtime-testing.d.ts +0 -3
- package/dist/agent-runtime-testing.d.ts.map +0 -1
- package/dist/agent-runtime-testing.js +0 -156
- package/dist/agent-runtime.d.ts.map +0 -1
- package/dist/application/channel.d.ts.map +0 -1
- package/dist/application-diagnostic-journal.d.ts.map +0 -1
- package/dist/application-grammy.d.ts.map +0 -1
- package/dist/application-opentelemetry.d.ts.map +0 -1
- package/dist/application-schemas.d.ts.map +0 -1
- package/dist/application.d.ts.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/contract/index.d.ts +0 -8
- package/dist/contract/index.d.ts.map +0 -1
- package/dist/declaration.d.ts.map +0 -1
- package/dist/files.d.ts.map +0 -1
- package/dist/geo.d.ts.map +0 -1
- package/dist/google.d.ts.map +0 -1
- package/dist/index-0j3vcp5m.js +0 -28
- package/dist/index-a11g2kry.js +0 -866
- package/dist/index-cnyk6te3.js +0 -77
- package/dist/index-fadnxwna.js +0 -1046
- package/dist/index-hrbajay7.js +0 -1196
- package/dist/index-s8cnx02s.js +0 -90
- package/dist/index-v5573g1k.js +0 -142
- package/dist/index-vk3kq9sb.js +0 -3344
- package/dist/index.d.ts.map +0 -1
- package/dist/internal/errors.d.ts.map +0 -1
- package/dist/internal/observability-sink.d.ts.map +0 -1
- package/dist/live.d.ts.map +0 -1
- package/dist/logger.d.ts.map +0 -1
- package/dist/node.d.ts.map +0 -1
- package/dist/oauth.d.ts.map +0 -1
- package/dist/observability/index.d.ts.map +0 -1
- package/dist/primitives.d.ts +0 -3
- package/dist/primitives.d.ts.map +0 -1
- package/dist/react.d.ts.map +0 -1
- package/dist/release.d.ts.map +0 -1
- package/dist/remote.d.ts.map +0 -1
- package/dist/remote.js +0 -88
- package/dist/retained.d.ts.map +0 -1
- package/dist/server/file-state-store.d.ts.map +0 -1
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/request.d.ts.map +0 -1
- package/dist/telegram.d.ts.map +0 -1
- package/dist/testing/agent-store-conformance.d.ts.map +0 -1
- package/dist/testing.d.ts +0 -39
- package/dist/testing.d.ts.map +0 -1
- package/dist/tool-invoker.d.ts.map +0 -1
- package/dist/tool-invoker.js +0 -21
- package/dist/tools/async-operation-contract.d.ts.map +0 -1
- package/dist/tools/async-operation.d.ts +0 -332
- package/dist/tools/async-operation.d.ts.map +0 -1
- package/dist/tools/cli-args.d.ts.map +0 -1
- package/dist/tools/cli-checkpoint.d.ts.map +0 -1
- package/dist/tools/cli-command.d.ts.map +0 -1
- package/dist/tools/cli-format.d.ts.map +0 -1
- package/dist/tools/cli-installer.d.ts.map +0 -1
- package/dist/tools/cli-invoke.d.ts.map +0 -1
- package/dist/tools/cli-manifest.d.ts.map +0 -1
- package/dist/tools/cli-policy.d.ts.map +0 -1
- package/dist/tools/cli-profile.d.ts.map +0 -1
- package/dist/tools/cli-signature.d.ts.map +0 -1
- package/dist/tools/cli-stream.d.ts.map +0 -1
- package/dist/tools/cli-update.d.ts.map +0 -1
- package/dist/tools/cli-view.d.ts.map +0 -1
- package/dist/tools/cli-wait.d.ts.map +0 -1
- package/dist/tools/cli.d.ts.map +0 -1
- package/dist/tools/coerce.d.ts.map +0 -1
- package/dist/tools/connections/index.d.ts.map +0 -1
- package/dist/tools/define-download-tool.d.ts.map +0 -1
- package/dist/tools/define-upload-tool.d.ts.map +0 -1
- package/dist/tools/define-view-file-tool.d.ts.map +0 -1
- package/dist/tools/define-wait-tool.d.ts.map +0 -1
- package/dist/tools/download-core.d.ts.map +0 -1
- package/dist/tools/flatten-join.d.ts.map +0 -1
- package/dist/tools/flatten.d.ts.map +0 -1
- package/dist/tools/json-schema-dialect.d.ts.map +0 -1
- package/dist/tools/json-schema.d.ts.map +0 -1
- package/dist/tools/managed-file-error.d.ts.map +0 -1
- package/dist/tools/mcp-app.d.ts.map +0 -1
- package/dist/tools/mcp-catalog.d.ts.map +0 -1
- package/dist/tools/mcp-handler.d.ts.map +0 -1
- package/dist/tools/mcp-prepare.d.ts.map +0 -1
- package/dist/tools/mcp-progress.d.ts.map +0 -1
- package/dist/tools/mcp-round-policy.d.ts.map +0 -1
- package/dist/tools/mcp-round.d.ts.map +0 -1
- package/dist/tools/mcp-stdio-signals.d.ts.map +0 -1
- package/dist/tools/mcp-stdio.d.ts.map +0 -1
- package/dist/tools/mcp-trace.d.ts.map +0 -1
- package/dist/tools/mcp.d.ts.map +0 -1
- package/dist/tools/mount-download.d.ts.map +0 -1
- package/dist/tools/mount-upload.d.ts.map +0 -1
- package/dist/tools/mount-wait.d.ts.map +0 -1
- package/dist/tools/native-mcp.d.ts +0 -20
- package/dist/tools/native-mcp.d.ts.map +0 -1
- package/dist/tools/native-result.d.ts.map +0 -1
- package/dist/tools/oauth-metadata.d.ts.map +0 -1
- package/dist/tools/oauth-provider.d.ts.map +0 -1
- package/dist/tools/portable-formats.d.ts.map +0 -1
- package/dist/tools/presentation.d.ts.map +0 -1
- package/dist/tools/schema.d.ts.map +0 -1
- package/dist/tools/untyped-properties.d.ts.map +0 -1
- package/dist/tools/upload-core.d.ts.map +0 -1
- package/dist/tools/view-file-contract.d.ts.map +0 -1
- package/dist/tools/view-file.d.ts.map +0 -1
- package/dist/tools/wait-core.d.ts.map +0 -1
- package/dist/tools-contract.d.ts.map +0 -1
- package/dist/tools.d.ts +0 -50
- package/dist/tools.d.ts.map +0 -1
- package/dist/tracking-server.d.ts.map +0 -1
- package/dist/tracking.d.ts.map +0 -1
- package/dist/upgrade-cli.d.ts.map +0 -1
- package/dist/{retained.d.ts → browser/retained.d.ts} +0 -0
- package/dist/{declaration.d.ts → declaration/declaration.d.ts} +0 -0
- package/dist/{upgrade-cli.d.ts → entrypoints/bin/upgrade-cli.d.ts} +0 -0
- package/dist/{tracking-server.js → entrypoints/tracking/server.js} +0 -0
- package/dist/{google.d.ts → google/google.d.ts} +0 -0
- package/dist/{index-pq95qkbf.js → index-5jm36zv1.js} +0 -0
- package/dist/{index-actkayzn.js → index-70yj2ebz.js} +3 -3
- /package/dist/{application → internal}/channel.d.ts +0 -0
- /package/dist/{logger.d.ts → internal/logger.d.ts} +0 -0
- /package/dist/{server → internal}/request.d.ts +0 -0
- /package/dist/{tools → json-schema}/json-schema.d.ts +0 -0
- /package/dist/tools/{async-operation-contract.d.ts → operations/async-operation-contract.d.ts} +0 -0
- /package/dist/tools/{mount-wait.d.ts → operations/mount-wait.d.ts} +0 -0
- /package/dist/tools/{wait-core.d.ts → operations/wait-core.d.ts} +0 -0
- /package/dist/tools/{coerce.d.ts → schema/coerce.d.ts} +0 -0
- /package/dist/tools/{flatten-join.d.ts → schema/flatten-join.d.ts} +0 -0
- /package/dist/tools/{flatten.d.ts → schema/flatten.d.ts} +0 -0
- /package/dist/tools/{portable-formats.d.ts → schema/portable-formats.d.ts} +0 -0
- /package/dist/tools/{schema.d.ts → schema/schema.d.ts} +0 -0
- /package/dist/tools/{untyped-properties.d.ts → schema/untyped-properties.d.ts} +0 -0
- /package/dist/tools/{native-result.d.ts → transfer/native-result.d.ts} +0 -0
- /package/dist/tools/{view-file-contract.d.ts → transfer/view-file-contract.d.ts} +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ManagedFileError
|
|
3
|
-
} from "
|
|
3
|
+
} from "../index-70yj2ebz.js";
|
|
4
4
|
import {
|
|
5
5
|
signJwt,
|
|
6
6
|
verifyPkce
|
|
7
|
-
} from "
|
|
8
|
-
import"
|
|
7
|
+
} from "../index-e2z3ahw9.js";
|
|
8
|
+
import"../index-hr4tzbwq.js";
|
|
9
9
|
import {
|
|
10
10
|
DEFAULT_CORS_ALLOW_HEADERS,
|
|
11
11
|
DEFAULT_PROCESS_SIGNALS,
|
|
@@ -14,20 +14,31 @@ import {
|
|
|
14
14
|
guardSignalCallback,
|
|
15
15
|
mediaTypeEssence,
|
|
16
16
|
reportSignalError
|
|
17
|
-
} from "
|
|
17
|
+
} from "../index-xnj9bgxe.js";
|
|
18
|
+
import"../index-kvj7f4hq.js";
|
|
18
19
|
import {
|
|
19
20
|
redact
|
|
20
|
-
} from "
|
|
21
|
+
} from "../index-yw4fzmdz.js";
|
|
21
22
|
import {
|
|
22
23
|
buildToolManifest,
|
|
23
24
|
describeToolCatalog,
|
|
24
25
|
mountAgent
|
|
25
|
-
} from "
|
|
26
|
+
} from "../index-dcpdvjsc.js";
|
|
26
27
|
import {
|
|
27
28
|
AgentToolError,
|
|
28
29
|
createLocalStepDurability,
|
|
29
30
|
isAgentToolError
|
|
30
|
-
} from "
|
|
31
|
+
} from "../index-cq4qfycf.js";
|
|
32
|
+
import"../index-rxfy4cq7.js";
|
|
33
|
+
import {
|
|
34
|
+
ALL_TRANSPORTS,
|
|
35
|
+
TOOL_TRANSPORTS,
|
|
36
|
+
defineContract
|
|
37
|
+
} from "../index-gyr48nzg.js";
|
|
38
|
+
import {
|
|
39
|
+
ManagedFilePathSchema,
|
|
40
|
+
ManagedFileRefSchema
|
|
41
|
+
} from "../index-6k1937bx.js";
|
|
31
42
|
import {
|
|
32
43
|
AsyncOperationCancelResultSchema,
|
|
33
44
|
McpAnnotationsSchema,
|
|
@@ -36,69 +47,52 @@ import {
|
|
|
36
47
|
ViewFileInputSchema,
|
|
37
48
|
ViewFileOutputSchema,
|
|
38
49
|
createAsyncOperationSnapshotSchema
|
|
39
|
-
} from "
|
|
50
|
+
} from "../index-m9zn6cpq.js";
|
|
40
51
|
import {
|
|
41
52
|
createToolInvoker
|
|
42
|
-
} from "
|
|
53
|
+
} from "../index-rewkc2hj.js";
|
|
43
54
|
import {
|
|
44
55
|
WaitTimeoutError,
|
|
45
56
|
createCli,
|
|
46
|
-
defineCliCommand,
|
|
47
57
|
fetchGuarded,
|
|
48
58
|
fetchPinnedDocument,
|
|
49
59
|
readCapped,
|
|
50
60
|
runWaitOperation
|
|
51
|
-
} from "
|
|
52
|
-
import
|
|
53
|
-
compareCodepoints,
|
|
54
|
-
serializeCanonicalJson
|
|
55
|
-
} from "./index-0j3vcp5m.js";
|
|
56
|
-
import"./index-sbdmyz75.js";
|
|
61
|
+
} from "../index-n6hxg8as.js";
|
|
62
|
+
import"../index-sbdmyz75.js";
|
|
57
63
|
import {
|
|
58
64
|
collectToolSurface
|
|
59
|
-
} from "
|
|
65
|
+
} from "../index-vcvkq30w.js";
|
|
60
66
|
import {
|
|
61
67
|
argumentsDigest
|
|
62
|
-
} from "
|
|
68
|
+
} from "../index-0b1ess5e.js";
|
|
63
69
|
import {
|
|
64
70
|
collectTools,
|
|
65
71
|
createToolRunner,
|
|
66
72
|
formatToolError
|
|
67
|
-
} from "
|
|
73
|
+
} from "../index-2cr1cqhn.js";
|
|
68
74
|
import {
|
|
69
75
|
ToolExecutionControlError,
|
|
70
|
-
executeToolMethod,
|
|
71
76
|
isToolExecutionControlError,
|
|
72
77
|
parseToolCallArguments,
|
|
73
78
|
toolResultFromError
|
|
74
|
-
} from "
|
|
79
|
+
} from "../index-j05z5gn4.js";
|
|
75
80
|
import {
|
|
76
81
|
getRequestContext,
|
|
77
82
|
getTraceId,
|
|
78
83
|
runWithRequestContext
|
|
79
|
-
} from "
|
|
80
|
-
import"
|
|
81
|
-
import {
|
|
82
|
-
normalizeError
|
|
83
|
-
} from "./index-e0znv5gn.js";
|
|
84
|
-
import {
|
|
85
|
-
defineContract
|
|
86
|
-
} from "./index-0a349c33.js";
|
|
87
|
-
import"./index-rxfy4cq7.js";
|
|
88
|
-
import {
|
|
89
|
-
ManagedFilePathSchema,
|
|
90
|
-
ManagedFileRefSchema
|
|
91
|
-
} from "./index-6k1937bx.js";
|
|
84
|
+
} from "../index-zfrhfn82.js";
|
|
85
|
+
import"../index-35mh5bhj.js";
|
|
92
86
|
import {
|
|
93
87
|
resolvePropagationContext
|
|
94
|
-
} from "
|
|
88
|
+
} from "../index-zcgf3gqf.js";
|
|
95
89
|
import {
|
|
96
90
|
coerceJsonArgs
|
|
97
|
-
} from "
|
|
91
|
+
} from "../index-1zbbzgfh.js";
|
|
98
92
|
import {
|
|
99
93
|
createRuntimeToolFactory,
|
|
100
94
|
defineRuntimeTool
|
|
101
|
-
} from "
|
|
95
|
+
} from "../index-v38qyerb.js";
|
|
102
96
|
import {
|
|
103
97
|
PORTABLE_JSON_SCHEMA_FORMATS,
|
|
104
98
|
assertToolName,
|
|
@@ -109,778 +103,714 @@ import {
|
|
|
109
103
|
prepareProjectedMcpTools,
|
|
110
104
|
presentationMetadata,
|
|
111
105
|
staticInputRounds,
|
|
106
|
+
toolSurfaceOutputSchema,
|
|
112
107
|
validateMcpRoundPolicy,
|
|
113
108
|
validateResolvedInputRounds
|
|
114
|
-
} from "
|
|
109
|
+
} from "../index-dydjcp8z.js";
|
|
115
110
|
import {
|
|
116
111
|
toJsonSchema
|
|
117
|
-
} from "
|
|
112
|
+
} from "../index-eqqvgdb3.js";
|
|
113
|
+
import {
|
|
114
|
+
normalizeError
|
|
115
|
+
} from "../index-znfs20ss.js";
|
|
116
|
+
import"../index-p5r7qpdd.js";
|
|
118
117
|
import {
|
|
119
118
|
AppError,
|
|
120
119
|
STITCH_ERROR_STATUS
|
|
121
|
-
} from "
|
|
122
|
-
import
|
|
120
|
+
} from "../index-2vtcmrj9.js";
|
|
121
|
+
import {
|
|
122
|
+
compareCodeUnits,
|
|
123
|
+
serializeCanonicalJson
|
|
124
|
+
} from "../index-k5hsfc3j.js";
|
|
125
|
+
import"../index-kzfs85xp.js";
|
|
123
126
|
import {
|
|
124
127
|
isRecord
|
|
125
|
-
} from "
|
|
126
|
-
// src/
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// src/tools/define-wait-tool.ts
|
|
134
|
-
function defineWaitTool(config) {
|
|
135
|
-
const renderedMcp = config.render ? {
|
|
136
|
-
mcp: (state) => {
|
|
137
|
-
const rendered = config.render?.(state, false);
|
|
138
|
-
return {
|
|
139
|
-
content: [
|
|
140
|
-
{
|
|
141
|
-
type: "text",
|
|
142
|
-
text: rendered?.text ?? JSON.stringify(state, null, 2)
|
|
143
|
-
}
|
|
144
|
-
]
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
} : undefined;
|
|
148
|
-
const present = renderedMcp || config.present ? { ...renderedMcp, ...config.present } : undefined;
|
|
149
|
-
return defineRuntimeTool({
|
|
150
|
-
name: config.name ?? "wait",
|
|
151
|
-
description: config.description,
|
|
152
|
-
identity: managedNativeIdentity(config.identity, "GET"),
|
|
153
|
-
input: config.input,
|
|
154
|
-
output: config.state,
|
|
155
|
-
transports: config.transports,
|
|
156
|
-
annotations: config.annotations,
|
|
157
|
-
present,
|
|
158
|
-
handler: async (context) => {
|
|
159
|
-
let result;
|
|
160
|
-
try {
|
|
161
|
-
result = await runWaitOperation({
|
|
162
|
-
input: context.input,
|
|
163
|
-
poll: async (input, signal) => config.state.parse(await config.poll(input, { ...context, signal })),
|
|
164
|
-
done: config.done,
|
|
165
|
-
backoff: config.backoff,
|
|
166
|
-
timeoutSec: config.timeoutFromInput?.(context.input) ?? config.defaultTimeout,
|
|
167
|
-
signal: context.signal
|
|
168
|
-
});
|
|
169
|
-
} catch (error) {
|
|
170
|
-
if (error instanceof WaitTimeoutError) {
|
|
171
|
-
throw new AppError("WAIT_TIMEOUT", "Wait timed out before the first snapshot", 408);
|
|
172
|
-
}
|
|
173
|
-
throw error;
|
|
174
|
-
}
|
|
175
|
-
const rendered = config.render?.(result.state, result.timedOut);
|
|
176
|
-
if (result.timedOut) {
|
|
177
|
-
throw new AppError("WAIT_TIMEOUT", rendered?.text ?? "Wait timed out before the operation completed", 408);
|
|
178
|
-
}
|
|
179
|
-
if (rendered?.isError) {
|
|
180
|
-
throw new AppError("WAIT_FAILED", rendered.text, 409);
|
|
181
|
-
}
|
|
182
|
-
return result.state;
|
|
183
|
-
}
|
|
184
|
-
});
|
|
128
|
+
} from "../index-hffjpmq9.js";
|
|
129
|
+
// src/server/oauth/metadata.ts
|
|
130
|
+
var PROTECTED_RESOURCE_PATH = "/.well-known/oauth-protected-resource";
|
|
131
|
+
function metadataPath(resource) {
|
|
132
|
+
const { pathname } = new URL(resource);
|
|
133
|
+
return pathname === "/" ? PROTECTED_RESOURCE_PATH : `${PROTECTED_RESOURCE_PATH}${pathname}`;
|
|
185
134
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
function terminal(snapshot) {
|
|
189
|
-
if (typeof snapshot !== "object" || snapshot === null || !("phase" in snapshot)) {
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
return snapshot.phase === "succeeded" || snapshot.phase === "failed" || snapshot.phase === "cancelled";
|
|
135
|
+
function protectedResourceMetadataUrl(resource) {
|
|
136
|
+
return `${new URL(resource).origin}${metadataPath(resource)}`;
|
|
193
137
|
}
|
|
194
|
-
function
|
|
195
|
-
return
|
|
138
|
+
function wwwAuthenticateHeader(resource) {
|
|
139
|
+
return `Bearer resource_metadata="${protectedResourceMetadataUrl(resource)}"`;
|
|
196
140
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
name: capabilityName(capability),
|
|
209
|
-
description: description(capability),
|
|
210
|
-
transports: config.transports?.[capability],
|
|
211
|
-
annotations: config.annotations?.[capability] ?? (capability === "start" || capability === "cancel" ? { destructiveHint: true } : undefined)
|
|
212
|
-
});
|
|
213
|
-
const inspect = async (capability, context) => {
|
|
214
|
-
await config.authorize(context.input, capability, context);
|
|
215
|
-
const state = config.state.parse(await config.inspect(context.input, context));
|
|
216
|
-
const snapshot = config.snapshot.parse(await config.classify(state, context));
|
|
217
|
-
return { state, snapshot };
|
|
218
|
-
};
|
|
219
|
-
const start = defineRuntimeTool({
|
|
220
|
-
...common("start"),
|
|
221
|
-
identity: identity("start", "POST"),
|
|
222
|
-
input: config.startInput,
|
|
223
|
-
output: config.id,
|
|
224
|
-
handler: (context) => config.start(context.input, context)
|
|
225
|
-
});
|
|
226
|
-
const status = defineRuntimeTool({
|
|
227
|
-
...common("status"),
|
|
228
|
-
identity: identity("status", "GET"),
|
|
229
|
-
input: config.id,
|
|
230
|
-
output: config.snapshot,
|
|
231
|
-
handler: async (context) => (await inspect("status", context)).snapshot
|
|
232
|
-
});
|
|
233
|
-
const wait = defineWaitTool({
|
|
234
|
-
...common("wait"),
|
|
235
|
-
identity: identity("wait", "GET"),
|
|
236
|
-
input: config.id,
|
|
237
|
-
state: config.snapshot,
|
|
238
|
-
poll: async (_id, context) => (await inspect("wait", context)).snapshot,
|
|
239
|
-
done: terminal,
|
|
240
|
-
backoff: config.backoff,
|
|
241
|
-
defaultTimeout: config.defaultTimeout,
|
|
242
|
-
timeoutFromInput: config.timeoutFromId
|
|
141
|
+
var PUBLIC_CORS = {
|
|
142
|
+
"Access-Control-Allow-Origin": "*",
|
|
143
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
144
|
+
"Access-Control-Allow-Headers": DEFAULT_CORS_ALLOW_HEADERS
|
|
145
|
+
};
|
|
146
|
+
function oauthProtectedResourceRoute(config) {
|
|
147
|
+
const body = JSON.stringify({
|
|
148
|
+
resource: config.resource,
|
|
149
|
+
authorization_servers: config.authorizationServers,
|
|
150
|
+
...config.scopesSupported && { scopes_supported: config.scopesSupported },
|
|
151
|
+
bearer_methods_supported: ["header"]
|
|
243
152
|
});
|
|
244
|
-
const definitions = { start, status, wait };
|
|
245
|
-
const names = new Set;
|
|
246
|
-
if (config.cancel) {
|
|
247
|
-
const capability = config.cancel;
|
|
248
|
-
definitions.cancel = defineRuntimeTool({
|
|
249
|
-
...common("cancel"),
|
|
250
|
-
identity: identity("cancel", "DELETE"),
|
|
251
|
-
input: config.id,
|
|
252
|
-
output: AsyncOperationCancelResultSchema,
|
|
253
|
-
handler: async (context) => {
|
|
254
|
-
const inspected = await inspect("cancel", context);
|
|
255
|
-
return capability.handler(inspected.state, context);
|
|
256
|
-
}
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
if (config.result) {
|
|
260
|
-
const capability = config.result;
|
|
261
|
-
definitions.result = defineRuntimeTool({
|
|
262
|
-
...common("result"),
|
|
263
|
-
identity: identity("result", "GET"),
|
|
264
|
-
input: config.id,
|
|
265
|
-
output: capability.output,
|
|
266
|
-
handler: async (context) => {
|
|
267
|
-
const inspected = await inspect("result", context);
|
|
268
|
-
if (!succeeded(inspected.snapshot)) {
|
|
269
|
-
throw new AppError("OPERATION_NOT_SUCCEEDED", "Operation result is not available", 409);
|
|
270
|
-
}
|
|
271
|
-
return capability.handler(inspected.state, context);
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
if (config.artifacts) {
|
|
276
|
-
const capability = config.artifacts;
|
|
277
|
-
definitions.artifacts = defineRuntimeTool({
|
|
278
|
-
...common("artifacts"),
|
|
279
|
-
identity: identity("artifacts", "GET"),
|
|
280
|
-
input: config.id,
|
|
281
|
-
output: capability.output,
|
|
282
|
-
handler: async (context) => {
|
|
283
|
-
const inspected = await inspect("artifacts", context);
|
|
284
|
-
if (!succeeded(inspected.snapshot)) {
|
|
285
|
-
throw new AppError("OPERATION_NOT_SUCCEEDED", "Operation artifacts are not available", 409);
|
|
286
|
-
}
|
|
287
|
-
return capability.handler(inspected.state, context);
|
|
288
|
-
}
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
for (const definition of Object.values(definitions)) {
|
|
292
|
-
assertUniqueToolName(definition.name, names.has(definition.name), "in-process tool name");
|
|
293
|
-
names.add(definition.name);
|
|
294
|
-
}
|
|
295
153
|
return {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
154
|
+
method: "ALL",
|
|
155
|
+
path: metadataPath(config.resource),
|
|
156
|
+
handler: (req) => {
|
|
157
|
+
if (req.method === "OPTIONS")
|
|
158
|
+
return new Response(null, { status: 204, headers: PUBLIC_CORS });
|
|
159
|
+
if (req.method !== "GET")
|
|
160
|
+
return new Response("Method Not Allowed", { status: 405 });
|
|
161
|
+
return new Response(body, {
|
|
162
|
+
status: 200,
|
|
163
|
+
headers: { "Content-Type": "application/json", ...PUBLIC_CORS }
|
|
164
|
+
});
|
|
302
165
|
}
|
|
303
166
|
};
|
|
304
167
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
return
|
|
168
|
+
// src/server/oauth/provider.ts
|
|
169
|
+
import { randomUUID } from "node:crypto";
|
|
170
|
+
import { z } from "zod";
|
|
171
|
+
function isPlainDisplayCharacter(character) {
|
|
172
|
+
const codePoint = character.codePointAt(0);
|
|
173
|
+
return codePoint !== undefined && codePoint >= 32 && codePoint !== 127 && character !== "<" && character !== ">";
|
|
311
174
|
}
|
|
312
|
-
var
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
"
|
|
319
|
-
"
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
175
|
+
var CimdClientMetadataSchema = z.object({
|
|
176
|
+
client_id: z.url(),
|
|
177
|
+
redirect_uris: z.array(z.url()).min(1),
|
|
178
|
+
client_name: z.string().trim().min(1).max(200).refine((value) => Array.from(value).every(isPlainDisplayCharacter), {
|
|
179
|
+
message: "client_name must be plain display text"
|
|
180
|
+
}),
|
|
181
|
+
token_endpoint_auth_method: z.literal("none"),
|
|
182
|
+
application_type: z.enum(["native", "web"]).optional()
|
|
183
|
+
});
|
|
184
|
+
function createSecureClientMetadataFetcher(policy = {}) {
|
|
185
|
+
assertPositiveInteger("cimd.maxBytes", policy.maxBytes ?? DEFAULT_CIMD_MAX_BYTES);
|
|
186
|
+
assertPositiveNumber("cimd.timeoutMs", policy.timeoutMs ?? DEFAULT_CIMD_TIMEOUT_MS);
|
|
187
|
+
assertNonNegativeInteger("cimd.maxRedirects", policy.maxRedirects ?? 3);
|
|
188
|
+
return {
|
|
189
|
+
fetch: (url, headers) => fetchPinnedDocument(url, {
|
|
190
|
+
maxBytes: policy.maxBytes ?? DEFAULT_CIMD_MAX_BYTES,
|
|
191
|
+
timeoutMs: policy.timeoutMs ?? DEFAULT_CIMD_TIMEOUT_MS,
|
|
192
|
+
maxRedirects: policy.maxRedirects ?? 3,
|
|
193
|
+
headers,
|
|
194
|
+
requireHttps: true
|
|
195
|
+
})
|
|
196
|
+
};
|
|
324
197
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
if (Array.isArray(value)) {
|
|
342
|
-
pending.push(...value);
|
|
343
|
-
continue;
|
|
344
|
-
}
|
|
345
|
-
if (isRecord(value))
|
|
346
|
-
pending.push(...Object.values(value));
|
|
198
|
+
var PUBLIC_CORS2 = {
|
|
199
|
+
"Access-Control-Allow-Origin": "*",
|
|
200
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
201
|
+
"Access-Control-Allow-Headers": DEFAULT_CORS_ALLOW_HEADERS
|
|
202
|
+
};
|
|
203
|
+
var AS_METADATA_PATH = "/.well-known/oauth-authorization-server";
|
|
204
|
+
var AUTH_CODE_TTL_MS = 60000;
|
|
205
|
+
var DEFAULT_CIMD_MAX_BYTES = 64 * 1024;
|
|
206
|
+
var DEFAULT_CIMD_TIMEOUT_MS = 5000;
|
|
207
|
+
function assertPositiveInteger(name, value) {
|
|
208
|
+
if (!Number.isInteger(value) || value < 1)
|
|
209
|
+
throw new Error(`${name} must be a positive integer`);
|
|
210
|
+
}
|
|
211
|
+
function assertNonNegativeInteger(name, value) {
|
|
212
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
213
|
+
throw new Error(`${name} must be a non-negative integer`);
|
|
347
214
|
}
|
|
348
215
|
}
|
|
349
|
-
function
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
216
|
+
function assertPositiveNumber(name, value) {
|
|
217
|
+
if (!Number.isFinite(value) || value <= 0)
|
|
218
|
+
throw new Error(`${name} must be positive`);
|
|
219
|
+
}
|
|
220
|
+
function absoluteClientUrl(clientId) {
|
|
221
|
+
try {
|
|
222
|
+
return new URL(clientId);
|
|
223
|
+
} catch {
|
|
224
|
+
return null;
|
|
355
225
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
};
|
|
370
|
-
const startOutput = config.startOutput ?? config.id;
|
|
371
|
-
const endpoints = {
|
|
372
|
-
start: endpoint("start", "POST", "/", config.startInput, startOutput),
|
|
373
|
-
status: endpoint("status", "POST", "/status", config.id, config.snapshot),
|
|
374
|
-
wait: endpoint("wait", "POST", "/wait", config.id, config.snapshot)
|
|
375
|
-
};
|
|
376
|
-
const capabilities = {
|
|
377
|
-
start: "start",
|
|
378
|
-
status: "status",
|
|
379
|
-
wait: "wait"
|
|
380
|
-
};
|
|
381
|
-
const schemas = {
|
|
382
|
-
startInput: config.startInput,
|
|
383
|
-
startOutput,
|
|
384
|
-
id: config.id,
|
|
385
|
-
snapshot: config.snapshot,
|
|
386
|
-
cancelResult: AsyncOperationCancelResultSchema
|
|
387
|
-
};
|
|
388
|
-
const inputFor = {
|
|
389
|
-
status: (id) => adapterResult("status", "input", config.id, id),
|
|
390
|
-
wait: (id) => adapterResult("wait", "input", config.id, id)
|
|
391
|
-
};
|
|
392
|
-
if (config.cancel) {
|
|
393
|
-
endpoints.cancel = endpoint("cancel", "POST", "/cancel", config.id, AsyncOperationCancelResultSchema);
|
|
394
|
-
capabilities.cancel = "cancel";
|
|
395
|
-
schemas.cancel = AsyncOperationCancelResultSchema;
|
|
396
|
-
inputFor.cancel = (id) => adapterResult("cancel", "input", config.id, id);
|
|
397
|
-
}
|
|
398
|
-
if (config.result) {
|
|
399
|
-
endpoints.result = endpoint("result", "POST", "/result", config.id, config.result);
|
|
400
|
-
capabilities.result = "result";
|
|
401
|
-
schemas.result = config.result;
|
|
402
|
-
inputFor.result = (id) => adapterResult("result", "input", config.id, id);
|
|
403
|
-
}
|
|
404
|
-
if (config.artifacts) {
|
|
405
|
-
endpoints.artifacts = endpoint("artifacts", "POST", "/artifacts", config.id, config.artifacts);
|
|
406
|
-
capabilities.artifacts = "artifacts";
|
|
407
|
-
schemas.artifacts = config.artifacts;
|
|
408
|
-
inputFor.artifacts = (id) => adapterResult("artifacts", "input", config.id, id);
|
|
226
|
+
}
|
|
227
|
+
function assertCimdClientId(clientId) {
|
|
228
|
+
const url = new URL(clientId);
|
|
229
|
+
if (url.protocol !== "https:")
|
|
230
|
+
throw new Error("CIMD client_id must use https");
|
|
231
|
+
if (url.username || url.password)
|
|
232
|
+
throw new Error("CIMD client_id cannot contain credentials");
|
|
233
|
+
if (url.hash)
|
|
234
|
+
throw new Error("CIMD client_id cannot contain a fragment");
|
|
235
|
+
if (url.search)
|
|
236
|
+
throw new Error("CIMD client_id cannot contain a query");
|
|
237
|
+
if (url.toString() !== clientId) {
|
|
238
|
+
throw new Error("CIMD client_id must be an exact canonical URL");
|
|
409
239
|
}
|
|
410
|
-
|
|
411
|
-
const applicationStartOutput = config.startOutput;
|
|
412
|
-
const applicationIdFromStart = config.idFromStart;
|
|
413
|
-
const idFromStart = applicationStartOutput && applicationIdFromStart ? (output) => adapterResult("start", "id", config.id, applicationIdFromStart(output)) : (output) => adapterResult("start", "id", config.id, output);
|
|
414
|
-
return {
|
|
415
|
-
contract,
|
|
416
|
-
capabilities,
|
|
417
|
-
schemas,
|
|
418
|
-
adapters: { idFromStart, inputFor }
|
|
419
|
-
};
|
|
240
|
+
return url;
|
|
420
241
|
}
|
|
421
|
-
function
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
const
|
|
430
|
-
const
|
|
431
|
-
if (
|
|
432
|
-
|
|
242
|
+
function parseAgeSeconds(headers) {
|
|
243
|
+
const raw = headers.get("age");
|
|
244
|
+
if (raw === null)
|
|
245
|
+
return 0;
|
|
246
|
+
const first = raw.split(",")[0]?.trim() ?? "";
|
|
247
|
+
return /^\d+$/.test(first) ? Number(first) : 0;
|
|
248
|
+
}
|
|
249
|
+
function responseFreshness(headers, policy, now) {
|
|
250
|
+
const maxTtl = policy.maxTtlMs ?? 3600000;
|
|
251
|
+
const cacheControl = headers.get("cache-control") ?? "";
|
|
252
|
+
if (/(?:^|,)\s*no-store\s*(?:,|$)/i.test(cacheControl)) {
|
|
253
|
+
return { freshnessMs: 0, store: false };
|
|
433
254
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
if (wait.output !== statusOutput) {
|
|
437
|
-
throw new Error('Contract async operation capability "wait" output must reuse the same schema instance as the status output');
|
|
255
|
+
if (/(?:^|,)\s*no-cache\s*(?:,|$)/i.test(cacheControl)) {
|
|
256
|
+
return { freshnessMs: 0, store: true };
|
|
438
257
|
}
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
const capability = status.input !== startOutput ? "status" : "wait";
|
|
448
|
-
throw new Error(`Contract async operation capability "${capability}" input must reuse the same schema instance as the start output`);
|
|
449
|
-
}
|
|
450
|
-
const inputFor2 = {
|
|
451
|
-
status: (id) => adapterResult("status", "input", startOutput, id),
|
|
452
|
-
wait: (id) => adapterResult("wait", "input", startOutput, id)
|
|
258
|
+
const maxAge = cacheControl.match(/(?:^|,)\s*max-age=([^,\s]*)/i)?.[1];
|
|
259
|
+
if (maxAge !== undefined) {
|
|
260
|
+
if (!/^\d+$/.test(maxAge))
|
|
261
|
+
return { freshnessMs: 0, store: true };
|
|
262
|
+
const age = parseAgeSeconds(headers);
|
|
263
|
+
return {
|
|
264
|
+
freshnessMs: Math.max(0, Math.min(maxTtl, (Number(maxAge) - age) * 1000)),
|
|
265
|
+
store: true
|
|
453
266
|
};
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
267
|
+
}
|
|
268
|
+
const expires = headers.get("expires");
|
|
269
|
+
if (expires !== null) {
|
|
270
|
+
const expiresAt = Date.parse(expires);
|
|
271
|
+
if (!Number.isFinite(expiresAt))
|
|
272
|
+
return { freshnessMs: 0, store: true };
|
|
273
|
+
const dateAt = Date.parse(headers.get("date") ?? "");
|
|
274
|
+
const ageMs = parseAgeSeconds(headers) * 1000;
|
|
275
|
+
const lifetime = Number.isFinite(dateAt) ? expiresAt - dateAt : expiresAt - now;
|
|
463
276
|
return {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
capabilities: config.capabilities,
|
|
467
|
-
schemas: { id: startOutput, snapshot: statusOutput },
|
|
468
|
-
adapters: {
|
|
469
|
-
idFromStart: (output) => adapterResult("start", "id", startOutput, output),
|
|
470
|
-
inputFor: inputFor2
|
|
471
|
-
}
|
|
277
|
+
freshnessMs: Math.max(0, Math.min(maxTtl, lifetime - ageMs)),
|
|
278
|
+
store: true
|
|
472
279
|
};
|
|
473
280
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
281
|
+
return { freshnessMs: Math.min(maxTtl, policy.defaultTtlMs ?? 300000), store: true };
|
|
282
|
+
}
|
|
283
|
+
function safeClientName(value) {
|
|
284
|
+
if (value === undefined)
|
|
285
|
+
return;
|
|
286
|
+
const safe = Array.from(value).filter(isPlainDisplayCharacter).join("").trim().slice(0, 200);
|
|
287
|
+
return safe || undefined;
|
|
288
|
+
}
|
|
289
|
+
function parseScopes(value) {
|
|
290
|
+
if (!value)
|
|
291
|
+
return [];
|
|
292
|
+
return [...new Set(value.split(/\s+/u).filter(Boolean))];
|
|
293
|
+
}
|
|
294
|
+
function toRegisteredClient(metadata) {
|
|
295
|
+
return {
|
|
296
|
+
clientId: metadata.client_id,
|
|
297
|
+
redirectUris: metadata.redirect_uris,
|
|
298
|
+
...metadata.client_name !== undefined && { clientName: metadata.client_name },
|
|
299
|
+
...metadata.application_type !== undefined && {
|
|
300
|
+
applicationType: metadata.application_type
|
|
480
301
|
}
|
|
481
|
-
return (id) => adapterResult(capability, "input", input, builder(id));
|
|
482
302
|
};
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
303
|
+
}
|
|
304
|
+
function isJsonContentType(value) {
|
|
305
|
+
const mediaType = value.split(";", 1)[0]?.trim().toLowerCase();
|
|
306
|
+
return mediaType === "application/json" || mediaType?.endsWith("+json") === true;
|
|
307
|
+
}
|
|
308
|
+
function createCimdResolver(options) {
|
|
309
|
+
const policy = options.cache ?? {};
|
|
310
|
+
const now = options.now ?? Date.now;
|
|
311
|
+
const positives = new Map;
|
|
312
|
+
const negatives = new Map;
|
|
313
|
+
const inflight = new Map;
|
|
314
|
+
const maxEntries = policy.maxEntries ?? 256;
|
|
315
|
+
const maxResolutions = policy.maxResolutions ?? 120;
|
|
316
|
+
const maxResolutionsPerClient = policy.maxResolutionsPerClient ?? 10;
|
|
317
|
+
const resolutionWindowMs = policy.resolutionWindowMs ?? 60000;
|
|
318
|
+
let resolutionWindowStartedAt = now();
|
|
319
|
+
let resolutionsInWindow = 0;
|
|
320
|
+
const clientWindows = new Map;
|
|
321
|
+
const CLIENT_WINDOW_CAP = 4096;
|
|
322
|
+
assertPositiveInteger("cimd.cache.maxEntries", maxEntries);
|
|
323
|
+
assertPositiveInteger("cimd.cache.maxResolutions", maxResolutions);
|
|
324
|
+
assertPositiveInteger("cimd.cache.maxResolutionsPerClient", maxResolutionsPerClient);
|
|
325
|
+
assertPositiveInteger("cimd.cache.resolutionWindowMs", resolutionWindowMs);
|
|
326
|
+
assertNonNegativeInteger("cimd.cache.defaultTtlMs", policy.defaultTtlMs ?? 300000);
|
|
327
|
+
assertNonNegativeInteger("cimd.cache.maxTtlMs", policy.maxTtlMs ?? 3600000);
|
|
328
|
+
assertNonNegativeInteger("cimd.cache.negativeTtlMs", policy.negativeTtlMs ?? 1e4);
|
|
329
|
+
const emit = (event) => {
|
|
330
|
+
try {
|
|
331
|
+
options.onCacheEvent?.(event);
|
|
332
|
+
} catch {}
|
|
486
333
|
};
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
334
|
+
const readCache = (key) => positives.get(key) ?? negatives.get(key);
|
|
335
|
+
const trimOldest = (pool) => {
|
|
336
|
+
while (pool.size > maxEntries) {
|
|
337
|
+
const oldest = pool.keys().next().value;
|
|
338
|
+
if (oldest === undefined)
|
|
339
|
+
break;
|
|
340
|
+
pool.delete(oldest);
|
|
492
341
|
}
|
|
493
|
-
inputFor[capability] = adaptedInput(capability, key, builder);
|
|
494
342
|
};
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
adapters: {
|
|
507
|
-
idFromStart: (output) => adapterResult("start", "id", idSchema, adapters.idFromStart(output)),
|
|
508
|
-
inputFor
|
|
343
|
+
const touch = (key, value) => {
|
|
344
|
+
if (value.ok) {
|
|
345
|
+
negatives.delete(key);
|
|
346
|
+
positives.delete(key);
|
|
347
|
+
positives.set(key, value);
|
|
348
|
+
trimOldest(positives);
|
|
349
|
+
} else {
|
|
350
|
+
positives.delete(key);
|
|
351
|
+
negatives.delete(key);
|
|
352
|
+
negatives.set(key, value);
|
|
353
|
+
trimOldest(negatives);
|
|
509
354
|
}
|
|
510
355
|
};
|
|
356
|
+
const dropCache = (key) => {
|
|
357
|
+
positives.delete(key);
|
|
358
|
+
negatives.delete(key);
|
|
359
|
+
};
|
|
360
|
+
const resolveOne = async (clientId) => {
|
|
361
|
+
const url = assertCimdClientId(clientId);
|
|
362
|
+
const cached = readCache(clientId);
|
|
363
|
+
if (cached && cached.expiresAt > now()) {
|
|
364
|
+
touch(clientId, cached);
|
|
365
|
+
if (cached.ok) {
|
|
366
|
+
emit({ clientId, status: "hit", freshnessMs: cached.expiresAt - now() });
|
|
367
|
+
return cached.client;
|
|
368
|
+
}
|
|
369
|
+
emit({ clientId, status: "negative", freshnessMs: cached.expiresAt - now() });
|
|
370
|
+
throw new Error(cached.message);
|
|
371
|
+
}
|
|
372
|
+
emit({ clientId, status: "miss" });
|
|
373
|
+
const currentTime = now();
|
|
374
|
+
if (currentTime - resolutionWindowStartedAt >= resolutionWindowMs) {
|
|
375
|
+
resolutionWindowStartedAt = currentTime;
|
|
376
|
+
resolutionsInWindow = 0;
|
|
377
|
+
}
|
|
378
|
+
let clientWindow = clientWindows.get(clientId);
|
|
379
|
+
if (!clientWindow || currentTime - clientWindow.startedAt >= resolutionWindowMs) {
|
|
380
|
+
clientWindow = { startedAt: currentTime, count: 0 };
|
|
381
|
+
clientWindows.delete(clientId);
|
|
382
|
+
clientWindows.set(clientId, clientWindow);
|
|
383
|
+
while (clientWindows.size > CLIENT_WINDOW_CAP) {
|
|
384
|
+
const oldest = clientWindows.keys().next().value;
|
|
385
|
+
if (oldest === undefined)
|
|
386
|
+
break;
|
|
387
|
+
clientWindows.delete(oldest);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (clientWindow.count >= maxResolutionsPerClient) {
|
|
391
|
+
throw new Error("CIMD metadata resolution rate limit exceeded for this client_id");
|
|
392
|
+
}
|
|
393
|
+
if (resolutionsInWindow >= maxResolutions) {
|
|
394
|
+
throw new Error("CIMD metadata resolution rate limit exceeded");
|
|
395
|
+
}
|
|
396
|
+
clientWindow.count += 1;
|
|
397
|
+
resolutionsInWindow += 1;
|
|
398
|
+
const conditionalHeaders = {};
|
|
399
|
+
if (cached?.ok && cached.etag)
|
|
400
|
+
conditionalHeaders["if-none-match"] = cached.etag;
|
|
401
|
+
if (cached?.ok && cached.lastModified) {
|
|
402
|
+
conditionalHeaders["if-modified-since"] = cached.lastModified;
|
|
403
|
+
}
|
|
404
|
+
try {
|
|
405
|
+
const response = await options.fetcher.fetch(url, conditionalHeaders);
|
|
406
|
+
const freshness = responseFreshness(response.headers, policy, now());
|
|
407
|
+
if (response.status === 304 && cached?.ok) {
|
|
408
|
+
const refreshed = {
|
|
409
|
+
...cached,
|
|
410
|
+
expiresAt: now() + freshness.freshnessMs
|
|
411
|
+
};
|
|
412
|
+
if (freshness.store)
|
|
413
|
+
touch(clientId, refreshed);
|
|
414
|
+
else
|
|
415
|
+
dropCache(clientId);
|
|
416
|
+
emit({ clientId, status: "revalidated", freshnessMs: freshness.freshnessMs });
|
|
417
|
+
return refreshed.client;
|
|
418
|
+
}
|
|
419
|
+
if (response.status !== 200) {
|
|
420
|
+
throw new Error(`CIMD endpoint returned HTTP ${response.status}`);
|
|
421
|
+
}
|
|
422
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
423
|
+
if (!isJsonContentType(contentType)) {
|
|
424
|
+
throw new Error("CIMD endpoint must return a JSON media type");
|
|
425
|
+
}
|
|
426
|
+
const decoded = JSON.parse(new TextDecoder().decode(response.body));
|
|
427
|
+
const metadata = CimdClientMetadataSchema.parse(decoded);
|
|
428
|
+
if (metadata.client_id !== clientId) {
|
|
429
|
+
throw new Error("CIMD client_id does not exactly match the requested URL");
|
|
430
|
+
}
|
|
431
|
+
if (!metadata.redirect_uris.every((uri) => isRegistrableRedirectUri(uri, metadata.application_type))) {
|
|
432
|
+
throw new Error("CIMD redirect_uris contains a URI forbidden for the application type");
|
|
433
|
+
}
|
|
434
|
+
const client = toRegisteredClient(metadata);
|
|
435
|
+
if (freshness.store && Number.isFinite(freshness.freshnessMs)) {
|
|
436
|
+
touch(clientId, {
|
|
437
|
+
ok: true,
|
|
438
|
+
client,
|
|
439
|
+
expiresAt: now() + freshness.freshnessMs,
|
|
440
|
+
...response.headers.get("etag") !== null && {
|
|
441
|
+
etag: response.headers.get("etag") ?? undefined
|
|
442
|
+
},
|
|
443
|
+
...response.headers.get("last-modified") !== null && {
|
|
444
|
+
lastModified: response.headers.get("last-modified") ?? undefined
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
} else {
|
|
448
|
+
dropCache(clientId);
|
|
449
|
+
}
|
|
450
|
+
return client;
|
|
451
|
+
} catch (error) {
|
|
452
|
+
const message = error instanceof Error ? error.message : "CIMD metadata resolution failed";
|
|
453
|
+
touch(clientId, {
|
|
454
|
+
ok: false,
|
|
455
|
+
message,
|
|
456
|
+
expiresAt: now() + (policy.negativeTtlMs ?? 1e4)
|
|
457
|
+
});
|
|
458
|
+
throw error;
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
return async (clientId) => {
|
|
462
|
+
const pending = inflight.get(clientId);
|
|
463
|
+
if (pending)
|
|
464
|
+
return pending;
|
|
465
|
+
const created = resolveOne(clientId).finally(() => inflight.delete(clientId));
|
|
466
|
+
inflight.set(clientId, created);
|
|
467
|
+
return created;
|
|
468
|
+
};
|
|
511
469
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
"audio/mpeg": ".mp3",
|
|
523
|
-
"audio/wav": ".wav",
|
|
524
|
-
"audio/ogg": ".ogg"
|
|
525
|
-
};
|
|
526
|
-
|
|
527
|
-
class DownloadOperationError extends Error {
|
|
528
|
-
code;
|
|
529
|
-
status;
|
|
530
|
-
constructor(code, message, status) {
|
|
531
|
-
super(message);
|
|
532
|
-
this.code = code;
|
|
533
|
-
this.status = status;
|
|
534
|
-
this.name = "DownloadOperationError";
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
function extensionFor(url, mime) {
|
|
538
|
-
if (MIME_EXT[mime])
|
|
539
|
-
return MIME_EXT[mime];
|
|
540
|
-
try {
|
|
541
|
-
const ext = extname(new URL(url).pathname).toLowerCase();
|
|
542
|
-
if (ext)
|
|
543
|
-
return ext;
|
|
544
|
-
} catch {}
|
|
545
|
-
return ".bin";
|
|
546
|
-
}
|
|
547
|
-
function baseNameFor(url) {
|
|
470
|
+
function json(body, status = 200, extraHeaders) {
|
|
471
|
+
return new Response(JSON.stringify(body), {
|
|
472
|
+
status,
|
|
473
|
+
headers: { "Content-Type": "application/json", ...PUBLIC_CORS2, ...extraHeaders }
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
function oauthError(error, description, status = 400) {
|
|
477
|
+
return json({ error, error_description: description }, status);
|
|
478
|
+
}
|
|
479
|
+
function isRegistrableRedirectUri(value, applicationType) {
|
|
548
480
|
try {
|
|
549
|
-
const
|
|
550
|
-
|
|
481
|
+
const url = new URL(value);
|
|
482
|
+
if (url.username || url.password || url.hash)
|
|
483
|
+
return false;
|
|
484
|
+
if (url.protocol === "https:")
|
|
485
|
+
return true;
|
|
486
|
+
if (url.protocol !== "http:")
|
|
487
|
+
return false;
|
|
488
|
+
if (applicationType === "web")
|
|
489
|
+
return false;
|
|
490
|
+
const host = url.hostname.replace(/^\[|\]$/g, "");
|
|
491
|
+
return host === "127.0.0.1" || host === "::1" || host === "localhost";
|
|
551
492
|
} catch {
|
|
552
|
-
return
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
async function runDownloadOperation(config) {
|
|
556
|
-
const res = await fetchGuarded(new URL(config.url), config.allowPrivateHosts ?? false, {
|
|
557
|
-
timeoutMs: config.timeoutMs,
|
|
558
|
-
signal: config.signal
|
|
559
|
-
});
|
|
560
|
-
if (!res.ok) {
|
|
561
|
-
throw new DownloadOperationError("DOWNLOAD_HTTP_ERROR", `HTTP ${res.status}`, 502);
|
|
562
|
-
}
|
|
563
|
-
const mimeType = mediaTypeEssence(res.headers.get("content-type")) || "application/octet-stream";
|
|
564
|
-
const max = config.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
565
|
-
const declared = Number(res.headers.get("content-length") ?? 0);
|
|
566
|
-
if (declared > max) {
|
|
567
|
-
await res.body?.cancel();
|
|
568
|
-
throw new DownloadOperationError("DOWNLOAD_TOO_LARGE", `file exceeds the ${max}-byte cap`, 413);
|
|
493
|
+
return false;
|
|
569
494
|
}
|
|
570
|
-
const path = config.path ?? `${baseNameFor(config.url)}${extensionFor(config.url, mimeType)}`;
|
|
571
|
-
return config.files.write(path, res.body ?? new Uint8Array, {
|
|
572
|
-
maxBytes: max,
|
|
573
|
-
mediaType: mimeType,
|
|
574
|
-
signal: config.signal
|
|
575
|
-
});
|
|
576
495
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
FILE_EXISTS: "Managed file already exists"
|
|
587
|
-
};
|
|
588
|
-
function managedFileAppError(error) {
|
|
589
|
-
if (!(error instanceof ManagedFileError))
|
|
590
|
-
return null;
|
|
591
|
-
switch (error.code) {
|
|
592
|
-
case "FILE_INVALID_PATH":
|
|
593
|
-
case "FILE_OUTSIDE_ROOT":
|
|
594
|
-
case "FILE_NOT_FOUND":
|
|
595
|
-
case "FILE_NOT_REGULAR":
|
|
596
|
-
case "FILE_INSPECTION_REJECTED":
|
|
597
|
-
case "FILE_TOO_LARGE":
|
|
598
|
-
case "FILE_EXISTS":
|
|
599
|
-
return new AppError(error.code, SAFE_MANAGED_FILE_MESSAGES[error.code], STITCH_ERROR_STATUS[error.code]);
|
|
600
|
-
case "FILE_IO_ERROR":
|
|
601
|
-
return null;
|
|
496
|
+
function isLoopbackRedirectUri(value) {
|
|
497
|
+
try {
|
|
498
|
+
const url = new URL(value);
|
|
499
|
+
if (url.protocol !== "http:")
|
|
500
|
+
return false;
|
|
501
|
+
const host = url.hostname.replace(/^\[|\]$/g, "");
|
|
502
|
+
return host === "127.0.0.1" || host === "::1" || host === "localhost";
|
|
503
|
+
} catch {
|
|
504
|
+
return false;
|
|
602
505
|
}
|
|
603
506
|
}
|
|
604
|
-
function
|
|
605
|
-
|
|
507
|
+
function redirectWith(uri, params) {
|
|
508
|
+
const url = new URL(uri);
|
|
509
|
+
for (const [key, value] of Object.entries(params))
|
|
510
|
+
url.searchParams.set(key, value);
|
|
511
|
+
return new Response(null, { status: 302, headers: { Location: url.toString() } });
|
|
606
512
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
transports: config.transports,
|
|
617
|
-
annotations: config.annotations,
|
|
618
|
-
present: config.present,
|
|
619
|
-
handler: async (context) => {
|
|
620
|
-
const url = await config.resolveUrl(context.input, context);
|
|
621
|
-
if (!url) {
|
|
622
|
-
throw new AppError("DOWNLOAD_NOT_FOUND", "Nothing to download", 404);
|
|
623
|
-
}
|
|
624
|
-
try {
|
|
625
|
-
return await runDownloadOperation({
|
|
626
|
-
url,
|
|
627
|
-
files: config.files,
|
|
628
|
-
path: config.pathFromInput?.(context.input),
|
|
629
|
-
allowPrivateHosts: config.allowPrivateHosts,
|
|
630
|
-
maxBytes: config.maxBytes,
|
|
631
|
-
timeoutMs: config.timeoutMs,
|
|
632
|
-
signal: context.signal
|
|
633
|
-
});
|
|
634
|
-
} catch (error) {
|
|
635
|
-
if (error instanceof DownloadOperationError) {
|
|
636
|
-
throw new AppError(error.code, `Download failed: ${error.message}`, error.status);
|
|
637
|
-
}
|
|
638
|
-
const managedError = managedFileAppError(error);
|
|
639
|
-
if (managedError)
|
|
640
|
-
throw managedError;
|
|
641
|
-
throw error;
|
|
513
|
+
async function readForm(req) {
|
|
514
|
+
const contentType = req.headers.get("content-type") ?? "";
|
|
515
|
+
if (contentType.includes("application/json")) {
|
|
516
|
+
const body = await req.json().catch(() => null);
|
|
517
|
+
const params = new URLSearchParams;
|
|
518
|
+
if (isRecord(body)) {
|
|
519
|
+
for (const [key, value] of Object.entries(body)) {
|
|
520
|
+
if (typeof value === "string")
|
|
521
|
+
params.set(key, value);
|
|
642
522
|
}
|
|
643
523
|
}
|
|
644
|
-
|
|
524
|
+
return params;
|
|
525
|
+
}
|
|
526
|
+
return new URLSearchParams(await req.text());
|
|
645
527
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
528
|
+
function mountOAuthProvider(config) {
|
|
529
|
+
const base = config.basePath ?? "/oauth";
|
|
530
|
+
const ttl = config.accessTokenTtlSec ?? 3600;
|
|
531
|
+
const registerPath = `${base}/register`;
|
|
532
|
+
const authorizePath = `${base}/authorize`;
|
|
533
|
+
const tokenPath = `${base}/token`;
|
|
534
|
+
const clientRegistration = config.clientRegistration ?? {};
|
|
535
|
+
const dcrRegistry = clientRegistration.dcr || undefined;
|
|
536
|
+
const dcrEnabled = dcrRegistry !== undefined;
|
|
537
|
+
const cimdConfig = clientRegistration.cimd === false ? undefined : clientRegistration.cimd ?? {};
|
|
538
|
+
const cimdEnabled = cimdConfig !== undefined;
|
|
539
|
+
const resolveCimd = cimdEnabled ? createCimdResolver({
|
|
540
|
+
cache: cimdConfig.cache,
|
|
541
|
+
fetcher: cimdConfig.fetcher ?? createSecureClientMetadataFetcher(),
|
|
542
|
+
onCacheEvent: cimdConfig.onCacheEvent
|
|
543
|
+
}) : undefined;
|
|
544
|
+
const resolveClient = async (clientId) => {
|
|
545
|
+
const preRegistered = await clientRegistration.preRegistered?.get(clientId);
|
|
546
|
+
if (preRegistered)
|
|
547
|
+
return preRegistered.clientId === clientId ? preRegistered : null;
|
|
548
|
+
const clientUrl = absoluteClientUrl(clientId);
|
|
549
|
+
if (clientUrl) {
|
|
550
|
+
if (!resolveCimd || clientUrl.protocol !== "https:")
|
|
551
|
+
return null;
|
|
670
552
|
try {
|
|
671
|
-
return await
|
|
672
|
-
} catch
|
|
673
|
-
|
|
674
|
-
if (managedError)
|
|
675
|
-
throw managedError;
|
|
676
|
-
throw error;
|
|
553
|
+
return await resolveCimd(clientId);
|
|
554
|
+
} catch {
|
|
555
|
+
return null;
|
|
677
556
|
}
|
|
678
557
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
const res = await fetchGuarded(url, options.allowPrivateHosts ?? false, {
|
|
703
|
-
timeoutMs: options.timeoutMs,
|
|
704
|
-
signal: options.signal
|
|
705
|
-
});
|
|
706
|
-
if (!res.ok)
|
|
707
|
-
throw new AppError("VIEW_HTTP_ERROR", `HTTP ${res.status}`, 502);
|
|
708
|
-
const headerMime = mediaTypeEssence(res.headers.get("content-type"));
|
|
709
|
-
const mimeType = headerMime || extMime || "application/octet-stream";
|
|
710
|
-
if (mimeType.startsWith("video/")) {
|
|
711
|
-
await res.body?.cancel();
|
|
712
|
-
return { video: true, mimeType, bytesRead: 0 };
|
|
713
|
-
}
|
|
714
|
-
const declared = Number(res.headers.get("content-length") ?? 0);
|
|
715
|
-
if (declared > maxBytes) {
|
|
716
|
-
await res.body?.cancel();
|
|
717
|
-
return { tooLarge: true, mimeType, bytesRead: 0 };
|
|
558
|
+
const dcrClient = await dcrRegistry?.get(clientId);
|
|
559
|
+
return dcrClient?.clientId === clientId ? dcrClient : null;
|
|
560
|
+
};
|
|
561
|
+
const redirectToClient = (uri, params) => redirectWith(uri, { ...params, iss: config.issuer });
|
|
562
|
+
const metadataRoute = {
|
|
563
|
+
method: "ALL",
|
|
564
|
+
path: AS_METADATA_PATH,
|
|
565
|
+
handler: (req) => {
|
|
566
|
+
if (req.method === "OPTIONS")
|
|
567
|
+
return new Response(null, { status: 204, headers: PUBLIC_CORS2 });
|
|
568
|
+
return json({
|
|
569
|
+
issuer: config.issuer,
|
|
570
|
+
authorization_endpoint: `${config.issuer}${authorizePath}`,
|
|
571
|
+
token_endpoint: `${config.issuer}${tokenPath}`,
|
|
572
|
+
...dcrEnabled && { registration_endpoint: `${config.issuer}${registerPath}` },
|
|
573
|
+
response_types_supported: ["code"],
|
|
574
|
+
grant_types_supported: config.refreshTokens ? ["authorization_code", "refresh_token"] : ["authorization_code"],
|
|
575
|
+
code_challenge_methods_supported: ["S256"],
|
|
576
|
+
token_endpoint_auth_methods_supported: ["none"],
|
|
577
|
+
...cimdEnabled && { client_id_metadata_document_supported: true },
|
|
578
|
+
authorization_response_iss_parameter_supported: true,
|
|
579
|
+
...config.scopesSupported && { scopes_supported: config.scopesSupported }
|
|
580
|
+
});
|
|
718
581
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
582
|
+
};
|
|
583
|
+
const registerRoute = {
|
|
584
|
+
method: "ALL",
|
|
585
|
+
path: registerPath,
|
|
586
|
+
handler: async (req) => {
|
|
587
|
+
if (req.method === "OPTIONS")
|
|
588
|
+
return new Response(null, { status: 204, headers: PUBLIC_CORS2 });
|
|
589
|
+
if (req.method !== "POST")
|
|
590
|
+
return oauthError("invalid_request", "POST required", 405);
|
|
591
|
+
const meta = await req.json().catch(() => null);
|
|
592
|
+
if (!isRecord(meta)) {
|
|
593
|
+
return oauthError("invalid_client_metadata", "Body must be a JSON object");
|
|
594
|
+
}
|
|
595
|
+
const rawAppType = meta.application_type;
|
|
596
|
+
if (rawAppType !== undefined && rawAppType !== "native" && rawAppType !== "web") {
|
|
597
|
+
return oauthError("invalid_client_metadata", "application_type must be 'native' or 'web'");
|
|
598
|
+
}
|
|
599
|
+
const applicationType = rawAppType;
|
|
600
|
+
const redirectUris = meta.redirect_uris;
|
|
601
|
+
const tokenEndpointAuthMethod = meta.token_endpoint_auth_method ?? "none";
|
|
602
|
+
if (tokenEndpointAuthMethod !== "none") {
|
|
603
|
+
return oauthError("invalid_client_metadata", 'Only public clients with token_endpoint_auth_method "none" are supported');
|
|
604
|
+
}
|
|
605
|
+
if (!Array.isArray(redirectUris) || redirectUris.length === 0 || !redirectUris.every((u) => typeof u === "string" && isRegistrableRedirectUri(u, applicationType))) {
|
|
606
|
+
return oauthError("invalid_redirect_uri", applicationType === "web" ? "redirect_uris must be a non-empty array of absolute https URLs (a web client cannot register an http loopback URI)" : "redirect_uris must be a non-empty array of absolute https URLs (http is allowed only on a loopback host)");
|
|
607
|
+
}
|
|
608
|
+
if (!dcrRegistry)
|
|
609
|
+
throw new Error("DCR route mounted without a DCR registry");
|
|
610
|
+
const client = await dcrRegistry.register({
|
|
611
|
+
redirectUris,
|
|
612
|
+
clientName: typeof meta.client_name === "string" ? meta.client_name : undefined,
|
|
613
|
+
tokenEndpointAuthMethod,
|
|
614
|
+
...applicationType && { applicationType }
|
|
615
|
+
});
|
|
616
|
+
return json({
|
|
617
|
+
client_id: client.clientId,
|
|
618
|
+
redirect_uris: client.redirectUris,
|
|
619
|
+
token_endpoint_auth_method: "none",
|
|
620
|
+
grant_types: config.refreshTokens ? ["authorization_code", "refresh_token"] : ["authorization_code"],
|
|
621
|
+
response_types: ["code"],
|
|
622
|
+
...client.clientName && { client_name: client.clientName },
|
|
623
|
+
...client.applicationType && { application_type: client.applicationType }
|
|
624
|
+
}, 201);
|
|
743
625
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
626
|
+
};
|
|
627
|
+
const authorizeRoute = {
|
|
628
|
+
method: "ALL",
|
|
629
|
+
path: authorizePath,
|
|
630
|
+
handler: async (req) => {
|
|
631
|
+
if (req.method === "OPTIONS")
|
|
632
|
+
return new Response(null, { status: 204, headers: PUBLIC_CORS2 });
|
|
633
|
+
const url = new URL(req.url);
|
|
634
|
+
const p = url.searchParams;
|
|
635
|
+
const clientId = p.get("client_id");
|
|
636
|
+
const redirectUri = p.get("redirect_uri");
|
|
637
|
+
const responseType = p.get("response_type");
|
|
638
|
+
const codeChallenge = p.get("code_challenge");
|
|
639
|
+
const codeChallengeMethod = p.get("code_challenge_method") ?? "S256";
|
|
640
|
+
const resource = p.get("resource");
|
|
641
|
+
const scope = p.get("scope") ?? undefined;
|
|
642
|
+
const state = p.get("state") ?? undefined;
|
|
643
|
+
if (!clientId || !redirectUri) {
|
|
644
|
+
return oauthError("invalid_request", "client_id and redirect_uri are required");
|
|
645
|
+
}
|
|
646
|
+
const client = await resolveClient(clientId);
|
|
647
|
+
if (!client)
|
|
648
|
+
return oauthError("invalid_client", "Unknown client_id", 401);
|
|
649
|
+
if (!client.redirectUris.includes(redirectUri)) {
|
|
650
|
+
return oauthError("invalid_request", "redirect_uri does not match a registered URI");
|
|
651
|
+
}
|
|
652
|
+
if (responseType !== "code") {
|
|
653
|
+
return redirectToClient(redirectUri, {
|
|
654
|
+
error: "unsupported_response_type",
|
|
655
|
+
...state && { state }
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
if (!codeChallenge || codeChallengeMethod !== "S256") {
|
|
659
|
+
return redirectToClient(redirectUri, {
|
|
660
|
+
error: "invalid_request",
|
|
661
|
+
error_description: "PKCE S256 code_challenge is required",
|
|
662
|
+
...state && { state }
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
if (!resource) {
|
|
666
|
+
return redirectToClient(redirectUri, {
|
|
667
|
+
error: "invalid_target",
|
|
668
|
+
error_description: "resource parameter is required",
|
|
669
|
+
...state && { state }
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
if (resource !== config.resource) {
|
|
673
|
+
return redirectToClient(redirectUri, {
|
|
674
|
+
error: "invalid_target",
|
|
675
|
+
error_description: "resource is not served by this authorization server",
|
|
676
|
+
...state && { state }
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
const requestedScopes = parseScopes(scope);
|
|
680
|
+
const supportedScopes = new Set(config.scopesSupported ?? []);
|
|
681
|
+
if (config.scopesSupported && requestedScopes.some((requestedScope2) => !supportedScopes.has(requestedScope2))) {
|
|
682
|
+
return redirectToClient(redirectUri, {
|
|
683
|
+
error: "invalid_scope",
|
|
684
|
+
error_description: "One or more requested scopes are not supported",
|
|
685
|
+
...state && { state }
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
const requestedScope = requestedScopes.length > 0 ? requestedScopes.join(" ") : undefined;
|
|
689
|
+
const authRequest = {
|
|
690
|
+
clientId,
|
|
691
|
+
redirectUri,
|
|
692
|
+
scope: requestedScope,
|
|
693
|
+
resource,
|
|
694
|
+
state,
|
|
695
|
+
isLoopbackRedirect: isLoopbackRedirectUri(redirectUri),
|
|
696
|
+
...safeClientName(client.clientName) !== undefined && {
|
|
697
|
+
clientName: safeClientName(client.clientName)
|
|
788
698
|
},
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
bytes: buffer.length
|
|
792
|
-
};
|
|
793
|
-
}
|
|
794
|
-
if (mimeType.startsWith("audio/")) {
|
|
795
|
-
return {
|
|
796
|
-
content: [
|
|
797
|
-
{
|
|
798
|
-
type: "audio",
|
|
799
|
-
data: buffer.toString("base64"),
|
|
800
|
-
mimeType,
|
|
801
|
-
annotations: { audience: ["user", "assistant"], priority: 0.9 }
|
|
699
|
+
...client.applicationType !== undefined && {
|
|
700
|
+
applicationType: client.applicationType
|
|
802
701
|
},
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
const message = normalizeFileToolError(error).message;
|
|
831
|
-
errors.push({ path: pathOrUrl, message });
|
|
832
|
-
content.push({ type: "text", text: `[${pathOrUrl}] Error: ${message}` });
|
|
702
|
+
...absoluteClientUrl(clientId)?.protocol === "https:" && {
|
|
703
|
+
clientOrigin: new URL(clientId).origin
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
const result = await config.authorizeUser(req, authRequest);
|
|
707
|
+
if (result instanceof Response)
|
|
708
|
+
return result;
|
|
709
|
+
if (!Array.isArray(result.approvedScopes) || !result.approvedScopes.every((scope2) => typeof scope2 === "string")) {
|
|
710
|
+
throw new Error("authorizeUser must return approvedScopes as an array of strings");
|
|
711
|
+
}
|
|
712
|
+
const approvedScopes = [...new Set(result.approvedScopes)];
|
|
713
|
+
if (approvedScopes.some((approvedScope2) => !requestedScopes.includes(approvedScope2) || config.scopesSupported !== undefined && !supportedScopes.has(approvedScope2))) {
|
|
714
|
+
throw new Error("authorizeUser approvedScopes must be a subset of requested scopes");
|
|
715
|
+
}
|
|
716
|
+
const approvedScope = approvedScopes.length > 0 ? approvedScopes.join(" ") : undefined;
|
|
717
|
+
const code = randomUUID();
|
|
718
|
+
await config.codes.save(code, {
|
|
719
|
+
clientId,
|
|
720
|
+
redirectUri,
|
|
721
|
+
codeChallenge,
|
|
722
|
+
codeChallengeMethod: "S256",
|
|
723
|
+
resource,
|
|
724
|
+
scope: approvedScope,
|
|
725
|
+
userId: result.userId,
|
|
726
|
+
expiresAt: Date.now() + AUTH_CODE_TTL_MS
|
|
727
|
+
});
|
|
728
|
+
return redirectToClient(redirectUri, { code, ...state && { state } });
|
|
833
729
|
}
|
|
834
|
-
}
|
|
835
|
-
return ViewFileOutputSchema.parse({ content, errors });
|
|
836
|
-
}
|
|
837
|
-
function mountViewFile(server, options = {}) {
|
|
838
|
-
server.registerTool("view_file", {
|
|
839
|
-
description: "View media (image, audio, video) by URL or local path — returns it as content you can SEE / HEAR. Pass several paths to view multiple files at once. Use it on a generation `output` url to inspect the result.",
|
|
840
|
-
inputSchema: ViewFileInputSchema.shape,
|
|
841
|
-
annotations: { title: "View Media", readOnlyHint: true, idempotentHint: true }
|
|
842
|
-
}, async (args) => {
|
|
843
|
-
const result = await runViewFileOperation(args.paths, options);
|
|
844
|
-
return { content: result.content };
|
|
845
|
-
});
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
// src/tools/define-view-file-tool.ts
|
|
849
|
-
function defineViewFileTool(config) {
|
|
850
|
-
const defaults = {
|
|
851
|
-
mcp: (output) => ({ content: output.content }),
|
|
852
|
-
agent: (output) => ({
|
|
853
|
-
type: "content",
|
|
854
|
-
value: output.content.map((part) => {
|
|
855
|
-
if (part.type === "text")
|
|
856
|
-
return part;
|
|
857
|
-
return {
|
|
858
|
-
type: "file",
|
|
859
|
-
data: { type: "data", data: part.data },
|
|
860
|
-
mediaType: part.mimeType
|
|
861
|
-
};
|
|
862
|
-
})
|
|
863
|
-
})
|
|
864
730
|
};
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
output: ViewFileOutputSchema,
|
|
871
|
-
transports: config.transports,
|
|
872
|
-
annotations: config.annotations ?? {
|
|
873
|
-
title: "View Media",
|
|
874
|
-
readOnlyHint: true,
|
|
875
|
-
idempotentHint: true
|
|
876
|
-
},
|
|
877
|
-
present: { ...defaults, ...config.present },
|
|
878
|
-
handler: (context) => runViewFileOperation(context.input.paths, {
|
|
879
|
-
files: config.files,
|
|
880
|
-
allowPrivateHosts: config.allowPrivateHosts,
|
|
881
|
-
timeoutMs: config.timeoutMs
|
|
882
|
-
}, context.signal)
|
|
731
|
+
const issueAccessToken = (userId, audience, clientId, scope) => signJwt({ scope, client_id: clientId }, config.signingSecret, {
|
|
732
|
+
issuer: config.issuer,
|
|
733
|
+
audience,
|
|
734
|
+
subject: userId,
|
|
735
|
+
expiresInSec: ttl
|
|
883
736
|
});
|
|
737
|
+
const tokenRoute = {
|
|
738
|
+
method: "ALL",
|
|
739
|
+
path: tokenPath,
|
|
740
|
+
handler: async (req) => {
|
|
741
|
+
if (req.method === "OPTIONS")
|
|
742
|
+
return new Response(null, { status: 204, headers: PUBLIC_CORS2 });
|
|
743
|
+
if (req.method !== "POST")
|
|
744
|
+
return oauthError("invalid_request", "POST required", 405);
|
|
745
|
+
const form = await readForm(req);
|
|
746
|
+
const grantType = form.get("grant_type");
|
|
747
|
+
if (grantType === "authorization_code") {
|
|
748
|
+
const code = form.get("code");
|
|
749
|
+
const verifier = form.get("code_verifier");
|
|
750
|
+
const redirectUri = form.get("redirect_uri");
|
|
751
|
+
const clientId = form.get("client_id");
|
|
752
|
+
if (!code || !verifier || !redirectUri || !clientId) {
|
|
753
|
+
return oauthError("invalid_request", "code, code_verifier, redirect_uri, client_id required");
|
|
754
|
+
}
|
|
755
|
+
const data = await config.codes.take(code);
|
|
756
|
+
if (!data)
|
|
757
|
+
return oauthError("invalid_grant", "Unknown or used authorization code");
|
|
758
|
+
if (data.expiresAt < Date.now())
|
|
759
|
+
return oauthError("invalid_grant", "Authorization code expired");
|
|
760
|
+
if (data.clientId !== clientId)
|
|
761
|
+
return oauthError("invalid_grant", "client_id mismatch");
|
|
762
|
+
if (data.redirectUri !== redirectUri)
|
|
763
|
+
return oauthError("invalid_grant", "redirect_uri mismatch");
|
|
764
|
+
if (!await verifyPkce(verifier, data.codeChallenge)) {
|
|
765
|
+
return oauthError("invalid_grant", "PKCE verification failed");
|
|
766
|
+
}
|
|
767
|
+
const accessToken = await issueAccessToken(data.userId, data.resource, data.clientId, data.scope);
|
|
768
|
+
let refreshToken;
|
|
769
|
+
if (config.refreshTokens) {
|
|
770
|
+
refreshToken = randomUUID();
|
|
771
|
+
await config.refreshTokens.save(refreshToken, {
|
|
772
|
+
clientId: data.clientId,
|
|
773
|
+
resource: data.resource,
|
|
774
|
+
scope: data.scope,
|
|
775
|
+
userId: data.userId
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
return json({
|
|
779
|
+
access_token: accessToken,
|
|
780
|
+
token_type: "Bearer",
|
|
781
|
+
expires_in: ttl,
|
|
782
|
+
...data.scope && { scope: data.scope },
|
|
783
|
+
...refreshToken && { refresh_token: refreshToken }
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
if (grantType === "refresh_token") {
|
|
787
|
+
if (!config.refreshTokens) {
|
|
788
|
+
return oauthError("unsupported_grant_type", "refresh_token grant is not enabled");
|
|
789
|
+
}
|
|
790
|
+
const token = form.get("refresh_token");
|
|
791
|
+
const clientId = form.get("client_id");
|
|
792
|
+
if (!token || !clientId)
|
|
793
|
+
return oauthError("invalid_request", "refresh_token and client_id required");
|
|
794
|
+
const data = await config.refreshTokens.take(token);
|
|
795
|
+
if (!data)
|
|
796
|
+
return oauthError("invalid_grant", "Unknown or used refresh token");
|
|
797
|
+
if (data.clientId !== clientId)
|
|
798
|
+
return oauthError("invalid_grant", "client_id mismatch");
|
|
799
|
+
const accessToken = await issueAccessToken(data.userId, data.resource, data.clientId, data.scope);
|
|
800
|
+
const newRefresh = randomUUID();
|
|
801
|
+
await config.refreshTokens.save(newRefresh, data);
|
|
802
|
+
return json({
|
|
803
|
+
access_token: accessToken,
|
|
804
|
+
token_type: "Bearer",
|
|
805
|
+
expires_in: ttl,
|
|
806
|
+
...data.scope && { scope: data.scope },
|
|
807
|
+
refresh_token: newRefresh
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
return oauthError("unsupported_grant_type", `Unsupported grant_type: ${grantType ?? "none"}`);
|
|
811
|
+
}
|
|
812
|
+
};
|
|
813
|
+
return dcrEnabled ? [metadataRoute, registerRoute, authorizeRoute, tokenRoute] : [metadataRoute, authorizeRoute, tokenRoute];
|
|
884
814
|
}
|
|
885
815
|
// src/tools/lifecycle.ts
|
|
886
816
|
function composeToolLifecycle(...hooks) {
|
|
@@ -901,7 +831,6 @@ function composeToolLifecycle(...hooks) {
|
|
|
901
831
|
};
|
|
902
832
|
}
|
|
903
833
|
// src/tools/list-names.ts
|
|
904
|
-
var TOOL_TRANSPORTS = ["MCP", "AGENT", "CLI"];
|
|
905
834
|
function listToolNames(surface) {
|
|
906
835
|
const entries = new Map;
|
|
907
836
|
for (const transport of TOOL_TRANSPORTS) {
|
|
@@ -931,14 +860,7 @@ function listToolNames(surface) {
|
|
|
931
860
|
function listContractToolNames(contracts) {
|
|
932
861
|
return listToolNames({ services: contracts.map(contractOnlyService) });
|
|
933
862
|
}
|
|
934
|
-
// src/tools/mcp.ts
|
|
935
|
-
import {
|
|
936
|
-
createRequestStateCodec,
|
|
937
|
-
McpServer
|
|
938
|
-
} from "@modelcontextprotocol/server";
|
|
939
|
-
import { z as z5 } from "zod";
|
|
940
|
-
|
|
941
|
-
// src/tools/mcp-app.ts
|
|
863
|
+
// src/tools/mcp/app.ts
|
|
942
864
|
import { readFileSync } from "node:fs";
|
|
943
865
|
import { fileURLToPath } from "node:url";
|
|
944
866
|
var RESOURCE_MIME_TYPE = "text/html;profile=mcp-app";
|
|
@@ -958,8 +880,7 @@ function inlineMcpAppBundle(html) {
|
|
|
958
880
|
}).join(",")}};`);
|
|
959
881
|
return html.replace(EXT_APPS_BUNDLE_PLACEHOLDER, () => bundle);
|
|
960
882
|
}
|
|
961
|
-
|
|
962
|
-
// src/tools/mcp-catalog.ts
|
|
883
|
+
// src/tools/mcp/catalog.ts
|
|
963
884
|
import { createHash } from "node:crypto";
|
|
964
885
|
var MCP_CATALOG_META_KEY = "stitchkit/catalog";
|
|
965
886
|
var stampCache = new WeakMap;
|
|
@@ -980,7 +901,7 @@ function mcpCatalogStamp(surface) {
|
|
|
980
901
|
const descriptors = [
|
|
981
902
|
...surface.contractTools,
|
|
982
903
|
...surface.runtimeTools.map((entry) => entry.descriptor)
|
|
983
|
-
].sort((left, right) =>
|
|
904
|
+
].sort((left, right) => compareCodeUnits(left.mountable.name, right.mountable.name));
|
|
984
905
|
const bytes = serializeCanonicalJson(descriptors.map(toolFingerprint));
|
|
985
906
|
const stamp = {
|
|
986
907
|
digest: createHash("sha256").update(bytes).digest("hex").slice(0, 16),
|
|
@@ -1015,8 +936,22 @@ function stampToolResult(result, stamp) {
|
|
|
1015
936
|
return result;
|
|
1016
937
|
return { ...result, _meta: { ...result._meta, ...mcpCatalogMeta(stamp) } };
|
|
1017
938
|
}
|
|
939
|
+
// src/tools/mcp/handler.ts
|
|
940
|
+
import {
|
|
941
|
+
createMcpHandler as createSdkMcpHandler,
|
|
942
|
+
hostHeaderValidationResponse,
|
|
943
|
+
localhostAllowedHostnames,
|
|
944
|
+
localhostAllowedOrigins,
|
|
945
|
+
originValidationResponse
|
|
946
|
+
} from "@modelcontextprotocol/server";
|
|
947
|
+
|
|
948
|
+
// src/tools/mcp/mount.ts
|
|
949
|
+
import {
|
|
950
|
+
createRequestStateCodec,
|
|
951
|
+
McpServer
|
|
952
|
+
} from "@modelcontextprotocol/server";
|
|
1018
953
|
|
|
1019
|
-
// src/tools/mcp
|
|
954
|
+
// src/tools/mcp/prepare.ts
|
|
1020
955
|
function textBlock(text) {
|
|
1021
956
|
return [{ type: "text", text }];
|
|
1022
957
|
}
|
|
@@ -1047,21 +982,25 @@ function prepareMcpSurface(services, config = {}) {
|
|
|
1047
982
|
return prepareMcpTools(tools, config);
|
|
1048
983
|
}
|
|
1049
984
|
function prepareMcpTools(tools, config = {}) {
|
|
1050
|
-
const prepared = prepareProjectedMcpTools(tools.map((mountable) =>
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
985
|
+
const prepared = prepareProjectedMcpTools(tools.map((mountable) => {
|
|
986
|
+
const outputSchema = toolSurfaceOutputSchema(mountable.method);
|
|
987
|
+
return {
|
|
988
|
+
tool: mountable,
|
|
989
|
+
name: mountable.name,
|
|
990
|
+
...mountable.method.paramsSchema !== undefined && {
|
|
991
|
+
paramsSchema: mountable.method.paramsSchema
|
|
992
|
+
},
|
|
993
|
+
...mountable.method.inputSchema !== undefined && {
|
|
994
|
+
inputSchema: mountable.method.inputSchema
|
|
995
|
+
},
|
|
996
|
+
...mountable.method.toolView?.defaults !== undefined && {
|
|
997
|
+
inputDefaults: mountable.method.toolView.defaults
|
|
998
|
+
},
|
|
999
|
+
...outputSchema !== undefined && { outputSchema },
|
|
1000
|
+
shouldExtend: mountable.shouldExtend,
|
|
1001
|
+
...mountable.method.mcp !== undefined && { mcp: mountable.method.mcp }
|
|
1002
|
+
};
|
|
1003
|
+
}), config).map((entry) => {
|
|
1065
1004
|
Object.freeze(entry.tool);
|
|
1066
1005
|
return Object.freeze({
|
|
1067
1006
|
mountable: entry.tool,
|
|
@@ -1105,7 +1044,13 @@ function prepareMcpServerSurface(surface, config = {}) {
|
|
|
1105
1044
|
return Object.freeze({ contractTools, runtimeTools: Object.freeze(runtimeTools) });
|
|
1106
1045
|
}
|
|
1107
1046
|
|
|
1108
|
-
// src/tools/mcp
|
|
1047
|
+
// src/tools/mcp/register.ts
|
|
1048
|
+
import {
|
|
1049
|
+
isCallToolResult
|
|
1050
|
+
} from "@modelcontextprotocol/server";
|
|
1051
|
+
import { z as z3 } from "zod";
|
|
1052
|
+
|
|
1053
|
+
// src/tools/mcp/round.ts
|
|
1109
1054
|
import {
|
|
1110
1055
|
acceptedContent,
|
|
1111
1056
|
CLIENT_INFO_META_KEY,
|
|
@@ -1113,9 +1058,9 @@ import {
|
|
|
1113
1058
|
inputResponse,
|
|
1114
1059
|
PROTOCOL_VERSION_META_KEY
|
|
1115
1060
|
} from "@modelcontextprotocol/server";
|
|
1116
|
-
import { z as
|
|
1061
|
+
import { z as z2 } from "zod";
|
|
1117
1062
|
|
|
1118
|
-
// src/tools/mcp
|
|
1063
|
+
// src/tools/mcp/progress.ts
|
|
1119
1064
|
function mcpProgressToken(context) {
|
|
1120
1065
|
const token = context.mcpReq._meta?.progressToken;
|
|
1121
1066
|
return typeof token === "string" || typeof token === "number" ? token : undefined;
|
|
@@ -1146,7 +1091,7 @@ function createMcpProgressReporter(context) {
|
|
|
1146
1091
|
};
|
|
1147
1092
|
}
|
|
1148
1093
|
|
|
1149
|
-
// src/tools/mcp
|
|
1094
|
+
// src/tools/mcp/round.ts
|
|
1150
1095
|
function transportContext(context, toolName, outcome, round) {
|
|
1151
1096
|
const protocolVersionValue = isRecord(context.mcpReq.envelope) ? Reflect.get(context.mcpReq.envelope, PROTOCOL_VERSION_META_KEY) : undefined;
|
|
1152
1097
|
const protocolVersion = typeof protocolVersionValue === "string" ? protocolVersionValue : undefined;
|
|
@@ -1213,7 +1158,7 @@ function roundPlanDigest(requests) {
|
|
|
1213
1158
|
plan: requests.map((request) => ({
|
|
1214
1159
|
key: request.key,
|
|
1215
1160
|
message: request.message,
|
|
1216
|
-
schema:
|
|
1161
|
+
schema: z2.toJSONSchema(request.schema, { io: "input", unrepresentable: "any" })
|
|
1217
1162
|
}))
|
|
1218
1163
|
});
|
|
1219
1164
|
}
|
|
@@ -1221,7 +1166,10 @@ async function resolveRequests(options, policy) {
|
|
|
1221
1166
|
const declared = staticInputRounds(policy);
|
|
1222
1167
|
if (declared)
|
|
1223
1168
|
return { requests: declared, guarded: false };
|
|
1224
|
-
const parsed = parseToolCallArguments(options.tool.method, options.rawArgs,
|
|
1169
|
+
const parsed = parseToolCallArguments(options.tool.method, options.rawArgs, {
|
|
1170
|
+
coerceJson: options.coerceJsonArgs ?? true,
|
|
1171
|
+
toolSurface: true
|
|
1172
|
+
});
|
|
1225
1173
|
if (!parsed.ok) {
|
|
1226
1174
|
return { requests: [], guarded: false };
|
|
1227
1175
|
}
|
|
@@ -1391,7 +1339,7 @@ async function resolveMcpRound(options) {
|
|
|
1391
1339
|
};
|
|
1392
1340
|
}
|
|
1393
1341
|
|
|
1394
|
-
// src/tools/mcp
|
|
1342
|
+
// src/tools/mcp/trace.ts
|
|
1395
1343
|
import {
|
|
1396
1344
|
BAGGAGE_META_KEY,
|
|
1397
1345
|
TRACEPARENT_META_KEY,
|
|
@@ -1423,73 +1371,102 @@ function runInMcpRequestContext(context, toolName, body) {
|
|
|
1423
1371
|
return runWithRequestContext(request, body);
|
|
1424
1372
|
}
|
|
1425
1373
|
|
|
1426
|
-
// src/tools/
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1374
|
+
// src/tools/mcp/register.ts
|
|
1375
|
+
function registerMcpTool(server, presentation, descriptor, config) {
|
|
1376
|
+
const { mountable } = descriptor;
|
|
1377
|
+
const toolConfig = {
|
|
1378
|
+
description: presentation.description,
|
|
1379
|
+
inputSchema: z3.looseObject({}).meta(presentationMetadata(descriptor.inputSchema))
|
|
1380
|
+
};
|
|
1381
|
+
if (descriptor.outputSchema)
|
|
1382
|
+
toolConfig.outputSchema = descriptor.outputSchema;
|
|
1383
|
+
if (presentation.annotations)
|
|
1384
|
+
toolConfig.annotations = presentation.annotations;
|
|
1385
|
+
if (presentation.ui) {
|
|
1386
|
+
toolConfig._meta = {
|
|
1387
|
+
ui: presentation.ui,
|
|
1388
|
+
"ui/resourceUri": presentation.ui.resourceUri
|
|
1437
1389
|
};
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
ui: definition.ui,
|
|
1445
|
-
"ui/resourceUri": definition.ui.resourceUri
|
|
1446
|
-
};
|
|
1447
|
-
}
|
|
1448
|
-
server.registerTool(definition.name, stampToolRegistration(toolConfig, config.catalog), async (rawArgs, mcpContext) => stampToolResult(await runInMcpRequestContext(mcpContext, definition.name, async () => {
|
|
1449
|
-
const args = isRecord(rawArgs) ? rawArgs : {};
|
|
1450
|
-
const runTool = (tool, toolArgs, roundContext) => executeToolMethod(tool.method, definition.name, toolArgs, { ...config.context, ...roundContext, source: "mcp" }, config.hooks, config.lifecycle, config.coerceJsonArgs ?? true, config.onOutputStrip ? (paths) => config.onOutputStrip?.(definition.name, paths) : undefined);
|
|
1390
|
+
}
|
|
1391
|
+
const { name } = presentation;
|
|
1392
|
+
const format = config.formatResult;
|
|
1393
|
+
server.registerTool(name, stampToolRegistration(toolConfig, config.catalog), async (rawArgs, mcpContext) => stampToolResult(await runInMcpRequestContext(mcpContext, name, async () => {
|
|
1394
|
+
const args = isRecord(rawArgs) ? rawArgs : {};
|
|
1395
|
+
try {
|
|
1451
1396
|
const round = await resolveMcpRound({
|
|
1452
1397
|
tool: mountable,
|
|
1453
1398
|
rawArgs: args,
|
|
1454
1399
|
context: mcpContext,
|
|
1455
1400
|
policy: mountable.method.mcp,
|
|
1456
1401
|
runtime: config.multiRoundRuntime,
|
|
1457
|
-
runTool,
|
|
1458
|
-
formatFailure: (
|
|
1402
|
+
runTool: config.runTool,
|
|
1403
|
+
formatFailure: (result2) => format(result2, "none", name)
|
|
1459
1404
|
});
|
|
1460
1405
|
if (round.kind === "response")
|
|
1461
1406
|
return round.response;
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1407
|
+
const { present } = presentation;
|
|
1408
|
+
if (!present) {
|
|
1409
|
+
const result2 = await config.runTool(mountable, args, round.context);
|
|
1410
|
+
return format(result2, descriptor.outputMode, name);
|
|
1465
1411
|
}
|
|
1466
|
-
const
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1412
|
+
const result = await config.runTool(mountable, args, round.context, {
|
|
1413
|
+
finalizeOutput: async (data) => {
|
|
1414
|
+
const presented = await present(data);
|
|
1415
|
+
if (!isRecord(presented)) {
|
|
1416
|
+
throw new AppError("INTERNAL_SERVER_ERROR", "Runtime MCP presenter did not return a result", 500);
|
|
1417
|
+
}
|
|
1418
|
+
if ("structuredContent" in presented || "isError" in presented) {
|
|
1419
|
+
throw new AppError("INTERNAL_SERVER_ERROR", "Runtime MCP presenter cannot set framework-owned structuredContent or isError", 500);
|
|
1420
|
+
}
|
|
1421
|
+
const structured = format({ ok: true, data }, descriptor.outputMode, name).structuredContent;
|
|
1422
|
+
const response = structured === undefined ? presented : { ...presented, structuredContent: structured };
|
|
1423
|
+
if (!isCallToolResult(response)) {
|
|
1424
|
+
throw new AppError("INTERNAL_SERVER_ERROR", "Runtime MCP presenter returned an invalid CallToolResult", 500);
|
|
1425
|
+
}
|
|
1426
|
+
return response;
|
|
1478
1427
|
}
|
|
1479
|
-
return response;
|
|
1480
1428
|
});
|
|
1481
|
-
if (!
|
|
1482
|
-
return
|
|
1483
|
-
|
|
1484
|
-
if (!isCallToolResult(presentedResult.data)) {
|
|
1429
|
+
if (!result.ok)
|
|
1430
|
+
return format(result, "none", name);
|
|
1431
|
+
if (!isCallToolResult(result.data)) {
|
|
1485
1432
|
throw new Error("[stitchkit] Runtime MCP presenter invariant failed");
|
|
1486
1433
|
}
|
|
1487
|
-
return
|
|
1488
|
-
})
|
|
1434
|
+
return result.data;
|
|
1435
|
+
} catch (err) {
|
|
1436
|
+
return format(toolResultFromError(err), "none", name);
|
|
1437
|
+
}
|
|
1438
|
+
}), config.catalog));
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
// src/tools/mcp/runtime-tools.ts
|
|
1442
|
+
function mountPreparedRuntimeMcp(server, tools, config) {
|
|
1443
|
+
const runTool = createToolRunner({
|
|
1444
|
+
source: "mcp",
|
|
1445
|
+
toolSurface: true,
|
|
1446
|
+
context: config.context,
|
|
1447
|
+
hooks: config.hooks,
|
|
1448
|
+
lifecycle: config.lifecycle,
|
|
1449
|
+
coerceJsonArgs: config.coerceJsonArgs,
|
|
1450
|
+
onOutputStrip: config.onOutputStrip
|
|
1451
|
+
});
|
|
1452
|
+
for (const { definition, descriptor } of tools) {
|
|
1453
|
+
const present = definition.present?.mcp;
|
|
1454
|
+
registerMcpTool(server, {
|
|
1455
|
+
name: definition.name,
|
|
1456
|
+
description: definition.description,
|
|
1457
|
+
annotations: definition.annotations,
|
|
1458
|
+
ui: definition.ui,
|
|
1459
|
+
...present && { present: (data) => present(data) }
|
|
1460
|
+
}, descriptor, {
|
|
1461
|
+
runTool,
|
|
1462
|
+
formatResult: config.formatResult,
|
|
1463
|
+
catalog: config.catalog,
|
|
1464
|
+
multiRoundRuntime: config.multiRoundRuntime
|
|
1465
|
+
});
|
|
1489
1466
|
}
|
|
1490
1467
|
}
|
|
1491
1468
|
|
|
1492
|
-
// src/tools/mcp.ts
|
|
1469
|
+
// src/tools/mcp/mount.ts
|
|
1493
1470
|
function validateMcpSchemas(config) {
|
|
1494
1471
|
prepareMcpSurface(config.services, {
|
|
1495
1472
|
extend: config.extend,
|
|
@@ -1513,51 +1490,27 @@ function mountMcp(mcpServer, services, config = {}) {
|
|
|
1513
1490
|
function mountPreparedMcp(mcpServer, prepared, config = {}) {
|
|
1514
1491
|
const runTool = createToolRunner({
|
|
1515
1492
|
source: "mcp",
|
|
1493
|
+
toolSurface: true,
|
|
1516
1494
|
extend: config.extend,
|
|
1517
1495
|
context: config.context,
|
|
1518
1496
|
hooks: config.hooks,
|
|
1519
1497
|
lifecycle: config.lifecycle,
|
|
1520
|
-
errorHint: config.errorHint,
|
|
1521
1498
|
coerceJsonArgs: config.coerceJsonArgs,
|
|
1522
1499
|
onOutputStrip: config.onOutputStrip
|
|
1523
1500
|
});
|
|
1524
1501
|
for (const descriptor of prepared) {
|
|
1525
|
-
const {
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
ui: mountable.method.ui,
|
|
1538
|
-
"ui/resourceUri": mountable.method.ui.resourceUri
|
|
1539
|
-
};
|
|
1540
|
-
}
|
|
1541
|
-
mcpServer.registerTool(mountable.name, stampToolRegistration(toolConfig, config.catalog), async (rawArgs, mcpContext) => stampToolResult(await runInMcpRequestContext(mcpContext, mountable.name, async () => {
|
|
1542
|
-
const args = isRecord(rawArgs) ? rawArgs : {};
|
|
1543
|
-
try {
|
|
1544
|
-
const round = await resolveMcpRound({
|
|
1545
|
-
tool: mountable,
|
|
1546
|
-
rawArgs: args,
|
|
1547
|
-
context: mcpContext,
|
|
1548
|
-
policy: mountable.method.mcp,
|
|
1549
|
-
runtime: config.multiRoundRuntime,
|
|
1550
|
-
runTool,
|
|
1551
|
-
formatFailure: (result2) => formatMcpResult(result2, "none", mountable.name, config.errorHint)
|
|
1552
|
-
});
|
|
1553
|
-
if (round.kind === "response")
|
|
1554
|
-
return round.response;
|
|
1555
|
-
const result = await runTool(mountable, args, round.context);
|
|
1556
|
-
return formatMcpResult(result, descriptor.outputMode, mountable.name, config.errorHint);
|
|
1557
|
-
} catch (err) {
|
|
1558
|
-
return formatMcpResult(toolResultFromError(err), "none", mountable.name, config.errorHint);
|
|
1559
|
-
}
|
|
1560
|
-
}), config.catalog));
|
|
1502
|
+
const { method, name } = descriptor.mountable;
|
|
1503
|
+
registerMcpTool(mcpServer, {
|
|
1504
|
+
name,
|
|
1505
|
+
description: method.desc,
|
|
1506
|
+
annotations: method.annotations,
|
|
1507
|
+
ui: method.ui
|
|
1508
|
+
}, descriptor, {
|
|
1509
|
+
runTool,
|
|
1510
|
+
formatResult: (result, mode, toolName) => formatMcpResult(result, mode, toolName, config.errorHint),
|
|
1511
|
+
catalog: config.catalog,
|
|
1512
|
+
multiRoundRuntime: config.multiRoundRuntime
|
|
1513
|
+
});
|
|
1561
1514
|
}
|
|
1562
1515
|
}
|
|
1563
1516
|
function buildMcpServer(config, auth) {
|
|
@@ -1675,56 +1628,8 @@ function mountMcpResource(server, resource) {
|
|
|
1675
1628
|
]
|
|
1676
1629
|
}));
|
|
1677
1630
|
}
|
|
1678
|
-
// src/tools/mcp-handler.ts
|
|
1679
|
-
import {
|
|
1680
|
-
createMcpHandler as createSdkMcpHandler,
|
|
1681
|
-
hostHeaderValidationResponse,
|
|
1682
|
-
localhostAllowedHostnames,
|
|
1683
|
-
localhostAllowedOrigins,
|
|
1684
|
-
originValidationResponse
|
|
1685
|
-
} from "@modelcontextprotocol/server";
|
|
1686
|
-
|
|
1687
|
-
// src/tools/oauth-metadata.ts
|
|
1688
|
-
var PROTECTED_RESOURCE_PATH = "/.well-known/oauth-protected-resource";
|
|
1689
|
-
function metadataPath(resource) {
|
|
1690
|
-
const { pathname } = new URL(resource);
|
|
1691
|
-
return pathname === "/" ? PROTECTED_RESOURCE_PATH : `${PROTECTED_RESOURCE_PATH}${pathname}`;
|
|
1692
|
-
}
|
|
1693
|
-
function protectedResourceMetadataUrl(resource) {
|
|
1694
|
-
return `${new URL(resource).origin}${metadataPath(resource)}`;
|
|
1695
|
-
}
|
|
1696
|
-
function wwwAuthenticateHeader(resource) {
|
|
1697
|
-
return `Bearer resource_metadata="${protectedResourceMetadataUrl(resource)}"`;
|
|
1698
|
-
}
|
|
1699
|
-
var PUBLIC_CORS = {
|
|
1700
|
-
"Access-Control-Allow-Origin": "*",
|
|
1701
|
-
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
1702
|
-
"Access-Control-Allow-Headers": DEFAULT_CORS_ALLOW_HEADERS
|
|
1703
|
-
};
|
|
1704
|
-
function oauthProtectedResourceRoute(config) {
|
|
1705
|
-
const body = JSON.stringify({
|
|
1706
|
-
resource: config.resource,
|
|
1707
|
-
authorization_servers: config.authorizationServers,
|
|
1708
|
-
...config.scopesSupported && { scopes_supported: config.scopesSupported },
|
|
1709
|
-
bearer_methods_supported: ["header"]
|
|
1710
|
-
});
|
|
1711
|
-
return {
|
|
1712
|
-
method: "ALL",
|
|
1713
|
-
path: metadataPath(config.resource),
|
|
1714
|
-
handler: (req) => {
|
|
1715
|
-
if (req.method === "OPTIONS")
|
|
1716
|
-
return new Response(null, { status: 204, headers: PUBLIC_CORS });
|
|
1717
|
-
if (req.method !== "GET")
|
|
1718
|
-
return new Response("Method Not Allowed", { status: 405 });
|
|
1719
|
-
return new Response(body, {
|
|
1720
|
-
status: 200,
|
|
1721
|
-
headers: { "Content-Type": "application/json", ...PUBLIC_CORS }
|
|
1722
|
-
});
|
|
1723
|
-
}
|
|
1724
|
-
};
|
|
1725
|
-
}
|
|
1726
1631
|
|
|
1727
|
-
// src/tools/mcp
|
|
1632
|
+
// src/tools/mcp/handler.ts
|
|
1728
1633
|
function jsonRpcError(code, message, status, headers) {
|
|
1729
1634
|
return Response.json({ jsonrpc: "2.0", error: { code, message }, id: null }, { status, headers });
|
|
1730
1635
|
}
|
|
@@ -1858,7 +1763,7 @@ function createMcpHttpRoute(config) {
|
|
|
1858
1763
|
handler: (request) => config.handler.fetch(request)
|
|
1859
1764
|
};
|
|
1860
1765
|
}
|
|
1861
|
-
// src/tools/mcp
|
|
1766
|
+
// src/tools/mcp/stdio.ts
|
|
1862
1767
|
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
1863
1768
|
async function createStdioMcpServer(config) {
|
|
1864
1769
|
const auth = await config.auth;
|
|
@@ -1867,7 +1772,7 @@ async function createStdioMcpServer(config) {
|
|
|
1867
1772
|
maxSubscriptions: 0
|
|
1868
1773
|
});
|
|
1869
1774
|
}
|
|
1870
|
-
// src/tools/mcp
|
|
1775
|
+
// src/tools/mcp/stdio-signals.ts
|
|
1871
1776
|
var bound = new WeakSet;
|
|
1872
1777
|
function bindStdioProcessSignals(handle, options = {}) {
|
|
1873
1778
|
if (bound.has(handle)) {
|
|
@@ -1902,900 +1807,1033 @@ function bindStdioProcessSignals(handle, options = {}) {
|
|
|
1902
1807
|
if (!started)
|
|
1903
1808
|
bound.delete(handle);
|
|
1904
1809
|
};
|
|
1905
|
-
const close = () => {
|
|
1906
|
-
const hadStarted = started;
|
|
1907
|
-
removeListeners();
|
|
1908
|
-
if (!hadStarted)
|
|
1909
|
-
resolveChain();
|
|
1810
|
+
const close = () => {
|
|
1811
|
+
const hadStarted = started;
|
|
1812
|
+
removeListeners();
|
|
1813
|
+
if (!hadStarted)
|
|
1814
|
+
resolveChain();
|
|
1815
|
+
};
|
|
1816
|
+
const run = async (signal) => {
|
|
1817
|
+
try {
|
|
1818
|
+
await options.onClose?.(signal);
|
|
1819
|
+
} catch (error) {
|
|
1820
|
+
reportSignalError("prepare", error, options.onError);
|
|
1821
|
+
}
|
|
1822
|
+
try {
|
|
1823
|
+
await handle.close();
|
|
1824
|
+
} catch (error) {
|
|
1825
|
+
rejectChain(error);
|
|
1826
|
+
reportSignalError("close", error, options.onError);
|
|
1827
|
+
removeListeners();
|
|
1828
|
+
return;
|
|
1829
|
+
}
|
|
1830
|
+
resolveChain();
|
|
1831
|
+
try {
|
|
1832
|
+
await options.onComplete?.();
|
|
1833
|
+
} catch (error) {
|
|
1834
|
+
reportSignalError("complete", error, options.onError);
|
|
1835
|
+
} finally {
|
|
1836
|
+
removeListeners();
|
|
1837
|
+
}
|
|
1838
|
+
};
|
|
1839
|
+
const onSignal = (signal) => {
|
|
1840
|
+
if (!started) {
|
|
1841
|
+
started = true;
|
|
1842
|
+
sameTurnAsStart = true;
|
|
1843
|
+
queueMicrotask(() => {
|
|
1844
|
+
sameTurnAsStart = false;
|
|
1845
|
+
});
|
|
1846
|
+
run(signal).catch((error) => reportSignalError("close", error, options.onError));
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1849
|
+
if (sameTurnAsStart)
|
|
1850
|
+
return;
|
|
1851
|
+
removeListeners();
|
|
1852
|
+
guardSignalCallback(() => options.onRepeatedSignal?.(signal), "close", options.onError);
|
|
1853
|
+
let restored = false;
|
|
1854
|
+
try {
|
|
1855
|
+
restored = source.raiseDefault(signal);
|
|
1856
|
+
} catch (error) {
|
|
1857
|
+
reportSignalError("close", error, options.onError);
|
|
1858
|
+
}
|
|
1859
|
+
if (!restored) {
|
|
1860
|
+
guardSignalCallback(() => options.onEscalationBlocked?.(signal), "close", options.onError);
|
|
1861
|
+
}
|
|
1862
|
+
};
|
|
1863
|
+
for (const signal of signals) {
|
|
1864
|
+
const handler = () => onSignal(signal);
|
|
1865
|
+
handlers.push([signal, handler]);
|
|
1866
|
+
source.on(signal, handler);
|
|
1867
|
+
}
|
|
1868
|
+
return { promise, close };
|
|
1869
|
+
}
|
|
1870
|
+
// src/tools/operations/async-operation-id.ts
|
|
1871
|
+
import { z as z4 } from "zod";
|
|
1872
|
+
function adapterResult(capability, target, schema, value) {
|
|
1873
|
+
const parsed = schema.safeParse(value);
|
|
1874
|
+
if (!parsed.success) {
|
|
1875
|
+
throw new Error(`Async operation adapter for capability "${capability}" returned invalid ${target}`, { cause: parsed.error });
|
|
1876
|
+
}
|
|
1877
|
+
return parsed.data;
|
|
1878
|
+
}
|
|
1879
|
+
var NON_WIRE_STABLE_ZOD_TYPES = new Set([
|
|
1880
|
+
"catch",
|
|
1881
|
+
"default",
|
|
1882
|
+
"lazy",
|
|
1883
|
+
"pipe",
|
|
1884
|
+
"prefault",
|
|
1885
|
+
"readonly",
|
|
1886
|
+
"success",
|
|
1887
|
+
"transform"
|
|
1888
|
+
]);
|
|
1889
|
+
function hasOverwriteCheck(value) {
|
|
1890
|
+
return isRecord(value) && isRecord(value._zod) && isRecord(value._zod.def) && value._zod.def.check === "overwrite";
|
|
1891
|
+
}
|
|
1892
|
+
function assertWireStableIdSchema(schema, boundary) {
|
|
1893
|
+
const visited = new Set;
|
|
1894
|
+
const pending = [schema];
|
|
1895
|
+
while (pending.length > 0) {
|
|
1896
|
+
const value = pending.pop();
|
|
1897
|
+
if (typeof value !== "object" || value === null || visited.has(value))
|
|
1898
|
+
continue;
|
|
1899
|
+
visited.add(value);
|
|
1900
|
+
if (value instanceof z4.ZodType) {
|
|
1901
|
+
const definition = value._zod.def;
|
|
1902
|
+
if (NON_WIRE_STABLE_ZOD_TYPES.has(definition.type) || "coerce" in definition && definition.coerce === true || "checks" in definition && Array.isArray(definition.checks) && definition.checks.some(hasOverwriteCheck)) {
|
|
1903
|
+
throw new Error(`${boundary} must use a wire-stable ID schema without transforms, coercion, defaults or overwrites; use binding: "adapted" with explicit ID adapters`);
|
|
1904
|
+
}
|
|
1905
|
+
pending.push(...Object.values(definition));
|
|
1906
|
+
continue;
|
|
1907
|
+
}
|
|
1908
|
+
if (Array.isArray(value)) {
|
|
1909
|
+
pending.push(...value);
|
|
1910
|
+
continue;
|
|
1911
|
+
}
|
|
1912
|
+
if (isRecord(value))
|
|
1913
|
+
pending.push(...Object.values(value));
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
// src/tools/operations/async-operation-binding.ts
|
|
1918
|
+
function bindContractAsyncOperation(config) {
|
|
1919
|
+
const endpoint = (key) => {
|
|
1920
|
+
const found = config.contract.endpoints[key];
|
|
1921
|
+
if (!found)
|
|
1922
|
+
throw new Error(`Contract async operation method "${key}" not found`);
|
|
1923
|
+
return found;
|
|
1924
|
+
};
|
|
1925
|
+
const start = endpoint(config.capabilities.start);
|
|
1926
|
+
const status = endpoint(config.capabilities.status);
|
|
1927
|
+
const wait = endpoint(config.capabilities.wait);
|
|
1928
|
+
if (!start.output || !status.output || !wait.output) {
|
|
1929
|
+
throw new Error("Contract async operation requires outputs for start, status and wait");
|
|
1930
|
+
}
|
|
1931
|
+
const startOutput = start.output;
|
|
1932
|
+
const statusOutput = status.output;
|
|
1933
|
+
if (wait.output !== statusOutput) {
|
|
1934
|
+
throw new Error('Contract async operation capability "wait" output must reuse the same schema instance as the status output');
|
|
1935
|
+
}
|
|
1936
|
+
const resolved = {
|
|
1937
|
+
endpoint,
|
|
1938
|
+
status,
|
|
1939
|
+
wait,
|
|
1940
|
+
startOutput,
|
|
1941
|
+
statusOutput
|
|
1942
|
+
};
|
|
1943
|
+
if (config.binding !== "adapted")
|
|
1944
|
+
return bindDirect(config, resolved);
|
|
1945
|
+
return bindAdapted(config, resolved);
|
|
1946
|
+
}
|
|
1947
|
+
var OPTIONAL_CAPABILITIES = [
|
|
1948
|
+
"cancel",
|
|
1949
|
+
"result",
|
|
1950
|
+
"artifacts"
|
|
1951
|
+
];
|
|
1952
|
+
function bindDirect(config, { endpoint, status, wait, startOutput, statusOutput }) {
|
|
1953
|
+
assertWireStableIdSchema(startOutput, "Direct contract async operation");
|
|
1954
|
+
if (status.input !== startOutput || wait.input !== startOutput) {
|
|
1955
|
+
const capability = status.input !== startOutput ? "status" : "wait";
|
|
1956
|
+
throw new Error(`Contract async operation capability "${capability}" input must reuse the same schema instance as the start output`);
|
|
1957
|
+
}
|
|
1958
|
+
const inputFor = {
|
|
1959
|
+
status: (id) => adapterResult("status", "input", startOutput, id),
|
|
1960
|
+
wait: (id) => adapterResult("wait", "input", startOutput, id)
|
|
1910
1961
|
};
|
|
1911
|
-
const
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
try {
|
|
1918
|
-
await handle.close();
|
|
1919
|
-
} catch (error) {
|
|
1920
|
-
rejectChain(error);
|
|
1921
|
-
reportSignalError("close", error, options.onError);
|
|
1922
|
-
removeListeners();
|
|
1923
|
-
return;
|
|
1962
|
+
for (const capability of OPTIONAL_CAPABILITIES) {
|
|
1963
|
+
const key = config.capabilities[capability];
|
|
1964
|
+
if (!key)
|
|
1965
|
+
continue;
|
|
1966
|
+
if (endpoint(key).input !== startOutput) {
|
|
1967
|
+
throw new Error(`Contract async operation capability "${capability}" input must reuse the same schema instance as the start output`);
|
|
1924
1968
|
}
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1969
|
+
inputFor[capability] = (id) => adapterResult(capability, "input", startOutput, id);
|
|
1970
|
+
}
|
|
1971
|
+
return {
|
|
1972
|
+
contract: config.contract,
|
|
1973
|
+
handlers: config.handlers,
|
|
1974
|
+
capabilities: config.capabilities,
|
|
1975
|
+
schemas: { id: startOutput, snapshot: statusOutput },
|
|
1976
|
+
adapters: {
|
|
1977
|
+
idFromStart: (output) => adapterResult("start", "id", startOutput, output),
|
|
1978
|
+
inputFor
|
|
1932
1979
|
}
|
|
1933
1980
|
};
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
return;
|
|
1981
|
+
}
|
|
1982
|
+
function bindAdapted(config, { endpoint, statusOutput }) {
|
|
1983
|
+
const adapters = config.adapters;
|
|
1984
|
+
const idSchema = config.id;
|
|
1985
|
+
const adaptedInput = (capability, key, builder) => {
|
|
1986
|
+
const input = endpoint(key).input;
|
|
1987
|
+
if (!input) {
|
|
1988
|
+
throw new Error(`Contract async operation capability "${capability}" requires an input schema`);
|
|
1943
1989
|
}
|
|
1944
|
-
|
|
1990
|
+
return (id) => adapterResult(capability, "input", input, builder(id));
|
|
1991
|
+
};
|
|
1992
|
+
const inputFor = {
|
|
1993
|
+
status: adaptedInput("status", config.capabilities.status, adapters.inputFor.status),
|
|
1994
|
+
wait: adaptedInput("wait", config.capabilities.wait, adapters.inputFor.wait)
|
|
1995
|
+
};
|
|
1996
|
+
const optionalInput = (capability, key, builder) => {
|
|
1997
|
+
if (!key)
|
|
1945
1998
|
return;
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
let restored = false;
|
|
1949
|
-
try {
|
|
1950
|
-
restored = source.raiseDefault(signal);
|
|
1951
|
-
} catch (error) {
|
|
1952
|
-
reportSignalError("close", error, options.onError);
|
|
1953
|
-
}
|
|
1954
|
-
if (!restored) {
|
|
1955
|
-
guardSignalCallback(() => options.onEscalationBlocked?.(signal), "close", options.onError);
|
|
1999
|
+
if (!builder) {
|
|
2000
|
+
throw new Error(`Contract async operation capability "${capability}" requires an input adapter`);
|
|
1956
2001
|
}
|
|
2002
|
+
inputFor[capability] = adaptedInput(capability, key, builder);
|
|
1957
2003
|
};
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
2004
|
+
optionalInput("cancel", config.capabilities.cancel, "cancel" in adapters.inputFor ? adapters.inputFor.cancel : undefined);
|
|
2005
|
+
optionalInput("result", config.capabilities.result, "result" in adapters.inputFor ? adapters.inputFor.result : undefined);
|
|
2006
|
+
optionalInput("artifacts", config.capabilities.artifacts, "artifacts" in adapters.inputFor ? adapters.inputFor.artifacts : undefined);
|
|
2007
|
+
if (!idSchema) {
|
|
2008
|
+
throw new Error("Contract adapted async operation requires an id schema");
|
|
1962
2009
|
}
|
|
1963
|
-
return { promise, close };
|
|
1964
|
-
}
|
|
1965
|
-
// src/tools/mount-download.ts
|
|
1966
|
-
import { z as z6 } from "zod";
|
|
1967
|
-
|
|
1968
|
-
// src/tools/native-result.ts
|
|
1969
|
-
function textResult(text, isError = false) {
|
|
1970
|
-
const content = [{ type: "text", text }];
|
|
1971
|
-
return isError ? { content, isError: true } : { content };
|
|
1972
|
-
}
|
|
1973
|
-
|
|
1974
|
-
// src/tools/mount-download.ts
|
|
1975
|
-
function mountDownload(server, config) {
|
|
1976
|
-
const name = config.name ?? "download";
|
|
1977
|
-
assertToolName(name, "<native>", "download");
|
|
1978
|
-
server.registerTool(name, { description: config.description, inputSchema: z6.object(config.inputSchema) }, async (rawArgs) => {
|
|
1979
|
-
const args = isRecord(rawArgs) ? rawArgs : {};
|
|
1980
|
-
try {
|
|
1981
|
-
const url = await config.resolveUrl(args);
|
|
1982
|
-
if (!url)
|
|
1983
|
-
return textResult("Nothing to download.", true);
|
|
1984
|
-
const result = await runDownloadOperation({
|
|
1985
|
-
url,
|
|
1986
|
-
files: config.files,
|
|
1987
|
-
path: config.pathFromArgs?.(args),
|
|
1988
|
-
allowPrivateHosts: config.allowPrivateHosts,
|
|
1989
|
-
maxBytes: config.maxBytes,
|
|
1990
|
-
timeoutMs: config.timeoutMs
|
|
1991
|
-
});
|
|
1992
|
-
return textResult(JSON.stringify(result, null, 2));
|
|
1993
|
-
} catch (error) {
|
|
1994
|
-
if (error instanceof DownloadOperationError) {
|
|
1995
|
-
return textResult(`Download failed [${error.code}]: ${error.message}`, true);
|
|
1996
|
-
}
|
|
1997
|
-
const normalized = normalizeFileToolError(error);
|
|
1998
|
-
return textResult(`Download failed [${normalized.code}]: ${normalized.message}`, true);
|
|
1999
|
-
}
|
|
2000
|
-
});
|
|
2001
|
-
}
|
|
2002
|
-
// src/tools/mount-upload.ts
|
|
2003
|
-
function mountUpload(server, config) {
|
|
2004
|
-
const name = config.name ?? "upload";
|
|
2005
|
-
assertToolName(name, "<native>", "upload");
|
|
2006
|
-
server.registerTool(name, {
|
|
2007
|
-
description: config.description,
|
|
2008
|
-
inputSchema: {
|
|
2009
|
-
path: ManagedFilePathSchema.describe("Path relative to the configured file boundary")
|
|
2010
|
-
}
|
|
2011
|
-
}, async (rawArgs) => {
|
|
2012
|
-
const args = isRecord(rawArgs) ? rawArgs : {};
|
|
2013
|
-
const path = typeof args.path === "string" ? args.path : "";
|
|
2014
|
-
if (!path)
|
|
2015
|
-
return textResult("Provide `path`.", true);
|
|
2016
|
-
try {
|
|
2017
|
-
const uploaded = await runUploadOperation(config.files, path, config.upload);
|
|
2018
|
-
return textResult(JSON.stringify(uploaded ?? null, null, 2));
|
|
2019
|
-
} catch (error) {
|
|
2020
|
-
const normalized = normalizeFileToolError(error);
|
|
2021
|
-
return textResult(`Upload failed [${normalized.code}]: ${normalized.message}`, true);
|
|
2022
|
-
}
|
|
2023
|
-
});
|
|
2024
|
-
}
|
|
2025
|
-
// src/tools/mount-wait.ts
|
|
2026
|
-
import { z as z7 } from "zod";
|
|
2027
|
-
function mountWait(server, config) {
|
|
2028
|
-
const name = config.name ?? "wait";
|
|
2029
|
-
assertToolName(name, "<native>", "wait");
|
|
2030
|
-
server.registerTool(name, { description: config.description, inputSchema: z7.object(config.inputSchema) }, async (rawArgs, mcpContext) => {
|
|
2031
|
-
const args = isRecord(rawArgs) ? rawArgs : {};
|
|
2032
|
-
const report = createMcpProgressReporter(mcpContext);
|
|
2033
|
-
let last;
|
|
2034
|
-
try {
|
|
2035
|
-
const { state, timedOut } = await runWaitOperation({
|
|
2036
|
-
input: args,
|
|
2037
|
-
poll: async (input) => {
|
|
2038
|
-
last = await config.poll(input);
|
|
2039
|
-
return last;
|
|
2040
|
-
},
|
|
2041
|
-
done: config.done,
|
|
2042
|
-
backoff: config.backoff,
|
|
2043
|
-
timeoutSec: config.timeoutFromArgs?.(args) ?? config.defaultTimeout,
|
|
2044
|
-
onTick: (attempt, elapsedSec) => {
|
|
2045
|
-
const snapshot = isRecord(last) ? last : undefined;
|
|
2046
|
-
const phase = typeof snapshot?.phase === "string" ? snapshot.phase : undefined;
|
|
2047
|
-
const measured = typeof snapshot?.progress === "number" && Number.isFinite(snapshot.progress) ? snapshot.progress : undefined;
|
|
2048
|
-
report({
|
|
2049
|
-
progress: measured ?? attempt,
|
|
2050
|
-
message: phase ? `${phase} — poll ${attempt}, ${Math.round(elapsedSec)}s elapsed` : `poll ${attempt}, ${Math.round(elapsedSec)}s elapsed`
|
|
2051
|
-
});
|
|
2052
|
-
}
|
|
2053
|
-
});
|
|
2054
|
-
const rendered = config.render ? config.render(state, timedOut) : { text: JSON.stringify(state, null, 2), isError: timedOut };
|
|
2055
|
-
return textResult(rendered.text, rendered.isError);
|
|
2056
|
-
} catch (err) {
|
|
2057
|
-
return textResult(`Wait failed: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
2058
|
-
}
|
|
2059
|
-
});
|
|
2060
|
-
}
|
|
2061
|
-
// src/tools/oauth-provider.ts
|
|
2062
|
-
import { randomUUID } from "node:crypto";
|
|
2063
|
-
import { z as z8 } from "zod";
|
|
2064
|
-
function isPlainDisplayCharacter(character) {
|
|
2065
|
-
const codePoint = character.codePointAt(0);
|
|
2066
|
-
return codePoint !== undefined && codePoint >= 32 && codePoint !== 127 && character !== "<" && character !== ">";
|
|
2067
|
-
}
|
|
2068
|
-
var CimdClientMetadataSchema = z8.object({
|
|
2069
|
-
client_id: z8.url(),
|
|
2070
|
-
redirect_uris: z8.array(z8.url()).min(1),
|
|
2071
|
-
client_name: z8.string().trim().min(1).max(200).refine((value) => Array.from(value).every(isPlainDisplayCharacter), {
|
|
2072
|
-
message: "client_name must be plain display text"
|
|
2073
|
-
}),
|
|
2074
|
-
token_endpoint_auth_method: z8.literal("none"),
|
|
2075
|
-
application_type: z8.enum(["native", "web"]).optional()
|
|
2076
|
-
});
|
|
2077
|
-
function createSecureClientMetadataFetcher(policy = {}) {
|
|
2078
|
-
assertPositiveInteger("cimd.maxBytes", policy.maxBytes ?? DEFAULT_CIMD_MAX_BYTES);
|
|
2079
|
-
assertPositiveNumber("cimd.timeoutMs", policy.timeoutMs ?? DEFAULT_CIMD_TIMEOUT_MS);
|
|
2080
|
-
assertNonNegativeInteger("cimd.maxRedirects", policy.maxRedirects ?? 3);
|
|
2081
2010
|
return {
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2011
|
+
contract: config.contract,
|
|
2012
|
+
handlers: config.handlers,
|
|
2013
|
+
capabilities: config.capabilities,
|
|
2014
|
+
schemas: { id: idSchema, snapshot: statusOutput },
|
|
2015
|
+
adapters: {
|
|
2016
|
+
idFromStart: (output) => adapterResult("start", "id", idSchema, adapters.idFromStart(output)),
|
|
2017
|
+
inputFor
|
|
2018
|
+
}
|
|
2089
2019
|
};
|
|
2090
2020
|
}
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
}
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2021
|
+
// src/tools/operations/async-operation-canonical.ts
|
|
2022
|
+
function buildCanonicalEndpointTable(config, startOutput) {
|
|
2023
|
+
const description = (capability) => config.descriptions?.[capability] ?? `${config.description}: ${capability}`;
|
|
2024
|
+
const endpoint = (capability, method, path, input, output) => {
|
|
2025
|
+
const scope = config.scopes?.[capability];
|
|
2026
|
+
const expose = config.expose?.[capability];
|
|
2027
|
+
return {
|
|
2028
|
+
method,
|
|
2029
|
+
path,
|
|
2030
|
+
desc: description(capability),
|
|
2031
|
+
input,
|
|
2032
|
+
output,
|
|
2033
|
+
...scope !== undefined && { scope },
|
|
2034
|
+
...expose !== undefined && { expose }
|
|
2035
|
+
};
|
|
2036
|
+
};
|
|
2037
|
+
const endpoints = {
|
|
2038
|
+
start: endpoint("start", "POST", "/", config.startInput, startOutput),
|
|
2039
|
+
status: endpoint("status", "POST", "/status", config.id, config.snapshot),
|
|
2040
|
+
wait: endpoint("wait", "POST", "/wait", config.id, config.snapshot)
|
|
2041
|
+
};
|
|
2042
|
+
const capabilities = {
|
|
2043
|
+
start: "start",
|
|
2044
|
+
status: "status",
|
|
2045
|
+
wait: "wait"
|
|
2046
|
+
};
|
|
2047
|
+
const schemas = {
|
|
2048
|
+
startInput: config.startInput,
|
|
2049
|
+
startOutput,
|
|
2050
|
+
id: config.id,
|
|
2051
|
+
snapshot: config.snapshot,
|
|
2052
|
+
cancelResult: AsyncOperationCancelResultSchema
|
|
2053
|
+
};
|
|
2054
|
+
const inputFor = {
|
|
2055
|
+
status: (id) => adapterResult("status", "input", config.id, id),
|
|
2056
|
+
wait: (id) => adapterResult("wait", "input", config.id, id)
|
|
2057
|
+
};
|
|
2058
|
+
if (config.cancel) {
|
|
2059
|
+
endpoints.cancel = endpoint("cancel", "POST", "/cancel", config.id, AsyncOperationCancelResultSchema);
|
|
2060
|
+
capabilities.cancel = "cancel";
|
|
2061
|
+
schemas.cancel = AsyncOperationCancelResultSchema;
|
|
2062
|
+
inputFor.cancel = (id) => adapterResult("cancel", "input", config.id, id);
|
|
2107
2063
|
}
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2064
|
+
if (config.result) {
|
|
2065
|
+
endpoints.result = endpoint("result", "POST", "/result", config.id, config.result);
|
|
2066
|
+
capabilities.result = "result";
|
|
2067
|
+
schemas.result = config.result;
|
|
2068
|
+
inputFor.result = (id) => adapterResult("result", "input", config.id, id);
|
|
2069
|
+
}
|
|
2070
|
+
if (config.artifacts) {
|
|
2071
|
+
endpoints.artifacts = endpoint("artifacts", "POST", "/artifacts", config.id, config.artifacts);
|
|
2072
|
+
capabilities.artifacts = "artifacts";
|
|
2073
|
+
schemas.artifacts = config.artifacts;
|
|
2074
|
+
inputFor.artifacts = (id) => adapterResult("artifacts", "input", config.id, id);
|
|
2118
2075
|
}
|
|
2076
|
+
return { endpoints, capabilities, schemas, inputFor };
|
|
2119
2077
|
}
|
|
2120
|
-
function
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
if (
|
|
2125
|
-
throw new Error("
|
|
2126
|
-
if (url.hash)
|
|
2127
|
-
throw new Error("CIMD client_id cannot contain a fragment");
|
|
2128
|
-
if (url.search)
|
|
2129
|
-
throw new Error("CIMD client_id cannot contain a query");
|
|
2130
|
-
if (url.toString() !== clientId) {
|
|
2131
|
-
throw new Error("CIMD client_id must be an exact canonical URL");
|
|
2078
|
+
function defineAsyncOperationContract(config) {
|
|
2079
|
+
assertWireStableIdSchema(config.id, "Async operation contract");
|
|
2080
|
+
const hasStartOutput = config.startOutput !== undefined;
|
|
2081
|
+
const hasIdFromStart = config.idFromStart !== undefined;
|
|
2082
|
+
if (hasStartOutput !== hasIdFromStart) {
|
|
2083
|
+
throw new Error("Async operation contract startOutput and idFromStart must be configured together");
|
|
2132
2084
|
}
|
|
2133
|
-
|
|
2085
|
+
const startOutput = config.startOutput ?? config.id;
|
|
2086
|
+
const { endpoints, capabilities, schemas, inputFor } = buildCanonicalEndpointTable(config, startOutput);
|
|
2087
|
+
const contract = config.scope ? defineContract({ prefix: config.prefix, scope: config.scope }, endpoints) : defineContract({ prefix: config.prefix }, endpoints);
|
|
2088
|
+
const applicationStartOutput = config.startOutput;
|
|
2089
|
+
const applicationIdFromStart = config.idFromStart;
|
|
2090
|
+
const idFromStart = applicationStartOutput && applicationIdFromStart ? (output) => adapterResult("start", "id", config.id, applicationIdFromStart(output)) : (output) => adapterResult("start", "id", config.id, output);
|
|
2091
|
+
return {
|
|
2092
|
+
contract,
|
|
2093
|
+
capabilities,
|
|
2094
|
+
schemas,
|
|
2095
|
+
adapters: { idFromStart, inputFor }
|
|
2096
|
+
};
|
|
2134
2097
|
}
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
return 0;
|
|
2139
|
-
const first = raw.split(",")[0]?.trim() ?? "";
|
|
2140
|
-
return /^\d+$/.test(first) ? Number(first) : 0;
|
|
2098
|
+
// src/tools/native-definition.ts
|
|
2099
|
+
function managedNativeIdentity(identity, method) {
|
|
2100
|
+
return { ...identity, method };
|
|
2141
2101
|
}
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2102
|
+
|
|
2103
|
+
// src/tools/operations/define-wait-tool.ts
|
|
2104
|
+
function defineWaitTool(config) {
|
|
2105
|
+
const renderedMcp = config.render ? {
|
|
2106
|
+
mcp: (state) => {
|
|
2107
|
+
const rendered = config.render?.(state, false);
|
|
2108
|
+
return {
|
|
2109
|
+
content: [
|
|
2110
|
+
{
|
|
2111
|
+
type: "text",
|
|
2112
|
+
text: rendered?.text ?? JSON.stringify(state, null, 2)
|
|
2113
|
+
}
|
|
2114
|
+
]
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
} : undefined;
|
|
2118
|
+
const present = renderedMcp || config.present ? { ...renderedMcp, ...config.present } : undefined;
|
|
2119
|
+
return defineRuntimeTool({
|
|
2120
|
+
name: config.name ?? "wait",
|
|
2121
|
+
description: config.description,
|
|
2122
|
+
identity: managedNativeIdentity(config.identity, "GET"),
|
|
2123
|
+
input: config.input,
|
|
2124
|
+
output: config.state,
|
|
2125
|
+
transports: config.transports,
|
|
2126
|
+
annotations: config.annotations,
|
|
2127
|
+
present,
|
|
2128
|
+
handler: async (context) => {
|
|
2129
|
+
let result;
|
|
2130
|
+
try {
|
|
2131
|
+
result = await runWaitOperation({
|
|
2132
|
+
input: context.input,
|
|
2133
|
+
poll: async (input, signal) => config.state.parse(await config.poll(input, { ...context, signal })),
|
|
2134
|
+
done: config.done,
|
|
2135
|
+
backoff: config.backoff,
|
|
2136
|
+
timeoutSec: config.timeoutFromInput?.(context.input) ?? config.defaultTimeout,
|
|
2137
|
+
signal: context.signal
|
|
2138
|
+
});
|
|
2139
|
+
} catch (error) {
|
|
2140
|
+
if (error instanceof WaitTimeoutError) {
|
|
2141
|
+
throw new AppError("WAIT_TIMEOUT", "Wait timed out before the first snapshot", 408);
|
|
2142
|
+
}
|
|
2143
|
+
throw error;
|
|
2144
|
+
}
|
|
2145
|
+
const rendered = config.render?.(result.state, result.timedOut);
|
|
2146
|
+
if (result.timedOut) {
|
|
2147
|
+
throw new AppError("WAIT_TIMEOUT", rendered?.text ?? "Wait timed out before the operation completed", 408);
|
|
2148
|
+
}
|
|
2149
|
+
if (rendered?.isError) {
|
|
2150
|
+
throw new AppError("WAIT_FAILED", rendered.text, 409);
|
|
2151
|
+
}
|
|
2152
|
+
return result.state;
|
|
2153
|
+
}
|
|
2154
|
+
});
|
|
2175
2155
|
}
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2156
|
+
|
|
2157
|
+
// src/tools/operations/async-operation-runtime-tools.ts
|
|
2158
|
+
function terminal(snapshot) {
|
|
2159
|
+
if (typeof snapshot !== "object" || snapshot === null || !("phase" in snapshot)) {
|
|
2160
|
+
return false;
|
|
2161
|
+
}
|
|
2162
|
+
return snapshot.phase === "succeeded" || snapshot.phase === "failed" || snapshot.phase === "cancelled";
|
|
2181
2163
|
}
|
|
2182
|
-
function
|
|
2183
|
-
|
|
2184
|
-
return [];
|
|
2185
|
-
return [...new Set(value.split(/\s+/u).filter(Boolean))];
|
|
2164
|
+
function succeeded(snapshot) {
|
|
2165
|
+
return typeof snapshot === "object" && snapshot !== null && "phase" in snapshot && snapshot.phase === "succeeded";
|
|
2186
2166
|
}
|
|
2187
|
-
function
|
|
2167
|
+
function createRuntimeOperationSurface(config) {
|
|
2168
|
+
const capabilityName = (capability) => config.names?.[capability] ?? `${config.name}_${capability}`;
|
|
2169
|
+
const description = (capability) => config.descriptions?.[capability] ?? `${config.description}: ${capability}`;
|
|
2188
2170
|
return {
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2171
|
+
config,
|
|
2172
|
+
identity: (capability, method) => ({
|
|
2173
|
+
serviceName: config.identity.serviceName,
|
|
2174
|
+
action: `${config.identity.action}.${capability}`,
|
|
2175
|
+
scope: config.scopes?.[capability] ?? config.identity.scope,
|
|
2176
|
+
meta: config.identity.meta,
|
|
2177
|
+
method
|
|
2178
|
+
}),
|
|
2179
|
+
common: (capability) => ({
|
|
2180
|
+
name: capabilityName(capability),
|
|
2181
|
+
description: description(capability),
|
|
2182
|
+
transports: config.transports?.[capability],
|
|
2183
|
+
annotations: config.annotations?.[capability] ?? (capability === "start" || capability === "cancel" ? { destructiveHint: true } : undefined)
|
|
2184
|
+
}),
|
|
2185
|
+
inspect: async (capability, context) => {
|
|
2186
|
+
await config.authorize(context.input, capability, context);
|
|
2187
|
+
const state = config.state.parse(await config.inspect(context.input, context));
|
|
2188
|
+
const snapshot = config.snapshot.parse(await config.classify(state, context));
|
|
2189
|
+
return { state, snapshot };
|
|
2194
2190
|
}
|
|
2195
2191
|
};
|
|
2196
2192
|
}
|
|
2197
|
-
function
|
|
2198
|
-
const
|
|
2199
|
-
|
|
2193
|
+
function defineObservationTools(surface) {
|
|
2194
|
+
const { config } = surface;
|
|
2195
|
+
const status = defineRuntimeTool({
|
|
2196
|
+
...surface.common("status"),
|
|
2197
|
+
identity: surface.identity("status", "GET"),
|
|
2198
|
+
input: config.id,
|
|
2199
|
+
output: config.snapshot,
|
|
2200
|
+
handler: async (context) => (await surface.inspect("status", context)).snapshot
|
|
2201
|
+
});
|
|
2202
|
+
const wait = defineWaitTool({
|
|
2203
|
+
...surface.common("wait"),
|
|
2204
|
+
identity: surface.identity("wait", "GET"),
|
|
2205
|
+
input: config.id,
|
|
2206
|
+
state: config.snapshot,
|
|
2207
|
+
poll: async (_id, context) => (await surface.inspect("wait", context)).snapshot,
|
|
2208
|
+
done: terminal,
|
|
2209
|
+
backoff: config.backoff,
|
|
2210
|
+
defaultTimeout: config.defaultTimeout,
|
|
2211
|
+
timeoutFromInput: config.timeoutFromId
|
|
2212
|
+
});
|
|
2213
|
+
return { status, wait };
|
|
2200
2214
|
}
|
|
2201
|
-
function
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
const resolutionWindowMs = policy.resolutionWindowMs ?? 60000;
|
|
2211
|
-
let resolutionWindowStartedAt = now();
|
|
2212
|
-
let resolutionsInWindow = 0;
|
|
2213
|
-
const clientWindows = new Map;
|
|
2214
|
-
const CLIENT_WINDOW_CAP = 4096;
|
|
2215
|
-
assertPositiveInteger("cimd.cache.maxEntries", maxEntries);
|
|
2216
|
-
assertPositiveInteger("cimd.cache.maxResolutions", maxResolutions);
|
|
2217
|
-
assertPositiveInteger("cimd.cache.maxResolutionsPerClient", maxResolutionsPerClient);
|
|
2218
|
-
assertPositiveInteger("cimd.cache.resolutionWindowMs", resolutionWindowMs);
|
|
2219
|
-
assertNonNegativeInteger("cimd.cache.defaultTtlMs", policy.defaultTtlMs ?? 300000);
|
|
2220
|
-
assertNonNegativeInteger("cimd.cache.maxTtlMs", policy.maxTtlMs ?? 3600000);
|
|
2221
|
-
assertNonNegativeInteger("cimd.cache.negativeTtlMs", policy.negativeTtlMs ?? 1e4);
|
|
2222
|
-
const emit = (event) => {
|
|
2223
|
-
try {
|
|
2224
|
-
options.onCacheEvent?.(event);
|
|
2225
|
-
} catch {}
|
|
2226
|
-
};
|
|
2227
|
-
const readCache = (key) => positives.get(key) ?? negatives.get(key);
|
|
2228
|
-
const trimOldest = (pool) => {
|
|
2229
|
-
while (pool.size > maxEntries) {
|
|
2230
|
-
const oldest = pool.keys().next().value;
|
|
2231
|
-
if (oldest === undefined)
|
|
2232
|
-
break;
|
|
2233
|
-
pool.delete(oldest);
|
|
2234
|
-
}
|
|
2235
|
-
};
|
|
2236
|
-
const touch = (key, value) => {
|
|
2237
|
-
if (value.ok) {
|
|
2238
|
-
negatives.delete(key);
|
|
2239
|
-
positives.delete(key);
|
|
2240
|
-
positives.set(key, value);
|
|
2241
|
-
trimOldest(positives);
|
|
2242
|
-
} else {
|
|
2243
|
-
positives.delete(key);
|
|
2244
|
-
negatives.delete(key);
|
|
2245
|
-
negatives.set(key, value);
|
|
2246
|
-
trimOldest(negatives);
|
|
2247
|
-
}
|
|
2248
|
-
};
|
|
2249
|
-
const dropCache = (key) => {
|
|
2250
|
-
positives.delete(key);
|
|
2251
|
-
negatives.delete(key);
|
|
2252
|
-
};
|
|
2253
|
-
const resolveOne = async (clientId) => {
|
|
2254
|
-
const url = assertCimdClientId(clientId);
|
|
2255
|
-
const cached = readCache(clientId);
|
|
2256
|
-
if (cached && cached.expiresAt > now()) {
|
|
2257
|
-
touch(clientId, cached);
|
|
2258
|
-
if (cached.ok) {
|
|
2259
|
-
emit({ clientId, status: "hit", freshnessMs: cached.expiresAt - now() });
|
|
2260
|
-
return cached.client;
|
|
2261
|
-
}
|
|
2262
|
-
emit({ clientId, status: "negative", freshnessMs: cached.expiresAt - now() });
|
|
2263
|
-
throw new Error(cached.message);
|
|
2264
|
-
}
|
|
2265
|
-
emit({ clientId, status: "miss" });
|
|
2266
|
-
const currentTime = now();
|
|
2267
|
-
if (currentTime - resolutionWindowStartedAt >= resolutionWindowMs) {
|
|
2268
|
-
resolutionWindowStartedAt = currentTime;
|
|
2269
|
-
resolutionsInWindow = 0;
|
|
2215
|
+
function defineCancelTool(surface, capability) {
|
|
2216
|
+
return defineRuntimeTool({
|
|
2217
|
+
...surface.common("cancel"),
|
|
2218
|
+
identity: surface.identity("cancel", "DELETE"),
|
|
2219
|
+
input: surface.config.id,
|
|
2220
|
+
output: AsyncOperationCancelResultSchema,
|
|
2221
|
+
handler: async (context) => {
|
|
2222
|
+
const inspected = await surface.inspect("cancel", context);
|
|
2223
|
+
return capability.handler(inspected.state, context);
|
|
2270
2224
|
}
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2225
|
+
});
|
|
2226
|
+
}
|
|
2227
|
+
function defineSucceededOutputTool(surface, kind, capability, unavailableMessage) {
|
|
2228
|
+
return defineRuntimeTool({
|
|
2229
|
+
...surface.common(kind),
|
|
2230
|
+
identity: surface.identity(kind, "GET"),
|
|
2231
|
+
input: surface.config.id,
|
|
2232
|
+
output: capability.output,
|
|
2233
|
+
handler: async (context) => {
|
|
2234
|
+
const inspected = await surface.inspect(kind, context);
|
|
2235
|
+
if (!succeeded(inspected.snapshot)) {
|
|
2236
|
+
throw new AppError("OPERATION_NOT_SUCCEEDED", unavailableMessage, 409);
|
|
2281
2237
|
}
|
|
2238
|
+
return capability.handler(inspected.state, context);
|
|
2282
2239
|
}
|
|
2283
|
-
|
|
2284
|
-
|
|
2240
|
+
});
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
// src/tools/operations/async-operation-runtime.ts
|
|
2244
|
+
function defineAsyncOperation(config) {
|
|
2245
|
+
const surface = createRuntimeOperationSurface(config);
|
|
2246
|
+
const start = defineRuntimeTool({
|
|
2247
|
+
...surface.common("start"),
|
|
2248
|
+
identity: surface.identity("start", "POST"),
|
|
2249
|
+
input: config.startInput,
|
|
2250
|
+
output: config.id,
|
|
2251
|
+
handler: (context) => config.start(context.input, context)
|
|
2252
|
+
});
|
|
2253
|
+
const { status, wait } = defineObservationTools(surface);
|
|
2254
|
+
const definitions = { start, status, wait };
|
|
2255
|
+
const names = new Set;
|
|
2256
|
+
if (config.cancel) {
|
|
2257
|
+
definitions.cancel = defineCancelTool(surface, config.cancel);
|
|
2258
|
+
}
|
|
2259
|
+
if (config.result) {
|
|
2260
|
+
definitions.result = defineSucceededOutputTool(surface, "result", config.result, "Operation result is not available");
|
|
2261
|
+
}
|
|
2262
|
+
if (config.artifacts) {
|
|
2263
|
+
definitions.artifacts = defineSucceededOutputTool(surface, "artifacts", config.artifacts, "Operation artifacts are not available");
|
|
2264
|
+
}
|
|
2265
|
+
for (const definition of Object.values(definitions)) {
|
|
2266
|
+
assertUniqueToolName(definition.name, names.has(definition.name), "in-process tool name");
|
|
2267
|
+
names.add(definition.name);
|
|
2268
|
+
}
|
|
2269
|
+
return {
|
|
2270
|
+
...definitions,
|
|
2271
|
+
runtimeTools: Object.values(definitions),
|
|
2272
|
+
schemas: {
|
|
2273
|
+
id: config.id,
|
|
2274
|
+
snapshot: config.snapshot,
|
|
2275
|
+
cancelResult: AsyncOperationCancelResultSchema
|
|
2285
2276
|
}
|
|
2286
|
-
|
|
2287
|
-
|
|
2277
|
+
};
|
|
2278
|
+
}
|
|
2279
|
+
// src/tools/operations/mount-wait.ts
|
|
2280
|
+
import { z as z5 } from "zod";
|
|
2281
|
+
|
|
2282
|
+
// src/tools/transfer/native-result.ts
|
|
2283
|
+
function textResult(text, isError = false) {
|
|
2284
|
+
const content = [{ type: "text", text }];
|
|
2285
|
+
return isError ? { content, isError: true } : { content };
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
// src/tools/operations/mount-wait.ts
|
|
2289
|
+
function mountWait(server, config) {
|
|
2290
|
+
const name = config.name ?? "wait";
|
|
2291
|
+
assertToolName(name, "<native>", "wait");
|
|
2292
|
+
server.registerTool(name, { description: config.description, inputSchema: z5.object(config.inputSchema) }, async (rawArgs, mcpContext) => {
|
|
2293
|
+
const args = isRecord(rawArgs) ? rawArgs : {};
|
|
2294
|
+
const report = createMcpProgressReporter(mcpContext);
|
|
2295
|
+
let last;
|
|
2296
|
+
try {
|
|
2297
|
+
const { state, timedOut } = await runWaitOperation({
|
|
2298
|
+
input: args,
|
|
2299
|
+
poll: async (input) => {
|
|
2300
|
+
last = await config.poll(input);
|
|
2301
|
+
return last;
|
|
2302
|
+
},
|
|
2303
|
+
done: config.done,
|
|
2304
|
+
backoff: config.backoff,
|
|
2305
|
+
timeoutSec: config.timeoutFromArgs?.(args) ?? config.defaultTimeout,
|
|
2306
|
+
onTick: (attempt, elapsedSec) => {
|
|
2307
|
+
const snapshot = isRecord(last) ? last : undefined;
|
|
2308
|
+
const phase = typeof snapshot?.phase === "string" ? snapshot.phase : undefined;
|
|
2309
|
+
const measured = typeof snapshot?.progress === "number" && Number.isFinite(snapshot.progress) ? snapshot.progress : undefined;
|
|
2310
|
+
report({
|
|
2311
|
+
progress: measured ?? attempt,
|
|
2312
|
+
message: phase ? `${phase} — poll ${attempt}, ${Math.round(elapsedSec)}s elapsed` : `poll ${attempt}, ${Math.round(elapsedSec)}s elapsed`
|
|
2313
|
+
});
|
|
2314
|
+
}
|
|
2315
|
+
});
|
|
2316
|
+
const rendered = config.render ? config.render(state, timedOut) : { text: JSON.stringify(state, null, 2), isError: timedOut };
|
|
2317
|
+
return textResult(rendered.text, rendered.isError);
|
|
2318
|
+
} catch (err) {
|
|
2319
|
+
return textResult(`Wait failed: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
2288
2320
|
}
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2321
|
+
});
|
|
2322
|
+
}
|
|
2323
|
+
// src/tools/registry.ts
|
|
2324
|
+
function defineToolRegistry(input) {
|
|
2325
|
+
const defaults = [...input.defaults];
|
|
2326
|
+
const slots = new Set;
|
|
2327
|
+
for (const tool of defaults) {
|
|
2328
|
+
if (!tool.name)
|
|
2329
|
+
throw new Error("A default tool must carry a non-empty name");
|
|
2330
|
+
if (slots.has(tool.name))
|
|
2331
|
+
throw new Error(`Duplicate default tool name: ${tool.name}`);
|
|
2332
|
+
slots.add(tool.name);
|
|
2333
|
+
}
|
|
2334
|
+
const disabled = new Set;
|
|
2335
|
+
const replacements = new Map;
|
|
2336
|
+
const assertKnown = (name, verb) => {
|
|
2337
|
+
if (!slots.has(name)) {
|
|
2338
|
+
throw new Error(`Tool registry ${verb}("${name}") names no default; declared: ${[...slots].sort().join(", ") || "(none)"}`);
|
|
2296
2339
|
}
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
expiresAt: now() + freshness.freshnessMs
|
|
2304
|
-
};
|
|
2305
|
-
if (freshness.store)
|
|
2306
|
-
touch(clientId, refreshed);
|
|
2307
|
-
else
|
|
2308
|
-
dropCache(clientId);
|
|
2309
|
-
emit({ clientId, status: "revalidated", freshnessMs: freshness.freshnessMs });
|
|
2310
|
-
return refreshed.client;
|
|
2311
|
-
}
|
|
2312
|
-
if (response.status !== 200) {
|
|
2313
|
-
throw new Error(`CIMD endpoint returned HTTP ${response.status}`);
|
|
2314
|
-
}
|
|
2315
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
2316
|
-
if (!isJsonContentType(contentType)) {
|
|
2317
|
-
throw new Error("CIMD endpoint must return a JSON media type");
|
|
2340
|
+
};
|
|
2341
|
+
const builder = {
|
|
2342
|
+
replace(name, tool) {
|
|
2343
|
+
assertKnown(name, "replace");
|
|
2344
|
+
if (disabled.has(name)) {
|
|
2345
|
+
throw new Error(`Tool registry cannot replace "${name}": it is disabled`);
|
|
2318
2346
|
}
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
if (metadata.client_id !== clientId) {
|
|
2322
|
-
throw new Error("CIMD client_id does not exactly match the requested URL");
|
|
2347
|
+
if (replacements.has(name)) {
|
|
2348
|
+
throw new Error(`Tool registry already replaced "${name}"`);
|
|
2323
2349
|
}
|
|
2324
|
-
if (
|
|
2325
|
-
throw new Error(
|
|
2350
|
+
if (tool.name !== name) {
|
|
2351
|
+
throw new Error(`A replacement for "${name}" must carry that name, not "${tool.name}"`);
|
|
2326
2352
|
}
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2353
|
+
replacements.set(name, tool);
|
|
2354
|
+
return builder;
|
|
2355
|
+
},
|
|
2356
|
+
disable(name) {
|
|
2357
|
+
assertKnown(name, "disable");
|
|
2358
|
+
disabled.add(name);
|
|
2359
|
+
return builder;
|
|
2360
|
+
},
|
|
2361
|
+
build() {
|
|
2362
|
+
const tools = [];
|
|
2363
|
+
const seen = new Set;
|
|
2364
|
+
for (const tool of defaults) {
|
|
2365
|
+
if (disabled.has(tool.name))
|
|
2366
|
+
continue;
|
|
2367
|
+
const effective = replacements.get(tool.name) ?? tool;
|
|
2368
|
+
if (seen.has(effective.name)) {
|
|
2369
|
+
throw new Error(`Duplicate composed tool name: ${effective.name}`);
|
|
2370
|
+
}
|
|
2371
|
+
seen.add(effective.name);
|
|
2372
|
+
tools.push(effective);
|
|
2342
2373
|
}
|
|
2343
|
-
return
|
|
2344
|
-
} catch (error) {
|
|
2345
|
-
const message = error instanceof Error ? error.message : "CIMD metadata resolution failed";
|
|
2346
|
-
touch(clientId, {
|
|
2347
|
-
ok: false,
|
|
2348
|
-
message,
|
|
2349
|
-
expiresAt: now() + (policy.negativeTtlMs ?? 1e4)
|
|
2350
|
-
});
|
|
2351
|
-
throw error;
|
|
2374
|
+
return { tools, names: tools.map((tool) => tool.name) };
|
|
2352
2375
|
}
|
|
2353
2376
|
};
|
|
2354
|
-
return
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2377
|
+
return builder;
|
|
2378
|
+
}
|
|
2379
|
+
// src/tools/tool-logger.ts
|
|
2380
|
+
function createToolLogger(config = {}) {
|
|
2381
|
+
const log = config.log ?? ((line) => console.error(line));
|
|
2382
|
+
return {
|
|
2383
|
+
afterToolCall: ({ toolName, result, durationMs, context, endpoint }) => {
|
|
2384
|
+
const record = {
|
|
2385
|
+
tool: toolName,
|
|
2386
|
+
service: endpoint.serviceName,
|
|
2387
|
+
action: endpoint.key,
|
|
2388
|
+
ok: result.ok,
|
|
2389
|
+
code: result.ok ? undefined : result.code,
|
|
2390
|
+
durationMs: Math.round(durationMs),
|
|
2391
|
+
source: String(context.source),
|
|
2392
|
+
traceId: getTraceId()
|
|
2393
|
+
};
|
|
2394
|
+
const codePart = result.ok ? "" : ` ${result.code}`;
|
|
2395
|
+
log(`[tool] ${result.ok ? "ok" : "warn"} ${toolName} (${endpoint.serviceName}.${endpoint.key})${codePart} ${record.durationMs}ms`);
|
|
2396
|
+
config.onRecord?.(record);
|
|
2397
|
+
}
|
|
2361
2398
|
};
|
|
2362
2399
|
}
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2400
|
+
// src/tools/toolkit.ts
|
|
2401
|
+
function createToolkit() {
|
|
2402
|
+
return {
|
|
2403
|
+
mountMcp: (server, services, config) => mountMcp(server, services, config),
|
|
2404
|
+
mountAgent: (services, config) => mountAgent(services, config),
|
|
2405
|
+
buildMcpServer: (config, auth) => buildMcpServer(config, auth),
|
|
2406
|
+
createMcpHandler: (config) => createMcpHandler(config),
|
|
2407
|
+
createStdioMcpServer: (config) => createStdioMcpServer(config),
|
|
2408
|
+
createCli: (config) => createCli(config)
|
|
2409
|
+
};
|
|
2368
2410
|
}
|
|
2369
|
-
|
|
2370
|
-
|
|
2411
|
+
// src/tools/transfer/download-core.ts
|
|
2412
|
+
import { basename, extname } from "node:path";
|
|
2413
|
+
var DEFAULT_MAX_BYTES = 100 * 1024 * 1024;
|
|
2414
|
+
var MIME_EXT = {
|
|
2415
|
+
"image/png": ".png",
|
|
2416
|
+
"image/jpeg": ".jpg",
|
|
2417
|
+
"image/webp": ".webp",
|
|
2418
|
+
"image/gif": ".gif",
|
|
2419
|
+
"video/mp4": ".mp4",
|
|
2420
|
+
"video/webm": ".webm",
|
|
2421
|
+
"audio/mpeg": ".mp3",
|
|
2422
|
+
"audio/wav": ".wav",
|
|
2423
|
+
"audio/ogg": ".ogg"
|
|
2424
|
+
};
|
|
2425
|
+
|
|
2426
|
+
class DownloadOperationError extends Error {
|
|
2427
|
+
code;
|
|
2428
|
+
status;
|
|
2429
|
+
constructor(code, message, status) {
|
|
2430
|
+
super(message);
|
|
2431
|
+
this.code = code;
|
|
2432
|
+
this.status = status;
|
|
2433
|
+
this.name = "DownloadOperationError";
|
|
2434
|
+
}
|
|
2371
2435
|
}
|
|
2372
|
-
function
|
|
2436
|
+
function extensionFor(url, mime) {
|
|
2437
|
+
if (MIME_EXT[mime])
|
|
2438
|
+
return MIME_EXT[mime];
|
|
2373
2439
|
try {
|
|
2374
|
-
const
|
|
2375
|
-
if (
|
|
2376
|
-
return
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
if (url.protocol !== "http:")
|
|
2380
|
-
return false;
|
|
2381
|
-
if (applicationType === "web")
|
|
2382
|
-
return false;
|
|
2383
|
-
const host = url.hostname.replace(/^\[|\]$/g, "");
|
|
2384
|
-
return host === "127.0.0.1" || host === "::1" || host === "localhost";
|
|
2385
|
-
} catch {
|
|
2386
|
-
return false;
|
|
2387
|
-
}
|
|
2440
|
+
const ext = extname(new URL(url).pathname).toLowerCase();
|
|
2441
|
+
if (ext)
|
|
2442
|
+
return ext;
|
|
2443
|
+
} catch {}
|
|
2444
|
+
return ".bin";
|
|
2388
2445
|
}
|
|
2389
|
-
function
|
|
2446
|
+
function baseNameFor(url) {
|
|
2390
2447
|
try {
|
|
2391
|
-
const
|
|
2392
|
-
|
|
2393
|
-
return false;
|
|
2394
|
-
const host = url.hostname.replace(/^\[|\]$/g, "");
|
|
2395
|
-
return host === "127.0.0.1" || host === "::1" || host === "localhost";
|
|
2448
|
+
const path = new URL(url).pathname;
|
|
2449
|
+
return (basename(path, extname(path)) || "download").slice(0, 60);
|
|
2396
2450
|
} catch {
|
|
2397
|
-
return
|
|
2451
|
+
return "download";
|
|
2398
2452
|
}
|
|
2399
2453
|
}
|
|
2400
|
-
function
|
|
2401
|
-
const
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2454
|
+
async function runDownloadOperation(config) {
|
|
2455
|
+
const res = await fetchGuarded(new URL(config.url), config.allowPrivateHosts ?? false, {
|
|
2456
|
+
timeoutMs: config.timeoutMs,
|
|
2457
|
+
signal: config.signal
|
|
2458
|
+
});
|
|
2459
|
+
if (!res.ok) {
|
|
2460
|
+
throw new DownloadOperationError("DOWNLOAD_HTTP_ERROR", `HTTP ${res.status}`, 502);
|
|
2461
|
+
}
|
|
2462
|
+
const mimeType = mediaTypeEssence(res.headers.get("content-type")) || "application/octet-stream";
|
|
2463
|
+
const max = config.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
2464
|
+
const declared = Number(res.headers.get("content-length") ?? 0);
|
|
2465
|
+
if (declared > max) {
|
|
2466
|
+
await res.body?.cancel();
|
|
2467
|
+
throw new DownloadOperationError("DOWNLOAD_TOO_LARGE", `file exceeds the ${max}-byte cap`, 413);
|
|
2468
|
+
}
|
|
2469
|
+
const path = config.path ?? `${baseNameFor(config.url)}${extensionFor(config.url, mimeType)}`;
|
|
2470
|
+
return config.files.write(path, res.body ?? new Uint8Array, {
|
|
2471
|
+
maxBytes: max,
|
|
2472
|
+
mediaType: mimeType,
|
|
2473
|
+
signal: config.signal
|
|
2474
|
+
});
|
|
2405
2475
|
}
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2476
|
+
|
|
2477
|
+
// src/tools/transfer/managed-file-error.ts
|
|
2478
|
+
var SAFE_MANAGED_FILE_MESSAGES = {
|
|
2479
|
+
FILE_INVALID_PATH: "Invalid managed-file path",
|
|
2480
|
+
FILE_OUTSIDE_ROOT: "Managed-file path escapes its boundary",
|
|
2481
|
+
FILE_NOT_FOUND: "Managed file not found",
|
|
2482
|
+
FILE_NOT_REGULAR: "Managed path is not a regular file",
|
|
2483
|
+
FILE_INSPECTION_REJECTED: "Managed file rejected by inspection",
|
|
2484
|
+
FILE_TOO_LARGE: "Managed file exceeds the configured size limit",
|
|
2485
|
+
FILE_EXISTS: "Managed file already exists"
|
|
2486
|
+
};
|
|
2487
|
+
function managedFileAppError(error) {
|
|
2488
|
+
if (!(error instanceof ManagedFileError))
|
|
2489
|
+
return null;
|
|
2490
|
+
switch (error.code) {
|
|
2491
|
+
case "FILE_INVALID_PATH":
|
|
2492
|
+
case "FILE_OUTSIDE_ROOT":
|
|
2493
|
+
case "FILE_NOT_FOUND":
|
|
2494
|
+
case "FILE_NOT_REGULAR":
|
|
2495
|
+
case "FILE_INSPECTION_REJECTED":
|
|
2496
|
+
case "FILE_TOO_LARGE":
|
|
2497
|
+
case "FILE_EXISTS":
|
|
2498
|
+
return new AppError(error.code, SAFE_MANAGED_FILE_MESSAGES[error.code], STITCH_ERROR_STATUS[error.code]);
|
|
2499
|
+
case "FILE_IO_ERROR":
|
|
2500
|
+
return null;
|
|
2418
2501
|
}
|
|
2419
|
-
return new URLSearchParams(await req.text());
|
|
2420
2502
|
}
|
|
2421
|
-
function
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
return preRegistered.clientId === clientId ? preRegistered : null;
|
|
2441
|
-
const clientUrl = absoluteClientUrl(clientId);
|
|
2442
|
-
if (clientUrl) {
|
|
2443
|
-
if (!resolveCimd || clientUrl.protocol !== "https:")
|
|
2444
|
-
return null;
|
|
2445
|
-
try {
|
|
2446
|
-
return await resolveCimd(clientId);
|
|
2447
|
-
} catch {
|
|
2448
|
-
return null;
|
|
2449
|
-
}
|
|
2450
|
-
}
|
|
2451
|
-
const dcrClient = await dcrRegistry?.get(clientId);
|
|
2452
|
-
return dcrClient?.clientId === clientId ? dcrClient : null;
|
|
2453
|
-
};
|
|
2454
|
-
const redirectToClient = (uri, params) => redirectWith(uri, { ...params, iss: config.issuer });
|
|
2455
|
-
const metadataRoute = {
|
|
2456
|
-
method: "ALL",
|
|
2457
|
-
path: AS_METADATA_PATH,
|
|
2458
|
-
handler: (req) => {
|
|
2459
|
-
if (req.method === "OPTIONS")
|
|
2460
|
-
return new Response(null, { status: 204, headers: PUBLIC_CORS2 });
|
|
2461
|
-
return json({
|
|
2462
|
-
issuer: config.issuer,
|
|
2463
|
-
authorization_endpoint: `${config.issuer}${authorizePath}`,
|
|
2464
|
-
token_endpoint: `${config.issuer}${tokenPath}`,
|
|
2465
|
-
...dcrEnabled && { registration_endpoint: `${config.issuer}${registerPath}` },
|
|
2466
|
-
response_types_supported: ["code"],
|
|
2467
|
-
grant_types_supported: config.refreshTokens ? ["authorization_code", "refresh_token"] : ["authorization_code"],
|
|
2468
|
-
code_challenge_methods_supported: ["S256"],
|
|
2469
|
-
token_endpoint_auth_methods_supported: ["none"],
|
|
2470
|
-
...cimdEnabled && { client_id_metadata_document_supported: true },
|
|
2471
|
-
authorization_response_iss_parameter_supported: true,
|
|
2472
|
-
...config.scopesSupported && { scopes_supported: config.scopesSupported }
|
|
2473
|
-
});
|
|
2474
|
-
}
|
|
2475
|
-
};
|
|
2476
|
-
const registerRoute = {
|
|
2477
|
-
method: "ALL",
|
|
2478
|
-
path: registerPath,
|
|
2479
|
-
handler: async (req) => {
|
|
2480
|
-
if (req.method === "OPTIONS")
|
|
2481
|
-
return new Response(null, { status: 204, headers: PUBLIC_CORS2 });
|
|
2482
|
-
if (req.method !== "POST")
|
|
2483
|
-
return oauthError("invalid_request", "POST required", 405);
|
|
2484
|
-
const meta = await req.json().catch(() => null);
|
|
2485
|
-
if (!isRecord(meta)) {
|
|
2486
|
-
return oauthError("invalid_client_metadata", "Body must be a JSON object");
|
|
2487
|
-
}
|
|
2488
|
-
const rawAppType = meta.application_type;
|
|
2489
|
-
if (rawAppType !== undefined && rawAppType !== "native" && rawAppType !== "web") {
|
|
2490
|
-
return oauthError("invalid_client_metadata", "application_type must be 'native' or 'web'");
|
|
2491
|
-
}
|
|
2492
|
-
const applicationType = rawAppType;
|
|
2493
|
-
const redirectUris = meta.redirect_uris;
|
|
2494
|
-
const tokenEndpointAuthMethod = meta.token_endpoint_auth_method ?? "none";
|
|
2495
|
-
if (tokenEndpointAuthMethod !== "none") {
|
|
2496
|
-
return oauthError("invalid_client_metadata", 'Only public clients with token_endpoint_auth_method "none" are supported');
|
|
2497
|
-
}
|
|
2498
|
-
if (!Array.isArray(redirectUris) || redirectUris.length === 0 || !redirectUris.every((u) => typeof u === "string" && isRegistrableRedirectUri(u, applicationType))) {
|
|
2499
|
-
return oauthError("invalid_redirect_uri", applicationType === "web" ? "redirect_uris must be a non-empty array of absolute https URLs (a web client cannot register an http loopback URI)" : "redirect_uris must be a non-empty array of absolute https URLs (http is allowed only on a loopback host)");
|
|
2500
|
-
}
|
|
2501
|
-
if (!dcrRegistry)
|
|
2502
|
-
throw new Error("DCR route mounted without a DCR registry");
|
|
2503
|
-
const client = await dcrRegistry.register({
|
|
2504
|
-
redirectUris,
|
|
2505
|
-
clientName: typeof meta.client_name === "string" ? meta.client_name : undefined,
|
|
2506
|
-
tokenEndpointAuthMethod,
|
|
2507
|
-
...applicationType && { applicationType }
|
|
2508
|
-
});
|
|
2509
|
-
return json({
|
|
2510
|
-
client_id: client.clientId,
|
|
2511
|
-
redirect_uris: client.redirectUris,
|
|
2512
|
-
token_endpoint_auth_method: "none",
|
|
2513
|
-
grant_types: config.refreshTokens ? ["authorization_code", "refresh_token"] : ["authorization_code"],
|
|
2514
|
-
response_types: ["code"],
|
|
2515
|
-
...client.clientName && { client_name: client.clientName },
|
|
2516
|
-
...client.applicationType && { application_type: client.applicationType }
|
|
2517
|
-
}, 201);
|
|
2518
|
-
}
|
|
2519
|
-
};
|
|
2520
|
-
const authorizeRoute = {
|
|
2521
|
-
method: "ALL",
|
|
2522
|
-
path: authorizePath,
|
|
2523
|
-
handler: async (req) => {
|
|
2524
|
-
if (req.method === "OPTIONS")
|
|
2525
|
-
return new Response(null, { status: 204, headers: PUBLIC_CORS2 });
|
|
2526
|
-
const url = new URL(req.url);
|
|
2527
|
-
const p = url.searchParams;
|
|
2528
|
-
const clientId = p.get("client_id");
|
|
2529
|
-
const redirectUri = p.get("redirect_uri");
|
|
2530
|
-
const responseType = p.get("response_type");
|
|
2531
|
-
const codeChallenge = p.get("code_challenge");
|
|
2532
|
-
const codeChallengeMethod = p.get("code_challenge_method") ?? "S256";
|
|
2533
|
-
const resource = p.get("resource");
|
|
2534
|
-
const scope = p.get("scope") ?? undefined;
|
|
2535
|
-
const state = p.get("state") ?? undefined;
|
|
2536
|
-
if (!clientId || !redirectUri) {
|
|
2537
|
-
return oauthError("invalid_request", "client_id and redirect_uri are required");
|
|
2538
|
-
}
|
|
2539
|
-
const client = await resolveClient(clientId);
|
|
2540
|
-
if (!client)
|
|
2541
|
-
return oauthError("invalid_client", "Unknown client_id", 401);
|
|
2542
|
-
if (!client.redirectUris.includes(redirectUri)) {
|
|
2543
|
-
return oauthError("invalid_request", "redirect_uri does not match a registered URI");
|
|
2544
|
-
}
|
|
2545
|
-
if (responseType !== "code") {
|
|
2546
|
-
return redirectToClient(redirectUri, {
|
|
2547
|
-
error: "unsupported_response_type",
|
|
2548
|
-
...state && { state }
|
|
2549
|
-
});
|
|
2550
|
-
}
|
|
2551
|
-
if (!codeChallenge || codeChallengeMethod !== "S256") {
|
|
2552
|
-
return redirectToClient(redirectUri, {
|
|
2553
|
-
error: "invalid_request",
|
|
2554
|
-
error_description: "PKCE S256 code_challenge is required",
|
|
2555
|
-
...state && { state }
|
|
2556
|
-
});
|
|
2557
|
-
}
|
|
2558
|
-
if (!resource) {
|
|
2559
|
-
return redirectToClient(redirectUri, {
|
|
2560
|
-
error: "invalid_target",
|
|
2561
|
-
error_description: "resource parameter is required",
|
|
2562
|
-
...state && { state }
|
|
2563
|
-
});
|
|
2564
|
-
}
|
|
2565
|
-
if (resource !== config.resource) {
|
|
2566
|
-
return redirectToClient(redirectUri, {
|
|
2567
|
-
error: "invalid_target",
|
|
2568
|
-
error_description: "resource is not served by this authorization server",
|
|
2569
|
-
...state && { state }
|
|
2570
|
-
});
|
|
2503
|
+
function normalizeFileToolError(error) {
|
|
2504
|
+
return managedFileAppError(error) ?? normalizeError(error);
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
// src/tools/transfer/define-download-tool.ts
|
|
2508
|
+
function defineDownloadTool(config) {
|
|
2509
|
+
return defineRuntimeTool({
|
|
2510
|
+
name: config.name ?? "download",
|
|
2511
|
+
description: config.description,
|
|
2512
|
+
identity: managedNativeIdentity(config.identity, "POST"),
|
|
2513
|
+
input: config.input,
|
|
2514
|
+
output: ManagedFileRefSchema,
|
|
2515
|
+
transports: config.transports,
|
|
2516
|
+
annotations: config.annotations,
|
|
2517
|
+
present: config.present,
|
|
2518
|
+
handler: async (context) => {
|
|
2519
|
+
const url = await config.resolveUrl(context.input, context);
|
|
2520
|
+
if (!url) {
|
|
2521
|
+
throw new AppError("DOWNLOAD_NOT_FOUND", "Nothing to download", 404);
|
|
2571
2522
|
}
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2523
|
+
try {
|
|
2524
|
+
return await runDownloadOperation({
|
|
2525
|
+
url,
|
|
2526
|
+
files: config.files,
|
|
2527
|
+
path: config.pathFromInput?.(context.input),
|
|
2528
|
+
allowPrivateHosts: config.allowPrivateHosts,
|
|
2529
|
+
maxBytes: config.maxBytes,
|
|
2530
|
+
timeoutMs: config.timeoutMs,
|
|
2531
|
+
signal: context.signal
|
|
2579
2532
|
});
|
|
2580
|
-
}
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
clientId,
|
|
2584
|
-
redirectUri,
|
|
2585
|
-
scope: requestedScope,
|
|
2586
|
-
resource,
|
|
2587
|
-
state,
|
|
2588
|
-
isLoopbackRedirect: isLoopbackRedirectUri(redirectUri),
|
|
2589
|
-
...safeClientName(client.clientName) !== undefined && {
|
|
2590
|
-
clientName: safeClientName(client.clientName)
|
|
2591
|
-
},
|
|
2592
|
-
...client.applicationType !== undefined && {
|
|
2593
|
-
applicationType: client.applicationType
|
|
2594
|
-
},
|
|
2595
|
-
...absoluteClientUrl(clientId)?.protocol === "https:" && {
|
|
2596
|
-
clientOrigin: new URL(clientId).origin
|
|
2533
|
+
} catch (error) {
|
|
2534
|
+
if (error instanceof DownloadOperationError) {
|
|
2535
|
+
throw new AppError(error.code, `Download failed: ${error.message}`, error.status);
|
|
2597
2536
|
}
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
if (!Array.isArray(result.approvedScopes) || !result.approvedScopes.every((scope2) => typeof scope2 === "string")) {
|
|
2603
|
-
throw new Error("authorizeUser must return approvedScopes as an array of strings");
|
|
2604
|
-
}
|
|
2605
|
-
const approvedScopes = [...new Set(result.approvedScopes)];
|
|
2606
|
-
if (approvedScopes.some((approvedScope2) => !requestedScopes.includes(approvedScope2) || config.scopesSupported !== undefined && !supportedScopes.has(approvedScope2))) {
|
|
2607
|
-
throw new Error("authorizeUser approvedScopes must be a subset of requested scopes");
|
|
2537
|
+
const managedError = managedFileAppError(error);
|
|
2538
|
+
if (managedError)
|
|
2539
|
+
throw managedError;
|
|
2540
|
+
throw error;
|
|
2608
2541
|
}
|
|
2609
|
-
const approvedScope = approvedScopes.length > 0 ? approvedScopes.join(" ") : undefined;
|
|
2610
|
-
const code = randomUUID();
|
|
2611
|
-
await config.codes.save(code, {
|
|
2612
|
-
clientId,
|
|
2613
|
-
redirectUri,
|
|
2614
|
-
codeChallenge,
|
|
2615
|
-
codeChallengeMethod: "S256",
|
|
2616
|
-
resource,
|
|
2617
|
-
scope: approvedScope,
|
|
2618
|
-
userId: result.userId,
|
|
2619
|
-
expiresAt: Date.now() + AUTH_CODE_TTL_MS
|
|
2620
|
-
});
|
|
2621
|
-
return redirectToClient(redirectUri, { code, ...state && { state } });
|
|
2622
2542
|
}
|
|
2623
|
-
};
|
|
2624
|
-
const issueAccessToken = (userId, audience, clientId, scope) => signJwt({ scope, client_id: clientId }, config.signingSecret, {
|
|
2625
|
-
issuer: config.issuer,
|
|
2626
|
-
audience,
|
|
2627
|
-
subject: userId,
|
|
2628
|
-
expiresInSec: ttl
|
|
2629
2543
|
});
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
if (config.refreshTokens) {
|
|
2663
|
-
refreshToken = randomUUID();
|
|
2664
|
-
await config.refreshTokens.save(refreshToken, {
|
|
2665
|
-
clientId: data.clientId,
|
|
2666
|
-
resource: data.resource,
|
|
2667
|
-
scope: data.scope,
|
|
2668
|
-
userId: data.userId
|
|
2669
|
-
});
|
|
2670
|
-
}
|
|
2671
|
-
return json({
|
|
2672
|
-
access_token: accessToken,
|
|
2673
|
-
token_type: "Bearer",
|
|
2674
|
-
expires_in: ttl,
|
|
2675
|
-
...data.scope && { scope: data.scope },
|
|
2676
|
-
...refreshToken && { refresh_token: refreshToken }
|
|
2677
|
-
});
|
|
2678
|
-
}
|
|
2679
|
-
if (grantType === "refresh_token") {
|
|
2680
|
-
if (!config.refreshTokens) {
|
|
2681
|
-
return oauthError("unsupported_grant_type", "refresh_token grant is not enabled");
|
|
2682
|
-
}
|
|
2683
|
-
const token = form.get("refresh_token");
|
|
2684
|
-
const clientId = form.get("client_id");
|
|
2685
|
-
if (!token || !clientId)
|
|
2686
|
-
return oauthError("invalid_request", "refresh_token and client_id required");
|
|
2687
|
-
const data = await config.refreshTokens.take(token);
|
|
2688
|
-
if (!data)
|
|
2689
|
-
return oauthError("invalid_grant", "Unknown or used refresh token");
|
|
2690
|
-
if (data.clientId !== clientId)
|
|
2691
|
-
return oauthError("invalid_grant", "client_id mismatch");
|
|
2692
|
-
const accessToken = await issueAccessToken(data.userId, data.resource, data.clientId, data.scope);
|
|
2693
|
-
const newRefresh = randomUUID();
|
|
2694
|
-
await config.refreshTokens.save(newRefresh, data);
|
|
2695
|
-
return json({
|
|
2696
|
-
access_token: accessToken,
|
|
2697
|
-
token_type: "Bearer",
|
|
2698
|
-
expires_in: ttl,
|
|
2699
|
-
...data.scope && { scope: data.scope },
|
|
2700
|
-
refresh_token: newRefresh
|
|
2701
|
-
});
|
|
2544
|
+
}
|
|
2545
|
+
// src/tools/transfer/define-upload-tool.ts
|
|
2546
|
+
import { z as z6 } from "zod";
|
|
2547
|
+
|
|
2548
|
+
// src/tools/transfer/upload-core.ts
|
|
2549
|
+
function runUploadOperation(files, path, upload, signal) {
|
|
2550
|
+
signal?.throwIfAborted();
|
|
2551
|
+
return files.read(path, { signal }).then((source) => upload(source, signal));
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
// src/tools/transfer/define-upload-tool.ts
|
|
2555
|
+
var UploadToolInputSchema = z6.object({
|
|
2556
|
+
path: ManagedFilePathSchema.describe("Path relative to the configured file boundary")
|
|
2557
|
+
});
|
|
2558
|
+
function defineUploadTool(config) {
|
|
2559
|
+
return defineRuntimeTool({
|
|
2560
|
+
name: config.name ?? "upload",
|
|
2561
|
+
description: config.description,
|
|
2562
|
+
identity: managedNativeIdentity(config.identity, "POST"),
|
|
2563
|
+
input: UploadToolInputSchema,
|
|
2564
|
+
output: config.output,
|
|
2565
|
+
transports: config.transports,
|
|
2566
|
+
annotations: config.annotations,
|
|
2567
|
+
present: config.present,
|
|
2568
|
+
handler: async (context) => {
|
|
2569
|
+
try {
|
|
2570
|
+
return await runUploadOperation(config.files, context.input.path, (source) => config.upload(source, context), context.signal);
|
|
2571
|
+
} catch (error) {
|
|
2572
|
+
const managedError = managedFileAppError(error);
|
|
2573
|
+
if (managedError)
|
|
2574
|
+
throw managedError;
|
|
2575
|
+
throw error;
|
|
2702
2576
|
}
|
|
2703
|
-
return oauthError("unsupported_grant_type", `Unsupported grant_type: ${grantType ?? "none"}`);
|
|
2704
2577
|
}
|
|
2578
|
+
});
|
|
2579
|
+
}
|
|
2580
|
+
// src/tools/transfer/view-file.ts
|
|
2581
|
+
import { extname as extname2 } from "node:path";
|
|
2582
|
+
var MAX_INLINE_BYTES = 20 * 1024 * 1024;
|
|
2583
|
+
var EXT_MIME = {
|
|
2584
|
+
".png": "image/png",
|
|
2585
|
+
".jpg": "image/jpeg",
|
|
2586
|
+
".jpeg": "image/jpeg",
|
|
2587
|
+
".gif": "image/gif",
|
|
2588
|
+
".webp": "image/webp",
|
|
2589
|
+
".mp3": "audio/mpeg",
|
|
2590
|
+
".wav": "audio/wav",
|
|
2591
|
+
".ogg": "audio/ogg",
|
|
2592
|
+
".m4a": "audio/mp4",
|
|
2593
|
+
".mp4": "video/mp4",
|
|
2594
|
+
".webm": "video/webm",
|
|
2595
|
+
".mov": "video/quicktime"
|
|
2596
|
+
};
|
|
2597
|
+
async function fetchSource(pathOrUrl, options, maxBytes) {
|
|
2598
|
+
const extMime = EXT_MIME[extname2(pathOrUrl).toLowerCase()];
|
|
2599
|
+
if (pathOrUrl.startsWith("http://") || pathOrUrl.startsWith("https://")) {
|
|
2600
|
+
const url = new URL(pathOrUrl);
|
|
2601
|
+
const res = await fetchGuarded(url, options.allowPrivateHosts ?? false, {
|
|
2602
|
+
timeoutMs: options.timeoutMs,
|
|
2603
|
+
signal: options.signal
|
|
2604
|
+
});
|
|
2605
|
+
if (!res.ok)
|
|
2606
|
+
throw new AppError("VIEW_HTTP_ERROR", `HTTP ${res.status}`, 502);
|
|
2607
|
+
const headerMime = mediaTypeEssence(res.headers.get("content-type"));
|
|
2608
|
+
const mimeType = headerMime || extMime || "application/octet-stream";
|
|
2609
|
+
if (mimeType.startsWith("video/")) {
|
|
2610
|
+
await res.body?.cancel();
|
|
2611
|
+
return { video: true, mimeType, bytesRead: 0 };
|
|
2612
|
+
}
|
|
2613
|
+
const declared = Number(res.headers.get("content-length") ?? 0);
|
|
2614
|
+
if (declared > maxBytes) {
|
|
2615
|
+
await res.body?.cancel();
|
|
2616
|
+
return { tooLarge: true, mimeType, bytesRead: 0 };
|
|
2617
|
+
}
|
|
2618
|
+
const buffer = await readCapped(res, maxBytes);
|
|
2619
|
+
return buffer ? { buffer, mimeType, bytesRead: buffer.length } : { tooLarge: true, mimeType, bytesRead: maxBytes };
|
|
2620
|
+
}
|
|
2621
|
+
if (!options.files) {
|
|
2622
|
+
throw new AppError("FILE_INVALID_PATH", "local file paths are disabled — set files to allow them", 400);
|
|
2623
|
+
}
|
|
2624
|
+
options.signal?.throwIfAborted();
|
|
2625
|
+
if (!extMime) {
|
|
2626
|
+
throw new AppError("FILE_INSPECTION_REJECTED", "refusing to read a non-media file", 422);
|
|
2627
|
+
}
|
|
2628
|
+
try {
|
|
2629
|
+
const source = await options.files.read(pathOrUrl, {
|
|
2630
|
+
maxBytes,
|
|
2631
|
+
signal: options.signal
|
|
2632
|
+
});
|
|
2633
|
+
const buffer = Buffer.from(source.bytes);
|
|
2634
|
+
return {
|
|
2635
|
+
buffer,
|
|
2636
|
+
mimeType: source.ref.mediaType ?? extMime,
|
|
2637
|
+
bytesRead: buffer.length
|
|
2638
|
+
};
|
|
2639
|
+
} catch (error) {
|
|
2640
|
+
if (error instanceof ManagedFileError && error.code === "FILE_TOO_LARGE") {
|
|
2641
|
+
return { tooLarge: true, mimeType: extMime, bytesRead: maxBytes };
|
|
2642
|
+
}
|
|
2643
|
+
throw error;
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
async function resolveMediaWithinBudget(pathOrUrl, options, maxBytes) {
|
|
2647
|
+
if (maxBytes <= 0) {
|
|
2648
|
+
return {
|
|
2649
|
+
content: [
|
|
2650
|
+
{ type: "text", text: `[media] total inline budget exhausted — ${pathOrUrl}` }
|
|
2651
|
+
],
|
|
2652
|
+
bytes: 0
|
|
2653
|
+
};
|
|
2654
|
+
}
|
|
2655
|
+
const extMime = EXT_MIME[extname2(pathOrUrl).toLowerCase()];
|
|
2656
|
+
if (extMime?.startsWith("video/")) {
|
|
2657
|
+
return {
|
|
2658
|
+
content: [{ type: "text", text: `[video] ${extMime} — ${pathOrUrl}` }],
|
|
2659
|
+
bytes: 0
|
|
2660
|
+
};
|
|
2661
|
+
}
|
|
2662
|
+
const result = await fetchSource(pathOrUrl, options, maxBytes);
|
|
2663
|
+
if ("video" in result) {
|
|
2664
|
+
return {
|
|
2665
|
+
content: [{ type: "text", text: `[video] ${result.mimeType} — ${pathOrUrl}` }],
|
|
2666
|
+
bytes: result.bytesRead
|
|
2667
|
+
};
|
|
2668
|
+
}
|
|
2669
|
+
if ("tooLarge" in result) {
|
|
2670
|
+
return {
|
|
2671
|
+
content: [
|
|
2672
|
+
{ type: "text", text: `[${result.mimeType}] too large to inline — ${pathOrUrl}` }
|
|
2673
|
+
],
|
|
2674
|
+
bytes: result.bytesRead
|
|
2675
|
+
};
|
|
2676
|
+
}
|
|
2677
|
+
const { buffer, mimeType } = result;
|
|
2678
|
+
const sizeKb = (buffer.length / 1024).toFixed(0);
|
|
2679
|
+
if (mimeType.startsWith("image/")) {
|
|
2680
|
+
return {
|
|
2681
|
+
content: [
|
|
2682
|
+
{
|
|
2683
|
+
type: "image",
|
|
2684
|
+
data: buffer.toString("base64"),
|
|
2685
|
+
mimeType,
|
|
2686
|
+
annotations: { audience: ["user", "assistant"], priority: 0.9 }
|
|
2687
|
+
},
|
|
2688
|
+
{ type: "text", text: `[image] ${mimeType}, ${sizeKb}KB` }
|
|
2689
|
+
],
|
|
2690
|
+
bytes: buffer.length
|
|
2691
|
+
};
|
|
2692
|
+
}
|
|
2693
|
+
if (mimeType.startsWith("audio/")) {
|
|
2694
|
+
return {
|
|
2695
|
+
content: [
|
|
2696
|
+
{
|
|
2697
|
+
type: "audio",
|
|
2698
|
+
data: buffer.toString("base64"),
|
|
2699
|
+
mimeType,
|
|
2700
|
+
annotations: { audience: ["user", "assistant"], priority: 0.9 }
|
|
2701
|
+
},
|
|
2702
|
+
{ type: "text", text: `[audio] ${mimeType}, ${sizeKb}KB` }
|
|
2703
|
+
],
|
|
2704
|
+
bytes: buffer.length
|
|
2705
|
+
};
|
|
2706
|
+
}
|
|
2707
|
+
return {
|
|
2708
|
+
content: [{ type: "text", text: `[${mimeType}] ${sizeKb}KB — ${pathOrUrl}` }],
|
|
2709
|
+
bytes: buffer.length
|
|
2705
2710
|
};
|
|
2706
|
-
return dcrEnabled ? [metadataRoute, registerRoute, authorizeRoute, tokenRoute] : [metadataRoute, authorizeRoute, tokenRoute];
|
|
2707
2711
|
}
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2712
|
+
async function resolveMedia(pathOrUrl, options = {}) {
|
|
2713
|
+
return (await resolveMediaWithinBudget(pathOrUrl, options, MAX_INLINE_BYTES)).content;
|
|
2714
|
+
}
|
|
2715
|
+
async function runViewFileOperation(paths, options = {}, signal) {
|
|
2716
|
+
const list = Array.isArray(paths) ? paths : [paths];
|
|
2717
|
+
const content = [];
|
|
2718
|
+
const errors = [];
|
|
2719
|
+
let remainingBytes = MAX_INLINE_BYTES;
|
|
2720
|
+
for (const pathOrUrl of list) {
|
|
2721
|
+
signal?.throwIfAborted();
|
|
2722
|
+
try {
|
|
2723
|
+
const resolved = await resolveMediaWithinBudget(pathOrUrl, { ...options, signal }, remainingBytes);
|
|
2724
|
+
remainingBytes -= resolved.bytes;
|
|
2725
|
+
content.push(...resolved.content);
|
|
2726
|
+
} catch (error) {
|
|
2727
|
+
if (signal?.aborted)
|
|
2728
|
+
throw signal.reason ?? error;
|
|
2729
|
+
const message = normalizeFileToolError(error).message;
|
|
2730
|
+
errors.push({ path: pathOrUrl, message });
|
|
2731
|
+
content.push({ type: "text", text: `[${pathOrUrl}] Error: ${message}` });
|
|
2724
2732
|
}
|
|
2733
|
+
}
|
|
2734
|
+
return ViewFileOutputSchema.parse({ content, errors });
|
|
2735
|
+
}
|
|
2736
|
+
function mountViewFile(server, options = {}) {
|
|
2737
|
+
server.registerTool("view_file", {
|
|
2738
|
+
description: "View media (image, audio, video) by URL or local path — returns it as content you can SEE / HEAR. Pass several paths to view multiple files at once. Use it on a generation `output` url to inspect the result.",
|
|
2739
|
+
inputSchema: ViewFileInputSchema.shape,
|
|
2740
|
+
annotations: { title: "View Media", readOnlyHint: true, idempotentHint: true }
|
|
2741
|
+
}, async (args) => {
|
|
2742
|
+
const result = await runViewFileOperation(args.paths, options);
|
|
2743
|
+
return { content: result.content };
|
|
2744
|
+
});
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
// src/tools/transfer/define-view-file-tool.ts
|
|
2748
|
+
function defineViewFileTool(config) {
|
|
2749
|
+
const defaults = {
|
|
2750
|
+
mcp: (output) => ({ content: output.content }),
|
|
2751
|
+
agent: (output) => ({
|
|
2752
|
+
type: "content",
|
|
2753
|
+
value: output.content.map((part) => {
|
|
2754
|
+
if (part.type === "text")
|
|
2755
|
+
return part;
|
|
2756
|
+
return {
|
|
2757
|
+
type: "file",
|
|
2758
|
+
data: { type: "data", data: part.data },
|
|
2759
|
+
mediaType: part.mimeType
|
|
2760
|
+
};
|
|
2761
|
+
})
|
|
2762
|
+
})
|
|
2725
2763
|
};
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
}
|
|
2738
|
-
replacements.set(name, tool);
|
|
2739
|
-
return builder;
|
|
2740
|
-
},
|
|
2741
|
-
disable(name) {
|
|
2742
|
-
assertKnown(name, "disable");
|
|
2743
|
-
disabled.add(name);
|
|
2744
|
-
return builder;
|
|
2764
|
+
return defineRuntimeTool({
|
|
2765
|
+
name: config.name ?? "view_file",
|
|
2766
|
+
description: config.description,
|
|
2767
|
+
identity: managedNativeIdentity(config.identity, "GET"),
|
|
2768
|
+
input: ViewFileInputSchema,
|
|
2769
|
+
output: ViewFileOutputSchema,
|
|
2770
|
+
transports: config.transports,
|
|
2771
|
+
annotations: config.annotations ?? {
|
|
2772
|
+
title: "View Media",
|
|
2773
|
+
readOnlyHint: true,
|
|
2774
|
+
idempotentHint: true
|
|
2745
2775
|
},
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2776
|
+
present: { ...defaults, ...config.present },
|
|
2777
|
+
handler: (context) => runViewFileOperation(context.input.paths, {
|
|
2778
|
+
files: config.files,
|
|
2779
|
+
allowPrivateHosts: config.allowPrivateHosts,
|
|
2780
|
+
timeoutMs: config.timeoutMs
|
|
2781
|
+
}, context.signal)
|
|
2782
|
+
});
|
|
2783
|
+
}
|
|
2784
|
+
// src/tools/transfer/mount-download.ts
|
|
2785
|
+
import { z as z7 } from "zod";
|
|
2786
|
+
function mountDownload(server, config) {
|
|
2787
|
+
const name = config.name ?? "download";
|
|
2788
|
+
assertToolName(name, "<native>", "download");
|
|
2789
|
+
server.registerTool(name, { description: config.description, inputSchema: z7.object(config.inputSchema) }, async (rawArgs) => {
|
|
2790
|
+
const args = isRecord(rawArgs) ? rawArgs : {};
|
|
2791
|
+
try {
|
|
2792
|
+
const url = await config.resolveUrl(args);
|
|
2793
|
+
if (!url)
|
|
2794
|
+
return textResult("Nothing to download.", true);
|
|
2795
|
+
const result = await runDownloadOperation({
|
|
2796
|
+
url,
|
|
2797
|
+
files: config.files,
|
|
2798
|
+
path: config.pathFromArgs?.(args),
|
|
2799
|
+
allowPrivateHosts: config.allowPrivateHosts,
|
|
2800
|
+
maxBytes: config.maxBytes,
|
|
2801
|
+
timeoutMs: config.timeoutMs
|
|
2802
|
+
});
|
|
2803
|
+
return textResult(JSON.stringify(result, null, 2));
|
|
2804
|
+
} catch (error) {
|
|
2805
|
+
if (error instanceof DownloadOperationError) {
|
|
2806
|
+
return textResult(`Download failed [${error.code}]: ${error.message}`, true);
|
|
2758
2807
|
}
|
|
2759
|
-
|
|
2808
|
+
const normalized = normalizeFileToolError(error);
|
|
2809
|
+
return textResult(`Download failed [${normalized.code}]: ${normalized.message}`, true);
|
|
2760
2810
|
}
|
|
2761
|
-
};
|
|
2762
|
-
return builder;
|
|
2811
|
+
});
|
|
2763
2812
|
}
|
|
2764
|
-
// src/tools/
|
|
2765
|
-
function
|
|
2766
|
-
const
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
action: endpoint.key,
|
|
2773
|
-
ok: result.ok,
|
|
2774
|
-
code: result.ok ? undefined : result.code,
|
|
2775
|
-
durationMs: Math.round(durationMs),
|
|
2776
|
-
source: String(context.source),
|
|
2777
|
-
traceId: getTraceId()
|
|
2778
|
-
};
|
|
2779
|
-
const codePart = result.ok ? "" : ` ${result.code}`;
|
|
2780
|
-
log(`[tool] ${result.ok ? "ok" : "warn"} ${toolName} (${endpoint.serviceName}.${endpoint.key})${codePart} ${record.durationMs}ms`);
|
|
2781
|
-
config.onRecord?.(record);
|
|
2813
|
+
// src/tools/transfer/mount-upload.ts
|
|
2814
|
+
function mountUpload(server, config) {
|
|
2815
|
+
const name = config.name ?? "upload";
|
|
2816
|
+
assertToolName(name, "<native>", "upload");
|
|
2817
|
+
server.registerTool(name, {
|
|
2818
|
+
description: config.description,
|
|
2819
|
+
inputSchema: {
|
|
2820
|
+
path: ManagedFilePathSchema.describe("Path relative to the configured file boundary")
|
|
2782
2821
|
}
|
|
2783
|
-
}
|
|
2784
|
-
}
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2822
|
+
}, async (rawArgs) => {
|
|
2823
|
+
const args = isRecord(rawArgs) ? rawArgs : {};
|
|
2824
|
+
const path = typeof args.path === "string" ? args.path : "";
|
|
2825
|
+
if (!path)
|
|
2826
|
+
return textResult("Provide `path`.", true);
|
|
2827
|
+
try {
|
|
2828
|
+
const uploaded = await runUploadOperation(config.files, path, config.upload);
|
|
2829
|
+
return textResult(JSON.stringify(uploaded ?? null, null, 2));
|
|
2830
|
+
} catch (error) {
|
|
2831
|
+
const normalized = normalizeFileToolError(error);
|
|
2832
|
+
return textResult(`Upload failed [${normalized.code}]: ${normalized.message}`, true);
|
|
2833
|
+
}
|
|
2834
|
+
});
|
|
2795
2835
|
}
|
|
2796
2836
|
// src/tools/transports.ts
|
|
2797
|
-
var ALL_TRANSPORTS = ["HTTP", "MCP", "AGENT", "CLI"];
|
|
2798
|
-
var TOOL_TRANSPORTS2 = ["MCP", "AGENT", "CLI"];
|
|
2799
2837
|
function emptyCounts() {
|
|
2800
2838
|
return { HTTP: 0, MCP: 0, AGENT: 0, CLI: 0 };
|
|
2801
2839
|
}
|
|
@@ -2805,7 +2843,7 @@ function contractCounts(service) {
|
|
|
2805
2843
|
if (!method.expose || method.expose.includes("HTTP"))
|
|
2806
2844
|
counts.HTTP += 1;
|
|
2807
2845
|
}
|
|
2808
|
-
for (const transport of
|
|
2846
|
+
for (const transport of TOOL_TRANSPORTS) {
|
|
2809
2847
|
counts[transport] = collectToolSurface({
|
|
2810
2848
|
surface: { services: [service] },
|
|
2811
2849
|
transport,
|
|
@@ -2817,7 +2855,7 @@ function contractCounts(service) {
|
|
|
2817
2855
|
}
|
|
2818
2856
|
function runtimeCounts(runtimeTools) {
|
|
2819
2857
|
const counts = emptyCounts();
|
|
2820
|
-
for (const transport of
|
|
2858
|
+
for (const transport of TOOL_TRANSPORTS) {
|
|
2821
2859
|
counts[transport] = collectToolSurface({
|
|
2822
2860
|
surface: { runtimeTools },
|
|
2823
2861
|
transport,
|
|
@@ -2892,7 +2930,6 @@ export {
|
|
|
2892
2930
|
defineToolRegistry,
|
|
2893
2931
|
defineRuntimeTool,
|
|
2894
2932
|
defineDownloadTool,
|
|
2895
|
-
defineCliCommand,
|
|
2896
2933
|
defineAsyncOperationContract,
|
|
2897
2934
|
defineAsyncOperation,
|
|
2898
2935
|
createToolkit,
|
|
@@ -2904,7 +2941,6 @@ export {
|
|
|
2904
2941
|
createMcpHttpRoute,
|
|
2905
2942
|
createMcpHandler,
|
|
2906
2943
|
createLocalStepDurability,
|
|
2907
|
-
createCli,
|
|
2908
2944
|
createAsyncOperationSnapshotSchema,
|
|
2909
2945
|
composeToolLifecycle,
|
|
2910
2946
|
collectTools,
|