@zackbart/connecta 0.34.1 → 0.35.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 +363 -0
- package/README.md +62 -106
- package/SECURITY.md +3 -2
- package/bin/connecta.mjs +41 -16
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.d.ts +2 -1
- package/dist/access-tokens.js +66 -30
- package/dist/activity.d.ts +1 -1
- package/dist/activity.js +2 -0
- package/dist/agent-config.d.ts +23 -0
- package/dist/agent-config.js +59 -0
- package/dist/agent-description.js +16 -0
- package/dist/agent-identity.js +4 -4
- package/dist/agent-observation.js +39 -0
- package/dist/auth/clerk-sdk/client.d.ts +2 -0
- package/dist/auth/clerk-sdk/generated.js +554 -563
- package/dist/auth/clerk-transport.js +70 -3
- package/dist/auth/clerk.d.ts +13 -0
- package/dist/auth/clerk.js +653 -246
- package/dist/auth/cloudflare-access.js +46 -42
- package/dist/auth/downstream-client-metadata.js +2 -2
- package/dist/auth/downstream-oauth.js +2051 -1160
- package/dist/auth/downstream-revocation.js +88 -88
- package/dist/auth/native-call.js +20 -0
- package/dist/auth/native.js +184 -0
- package/dist/auth/oauth-start-reset.js +11 -5
- package/dist/auth/oauth-token-response.js +121 -102
- package/dist/auth/oauth-v2-migration.js +97 -78
- package/dist/auth/renewable-grant.js +10 -0
- package/dist/auth/static-oauth.js +663 -271
- package/dist/auth/vercel-agent.js +266 -0
- package/dist/auth-admission.js +204 -0
- package/dist/auth-elicitation.js +350 -241
- package/dist/authorization-handoff.js +164 -90
- package/dist/call-admission-context.js +3 -10
- package/dist/call-admission.js +2 -0
- package/dist/call-classification.js +9 -0
- package/dist/call-recovery.js +32 -13
- package/dist/canonical-json.js +8 -0
- package/dist/catalog-cache.js +369 -213
- package/dist/catalog-service.js +659 -530
- package/dist/catalog.js +5 -1
- package/dist/concurrency.js +0 -25
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +3 -1
- package/dist/config-schema.js +13 -3
- package/dist/config.d.ts +40 -6
- package/dist/config.js +40 -26
- package/dist/connector-caller.d.ts +4 -0
- package/dist/connector-caller.js +17 -4
- package/dist/connectors/api-connector.d.ts +12 -7
- package/dist/connectors/api-connector.js +142 -56
- package/dist/connectors/guarded-fetch.d.ts +51 -17
- package/dist/connectors/guarded-fetch.js +215 -121
- package/dist/connectors/negotiation-cache.js +20 -10
- package/dist/connectors/remote-mcp.d.ts +10 -30
- package/dist/connectors/remote-mcp.js +955 -563
- package/dist/d1.js +10 -243
- package/dist/derived-context.js +5 -0
- package/dist/describe-config.d.ts +3 -1
- package/dist/describe-config.js +2 -0
- package/dist/downstream-input.js +347 -260
- package/dist/errors.d.ts +87 -6
- package/dist/errors.js +114 -4
- package/dist/execute.js +87 -50
- package/dist/executor-admission.js +3 -0
- package/dist/executors/quickjs.js +163 -112
- package/dist/guarded-transport.js +50 -0
- package/dist/guest-runtime.js +1 -1
- package/dist/http/client.js +439 -0
- package/dist/http/errors.js +558 -0
- package/dist/http/fields.js +49 -0
- package/dist/http/hatch-paging.js +318 -0
- package/dist/http/hatch.js +1279 -0
- package/dist/http/json-schema.js +267 -0
- package/dist/http/node-transport.js +241 -0
- package/dist/http/oauth.js +162 -0
- package/dist/http/paging.js +228 -0
- package/dist/http/provider.js +221 -0
- package/dist/http/safety.js +82 -0
- package/dist/http/services.js +79 -0
- package/dist/http/tools.js +383 -0
- package/dist/http/vendor-index.js +138 -0
- package/dist/http/vendor.js +448 -0
- package/dist/http/write-request.js +66 -0
- package/dist/http-policy.d.ts +11 -0
- package/dist/http-policy.js +45 -0
- package/dist/inbound-credential.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -0
- package/dist/invocation-auth.js +30 -19
- package/dist/invocation.js +218 -85
- package/dist/mcp-result.js +0 -4
- package/dist/membership.d.ts +4 -0
- package/dist/meta-output.js +1 -1
- package/dist/meta-tools.js +531 -406
- package/dist/native-recovery.js +52 -0
- package/dist/native-tools.js +382 -0
- package/dist/node.d.ts +4 -3
- package/dist/node.js +45 -8
- package/dist/oauth-handoff.js +177 -104
- package/dist/oauth-partition.js +6 -20
- package/dist/oauth-sealing.js +6 -7
- package/dist/operation-vocabulary.js +101 -0
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +17 -4
- package/dist/operator-ui/agents.d.ts +5 -4
- package/dist/operator-ui/agents.js +17 -7
- package/dist/operator-ui/config-summary.js +5 -4
- package/dist/operator-ui/connection-map.js +7 -5
- package/dist/operator-ui/contract.d.ts +7 -3
- package/dist/operator-ui/endpoints.js +4 -13
- package/dist/operator-ui/fix-prompts.d.ts +1 -1
- package/dist/operator-ui/fix-prompts.js +0 -8
- package/dist/operator-ui/generated.js +3 -3
- package/dist/operator-ui/model.d.ts +2 -3
- package/dist/operator-ui/overview.js +5 -2
- package/dist/operator-ui/setup-commands.js +0 -4
- package/dist/operator-ui/view.d.ts +2 -25
- package/dist/operator-ui/view.js +2 -76
- package/dist/program-writes.js +47 -26
- package/dist/provider-call-facts.d.ts +115 -0
- package/dist/provider-call-facts.js +182 -0
- package/dist/provider-call.d.ts +112 -0
- package/dist/provider-call.js +271 -0
- package/dist/provider.d.ts +65 -31
- package/dist/provider.js +116 -39
- package/dist/providers/_shared/google/hatch.js +91 -0
- package/dist/providers/_shared/google/kit.js +537 -0
- package/dist/providers/_shared/google/result-size.js +15 -2
- package/dist/providers/_shared/google/service-account.d.ts +2 -1
- package/dist/providers/_shared/google/service-account.js +8 -4
- package/dist/providers/_shared/google/workspace.d.ts +6 -210
- package/dist/providers/_shared/google/workspace.js +6 -425
- package/dist/providers/_shared/rest/compact-index.js +86 -0
- package/dist/providers/_shared/rest/dispatch.js +3 -23
- package/dist/providers/_shared/rest/kit.js +546 -0
- package/dist/providers/_shared/rest/operation-index.js +230 -14
- package/dist/providers/_shared/rest/response-media.js +11 -0
- package/dist/providers/_shared/rest/tools.js +101 -78
- package/dist/providers/_shared/rest/value-safety.js +353 -195
- package/dist/providers/_shared/sensitive-fields.js +27 -0
- package/dist/providers/breeze/api.js +584 -0
- package/dist/providers/breeze/index.d.ts +1 -3
- package/dist/providers/breeze/index.js +67 -1420
- package/dist/providers/breeze/openapi.generated.js +60 -0
- package/dist/providers/breeze/skill.generated.js +2 -6
- package/dist/providers/breeze/tools.js +696 -0
- package/dist/providers/ccb/api.js +496 -0
- package/dist/providers/ccb/index.d.ts +15 -37
- package/dist/providers/ccb/index.js +77 -1702
- package/dist/providers/ccb/lib/constants.d.ts +34 -0
- package/dist/providers/ccb/lib/constants.js +74 -0
- package/dist/providers/ccb/lib/guide.js +9 -0
- package/dist/providers/ccb/lib/verdicts.js +387 -0
- package/dist/providers/ccb/openapi.generated.js +397 -0
- package/dist/providers/ccb/safety.js +295 -0
- package/dist/providers/ccb/skill.generated.js +2 -6
- package/dist/providers/ccb/tools.js +853 -0
- package/dist/providers/cloudflare/api.js +1066 -0
- package/dist/providers/cloudflare/index.d.ts +5 -22
- package/dist/providers/cloudflare/index.js +79 -197
- package/dist/providers/cloudflare/lib/config.d.ts +16 -0
- package/dist/providers/cloudflare/lib/config.js +2 -0
- package/dist/providers/cloudflare/lib/discovery.js +29 -0
- package/dist/providers/cloudflare/{graphql.js → lib/graphql.js} +1 -1
- package/dist/providers/cloudflare/lib/hatch.js +103 -0
- package/dist/providers/cloudflare/lib/path-bounds.js +28 -0
- package/dist/providers/cloudflare/lib/response-media.js +75 -0
- package/dist/providers/cloudflare/openapi.generated.js +4 -3
- package/dist/providers/cloudflare/{value-safety.js → safety.js} +487 -0
- package/dist/providers/cloudflare/skill.generated.js +2 -10
- package/dist/providers/cloudflare/tools.js +360 -0
- package/dist/providers/docs/api.js +51 -0
- package/dist/providers/docs/index.d.ts +1 -12
- package/dist/providers/docs/index.js +53 -1165
- package/dist/providers/docs/lib/constants.d.ts +18 -0
- package/dist/providers/docs/lib/constants.js +18 -0
- package/dist/providers/docs/lib/discovery.js +26 -0
- package/dist/providers/docs/lib/requests.js +48 -0
- package/dist/providers/docs/lib/schemas.js +168 -0
- package/dist/providers/docs/openapi.generated.js +16 -0
- package/dist/providers/docs/safety.js +54 -0
- package/dist/providers/docs/skill.generated.js +2 -6
- package/dist/providers/docs/tools.js +802 -0
- package/dist/providers/drive/api.js +177 -0
- package/dist/providers/drive/index.d.ts +1 -12
- package/dist/providers/drive/index.js +38 -1448
- package/dist/providers/drive/lib/constants.d.ts +6 -0
- package/dist/providers/drive/lib/constants.js +6 -0
- package/dist/providers/drive/lib/contracts.js +532 -0
- package/dist/providers/drive/lib/hatch.js +197 -0
- package/dist/providers/drive/openapi.generated.js +77 -0
- package/dist/providers/drive/safety.js +90 -0
- package/dist/providers/drive/skill.generated.js +2 -6
- package/dist/providers/drive/tools.js +1290 -0
- package/dist/providers/forms/api.js +71 -0
- package/dist/providers/forms/index.d.ts +1 -12
- package/dist/providers/forms/index.js +49 -1145
- package/dist/providers/forms/lib/constants.d.ts +14 -0
- package/dist/providers/forms/lib/constants.js +19 -0
- package/dist/providers/forms/lib/hatch.js +72 -0
- package/dist/providers/forms/openapi.generated.js +23 -0
- package/dist/providers/forms/safety.js +34 -0
- package/dist/providers/forms/skill.generated.js +2 -8
- package/dist/providers/forms/tools.js +1024 -0
- package/dist/providers/github/api.js +150 -0
- package/dist/providers/github/index.d.ts +4 -5
- package/dist/providers/github/index.js +30 -87
- package/dist/providers/github/{auth.d.ts → lib/auth.d.ts} +5 -3
- package/dist/providers/github/{auth.js → lib/auth.js} +40 -43
- package/dist/providers/github/lib/discovery.js +132 -0
- package/dist/providers/github/lib/operations.js +58 -0
- package/dist/providers/github/{scope.d.ts → lib/scope.d.ts} +2 -0
- package/dist/providers/github/{scope.js → lib/scope.js} +25 -1
- package/dist/providers/github/openapi.generated.js +1216 -0
- package/dist/providers/github/safety.js +216 -0
- package/dist/providers/github/skill.generated.js +2 -5
- package/dist/providers/github/tools.js +506 -0
- package/dist/providers/gmail/api.js +114 -0
- package/dist/providers/gmail/index.d.ts +1 -12
- package/dist/providers/gmail/index.js +73 -1621
- package/dist/providers/gmail/lib/constants.d.ts +13 -0
- package/dist/providers/gmail/lib/constants.js +16 -0
- package/dist/providers/gmail/lib/index.js +173 -0
- package/dist/providers/gmail/lib/schemas.js +176 -0
- package/dist/providers/gmail/lib/threading.js +120 -0
- package/dist/providers/gmail/openapi.generated.js +92 -0
- package/dist/providers/gmail/safety.js +21 -0
- package/dist/providers/gmail/skill.generated.js +2 -6
- package/dist/providers/gmail/tools.js +1435 -0
- package/dist/providers/overflow/api.js +1008 -0
- package/dist/providers/overflow/index.d.ts +1 -17
- package/dist/providers/overflow/index.js +60 -1488
- package/dist/providers/overflow/skill.generated.js +2 -5
- package/dist/providers/overflow/tools.js +873 -0
- package/dist/providers/planning-center/api.js +467 -0
- package/dist/providers/planning-center/index.d.ts +4 -35
- package/dist/providers/planning-center/index.js +69 -2445
- package/dist/providers/planning-center/lib/constants.d.ts +58 -0
- package/dist/providers/planning-center/lib/constants.js +83 -0
- package/dist/providers/planning-center/lib/discovery.js +40 -0
- package/dist/providers/planning-center/lib/guide.js +39 -0
- package/dist/providers/planning-center/lib/json-api.js +83 -0
- package/dist/providers/planning-center/lib/verdicts.js +1758 -0
- package/dist/providers/planning-center/openapi.generated.js +1764 -0
- package/dist/providers/planning-center/safety.js +188 -0
- package/dist/providers/planning-center/skill.generated.js +2 -27
- package/dist/providers/planning-center/tools.js +1010 -0
- package/dist/providers/sheets/api.js +133 -0
- package/dist/providers/sheets/index.d.ts +4 -16
- package/dist/providers/sheets/index.js +61 -1172
- package/dist/providers/sheets/lib/constants.d.ts +14 -0
- package/dist/providers/sheets/lib/constants.js +14 -0
- package/dist/providers/sheets/lib/hatch.js +163 -0
- package/dist/providers/sheets/lib/requests.js +105 -0
- package/dist/providers/sheets/lib/schemas.js +138 -0
- package/dist/providers/sheets/openapi.generated.js +30 -0
- package/dist/providers/sheets/safety.js +27 -0
- package/dist/providers/sheets/skill.generated.js +2 -7
- package/dist/providers/sheets/tools.js +734 -0
- package/dist/providers/slides/api.js +147 -0
- package/dist/providers/slides/index.d.ts +2 -10
- package/dist/providers/slides/index.js +48 -2555
- package/dist/providers/slides/lib/constants.d.ts +11 -0
- package/dist/providers/slides/lib/constants.js +11 -0
- package/dist/providers/slides/lib/requests.js +60 -0
- package/dist/providers/slides/lib/schemas.js +565 -0
- package/dist/providers/slides/openapi.generated.js +18 -0
- package/dist/providers/slides/skill.generated.js +2 -6
- package/dist/providers/slides/tools.js +1968 -0
- package/dist/providers/stripe/api.js +401 -0
- package/dist/providers/stripe/index.d.ts +5 -27
- package/dist/providers/stripe/index.js +74 -210
- package/dist/providers/stripe/lib/config.d.ts +4 -0
- package/dist/providers/stripe/lib/config.js +2 -0
- package/dist/providers/stripe/openapi.generated.js +4 -3
- package/dist/providers/stripe/{value-safety.js → safety.js} +5 -5
- package/dist/providers/stripe/skill.generated.js +2 -11
- package/dist/providers/stripe/tools.js +77 -0
- package/dist/providers/tithely/api.js +531 -0
- package/dist/providers/tithely/index.d.ts +17 -0
- package/dist/providers/tithely/index.js +86 -965
- package/dist/providers/tithely/skill.generated.js +2 -5
- package/dist/providers/tithely/tools.js +426 -0
- package/dist/providers/vercel/api.js +551 -0
- package/dist/providers/vercel/index.d.ts +9 -25
- package/dist/providers/vercel/index.js +66 -260
- package/dist/providers/vercel/lib/config.d.ts +2 -0
- package/dist/providers/vercel/lib/config.js +2 -0
- package/dist/providers/vercel/lib/index.js +117 -0
- package/dist/providers/vercel/lib/logs.js +100 -0
- package/dist/providers/vercel/lib/pin.js +270 -0
- package/dist/providers/vercel/openapi.generated.js +10 -5
- package/dist/providers/vercel/{value-safety.js → safety.js} +247 -38
- package/dist/providers/vercel/skill.generated.js +2 -7
- package/dist/providers/vercel/tools.js +642 -0
- package/dist/redaction-identity.js +47 -0
- package/dist/redaction-marker.js +125 -0
- package/dist/registry.d.ts +21 -15
- package/dist/registry.js +409 -376
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/actor-labels.js +1 -1
- package/dist/routes/connect.js +1 -1
- package/dist/routes/dispatch.js +103 -0
- package/dist/routes/mcp.js +170 -79
- package/dist/routes/oauth.js +3 -1
- package/dist/routes/operator.js +2 -2
- package/dist/routes/shared.d.ts +5 -4
- package/dist/routes/shared.js +244 -177
- package/dist/routes/skills.js +10 -8
- package/dist/routes/ui-config.js +27 -3
- package/dist/routes/ui-data.js +21 -20
- package/dist/routes/ui.js +1 -1
- package/dist/runtime/call-admission.js +0 -26
- package/dist/runtime/call-entry.js +30 -0
- package/dist/runtime/call.js +556 -0
- package/dist/runtime/connector-scope.js +7 -0
- package/dist/runtime/credential-probe.js +34 -0
- package/dist/runtime/executor.js +40 -0
- package/dist/runtime/native-storage.js +63 -0
- package/dist/runtime/registry.js +27 -0
- package/dist/runtime/request-scope.js +72 -0
- package/dist/runtime/run.js +15 -7
- package/dist/runtime/shared-read.js +34 -5
- package/dist/runtime/storage.js +34 -21
- package/dist/sent-secrets.d.ts +194 -37
- package/dist/sent-secrets.js +1743 -340
- package/dist/server.js +5 -56
- package/dist/skills.js +322 -222
- package/dist/sqlite.js +15 -14
- package/dist/storage/d1-copy.js +255 -0
- package/dist/storage/d1-driver.js +15 -0
- package/dist/storage/keys.js +28 -0
- package/dist/storage/sql-driver.js +88 -0
- package/dist/storage/sql-native.js +544 -0
- package/dist/storage/sql-schema.d.ts +6 -0
- package/dist/storage/sql-schema.js +15 -0
- package/dist/storage/sql.d.ts +14 -20
- package/dist/storage/sql.js +29 -535
- package/dist/timeout.js +0 -26
- package/dist/tool-safety.js +20 -10
- package/dist/types.d.ts +7 -109
- package/dist/upward.js +3 -0
- package/dist/usage-guide.js +3 -1
- package/dist/validate.d.ts +4 -2
- package/dist/validate.js +86 -9
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +312 -292
- package/dist/workspace-subject.js +5 -1
- package/dist/write-outcome.js +454 -34
- package/documentation/README.md +27 -7
- package/documentation/architecture.md +2547 -226
- package/documentation/auth.md +821 -98
- package/documentation/code-mode.md +49 -16
- package/documentation/deploying.md +164 -20
- package/documentation/hosted-mcp-migration.md +460 -0
- package/documentation/integrating.md +129 -144
- package/documentation/meta-tools.md +409 -32
- package/documentation/operating.md +27 -3
- package/documentation/operator-tests.md +3 -0
- package/documentation/operator-ui.md +68 -45
- package/documentation/provider-auth-migration.md +16 -63
- package/documentation/provider-migration-0.29.md +10 -5
- package/documentation/provider-migration-0.35.md +975 -0
- package/documentation/providers.md +965 -0
- package/examples/worker/AGENTS.md +23 -9
- package/examples/worker/README.md +254 -135
- package/examples/worker/alchemy.run.ts +116 -0
- package/examples/worker/gitignore +10 -0
- package/examples/worker/src/connecta.config.ts +23 -21
- package/examples/worker/wrangler.jsonc +14 -5
- package/guides/connecting-agents.md +157 -0
- package/guides/how-it-works.md +156 -0
- package/guides/safety-and-trust.md +153 -0
- package/package.json +27 -46
- package/templates/node/.env.example +3 -0
- package/templates/node/README.md +22 -2
- package/templates/node/docker-compose.yml +1 -0
- package/templates/node/package.json +3 -2
- package/templates/node/src/connecta.config.ts +8 -0
- package/dist/catalog-drift.d.ts +0 -138
- package/dist/catalog-drift.js +0 -578
- package/dist/providers/_shared/request-token.js +0 -83
- package/dist/providers/_shared/rest/operation-index.d.ts +0 -133
- package/dist/providers/_shared/rest/tools.d.ts +0 -141
- package/dist/providers/_shared/rest/value-safety.d.ts +0 -201
- package/dist/providers/basecamp/index.d.ts +0 -45
- package/dist/providers/basecamp/index.js +0 -997
- package/dist/providers/basecamp/skill.generated.js +0 -9
- package/dist/providers/cloudflare/rest.d.ts +0 -37
- package/dist/providers/cloudflare/rest.js +0 -1097
- package/dist/providers/github/hosted.js +0 -218
- package/dist/providers/github/rest.js +0 -321
- package/dist/providers/infisical/index.d.ts +0 -7
- package/dist/providers/infisical/index.js +0 -713
- package/dist/providers/infisical/skill.generated.js +0 -8
- package/dist/providers/intercom/index.d.ts +0 -24
- package/dist/providers/intercom/index.js +0 -121
- package/dist/providers/intercom/skill.generated.js +0 -8
- package/dist/providers/linear/index.d.ts +0 -55
- package/dist/providers/linear/index.js +0 -537
- package/dist/providers/linear/skill.generated.js +0 -8
- package/dist/providers/mixpanel/index.d.ts +0 -59
- package/dist/providers/mixpanel/index.js +0 -472
- package/dist/providers/mixpanel/operations.js +0 -20
- package/dist/providers/mixpanel/rest.js +0 -603
- package/dist/providers/mixpanel/skill.generated.js +0 -10
- package/dist/providers/mixpanel/value-safety.js +0 -16
- package/dist/providers/notion/index.d.ts +0 -67
- package/dist/providers/notion/index.js +0 -368
- package/dist/providers/notion/openapi.generated.js +0 -75
- package/dist/providers/notion/rest.d.ts +0 -24
- package/dist/providers/notion/rest.js +0 -1226
- package/dist/providers/notion/skill.generated.js +0 -11
- package/dist/providers/notion/value-safety.js +0 -56
- package/dist/providers/revenuecat/index.d.ts +0 -51
- package/dist/providers/revenuecat/index.js +0 -697
- package/dist/providers/revenuecat/skill.generated.js +0 -11
- package/dist/providers/stripe/rest.d.ts +0 -14
- package/dist/providers/stripe/rest.js +0 -402
- package/dist/providers/vercel/rest.d.ts +0 -20
- package/dist/providers/vercel/rest.js +0 -1020
- package/dist/providers/wispr-flow/index.d.ts +0 -14
- package/dist/providers/wispr-flow/index.js +0 -117
- package/dist/providers/wispr-flow/skill.generated.js +0 -8
- /package/dist/providers/github/{key.js → lib/key.js} +0 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Adapt only Connecta error envelopes; successful downstream payloads stay untouched. */
|
|
2
|
+
export function nativeRecovery(result, executeCode, wireName) {
|
|
3
|
+
const response = result;
|
|
4
|
+
const body = response?.structuredContent;
|
|
5
|
+
if (!response?.isError || !body || typeof body.error !== "object" || !body.error)
|
|
6
|
+
return result;
|
|
7
|
+
const error = { ...body.error };
|
|
8
|
+
const action = error.nextAction;
|
|
9
|
+
if (action?.tool === "search_tools" || (!action && error.code === "unknown_tool")) {
|
|
10
|
+
error.nextAction = {
|
|
11
|
+
host: "search",
|
|
12
|
+
arguments: { query: action?.arguments?.query ?? wireName },
|
|
13
|
+
purpose: action?.purpose,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
else if (action?.tool === "authorize_connector") {
|
|
17
|
+
const url = error.authorizationUrl ?? error.operatorUrl;
|
|
18
|
+
error.nextAction = url
|
|
19
|
+
? { url, purpose: "Give this handoff to the user or operator." }
|
|
20
|
+
: { operator: "Check connector authentication in deployment configuration." };
|
|
21
|
+
}
|
|
22
|
+
else if (action?.tool === "call_tool" || action?.tool === "call_destructive_tool") {
|
|
23
|
+
error.nextAction = wireName
|
|
24
|
+
? {
|
|
25
|
+
tool: wireName,
|
|
26
|
+
arguments: action.arguments?.args ?? {},
|
|
27
|
+
purpose: `${action.purpose ?? "Retry the original native call."}${action.arguments?.argsRedacted ? " Resend the original arguments; sensitive fields were omitted." : ""}`,
|
|
28
|
+
}
|
|
29
|
+
: { host: "search", purpose: action.purpose };
|
|
30
|
+
}
|
|
31
|
+
// These are Connecta-authored recovery fields, never arbitrary downstream data.
|
|
32
|
+
const rewrite = (text) => text.replace(/authorize_connector|search_tools|call_(?:destructive_)?tool|execute_code/g, (tool) => tool === "authorize_connector"
|
|
33
|
+
? "the returned authorization handoff"
|
|
34
|
+
: tool.startsWith("call_")
|
|
35
|
+
? "the original native tool"
|
|
36
|
+
: tool === "execute_code" && executeCode
|
|
37
|
+
? tool
|
|
38
|
+
: "your host's tool search");
|
|
39
|
+
for (const key of ["message", "instructions", "retry"])
|
|
40
|
+
if (typeof error[key] === "string")
|
|
41
|
+
error[key] = rewrite(error[key]);
|
|
42
|
+
if (wireName && typeof error.operation === "string" && typeof error.retry === "string")
|
|
43
|
+
error.retry = error.retry.replaceAll(rewrite(error.operation), wireName);
|
|
44
|
+
if (!executeCode && typeof error.instructions === "string") {
|
|
45
|
+
error.instructions = error.instructions.replace(/; connecta\.search[\s\S]*$/, ". Retry the original native call after recovery.");
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
...response,
|
|
49
|
+
structuredContent: { ...body, error },
|
|
50
|
+
content: [{ type: "text", text: JSON.stringify({ ...body, error }) }],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { duringViewEffect } from "./runtime/call.js";
|
|
2
|
+
import { RequestScope, requestOwner } from "./runtime/request-scope.js";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
import { upwardStep } from "./upward.js";
|
|
5
|
+
import { authCall, authInvoke, authFlow, authMethod, registerAuth, registerAuthMethod, authOwnMethod, } from "./auth/native.js";
|
|
6
|
+
import { runEdge } from "./runtime/run.js";
|
|
7
|
+
import { ProtocolError, } from "@modelcontextprotocol/server";
|
|
8
|
+
import { withAuthorizationHandoff } from "./authorization-handoff.js";
|
|
9
|
+
import { classifyCallError } from "./errors.js";
|
|
10
|
+
import { nativeRecovery } from "./native-recovery.js";
|
|
11
|
+
import { CatalogService } from "./catalog-service.js";
|
|
12
|
+
import { jsonResult } from "./meta-tools.js";
|
|
13
|
+
import { failureRecord, logFailure } from "./operator-record.js";
|
|
14
|
+
/**
|
|
15
|
+
* Longest native name. A client prefixes its own connection name, and common
|
|
16
|
+
* tool-name limits are 64 characters: Eve's `connecta__` and Claude Code's
|
|
17
|
+
* `mcp__connecta__` both fit beside 48.
|
|
18
|
+
*/
|
|
19
|
+
export const NATIVE_NAME_MAX = 48;
|
|
20
|
+
/** One page, no cursor: Eve 0.83 reads only the first `tools/list` page. */
|
|
21
|
+
const MAX_NATIVE_TOOLS = 1_000;
|
|
22
|
+
/** Native `tools/list` cache hint. Shorter than the meta surface's hour: the list follows downstream catalogs. */
|
|
23
|
+
export const NATIVE_TOOLS_LIST_TTL_MS = 300_000;
|
|
24
|
+
const HASH_CHARS = 6;
|
|
25
|
+
const SEPARATOR = "__";
|
|
26
|
+
/** No `__`, no leading `_`: the last `__` of a plain name is then always its separator. */
|
|
27
|
+
const PLAIN_TOOL = /^[A-Za-z0-9-](?:[A-Za-z0-9-]|_(?!_))*$/;
|
|
28
|
+
const NATIVE_NAME = new RegExp(`^[A-Za-z][A-Za-z0-9_-]{0,${NATIVE_NAME_MAX - 1}}$`);
|
|
29
|
+
/** Connecta's own tools on a native endpoint. Their names never contain `__`. */
|
|
30
|
+
const NATIVE_OWN_TOOLS = ["execute_code", "skills", "read_result"];
|
|
31
|
+
const enc = new TextEncoder();
|
|
32
|
+
function shortHash(text) {
|
|
33
|
+
return runEdge(shortHashNative(text));
|
|
34
|
+
}
|
|
35
|
+
const shortHashNative = /* @__PURE__ */ registerAuth(shortHash, function (text) {
|
|
36
|
+
return authFlow(function* () {
|
|
37
|
+
const digest = new Uint8Array(yield* upwardStep(authInvoke(crypto.subtle, "digest", ["SHA-256", enc.encode(text)])));
|
|
38
|
+
return [...digest.subarray(0, HASH_CHARS / 2)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
/**
|
|
42
|
+
* The connector half of a native name. Connector ids are `[a-z0-9_-]+`; one
|
|
43
|
+
* that does not start with a letter gets an uppercase `C`, which no connector
|
|
44
|
+
* id can contain, so the prefix never collides with another id.
|
|
45
|
+
*/
|
|
46
|
+
function connectorPart(connectorId) {
|
|
47
|
+
return /^[a-z]/.test(connectorId) ? connectorId : `C${connectorId}`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A pure function of the connector id and downstream tool name, so it is
|
|
51
|
+
* stable across catalog refreshes: `<connector>__<tool>` when that is exact,
|
|
52
|
+
* legal and short, otherwise its head plus a six-hex SHA-256 suffix of the
|
|
53
|
+
* original pair.
|
|
54
|
+
*/
|
|
55
|
+
export function nativeToolName(connectorId, toolName) {
|
|
56
|
+
return runEdge(nativeToolNameNative(connectorId, toolName));
|
|
57
|
+
}
|
|
58
|
+
const nativeToolNameNative = /* @__PURE__ */ registerAuth(nativeToolName, function (connectorId, toolName) {
|
|
59
|
+
return authFlow(function* () {
|
|
60
|
+
const tool = toolName.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
61
|
+
const plain = `${connectorPart(connectorId)}${SEPARATOR}${tool}`;
|
|
62
|
+
if (tool === toolName && PLAIN_TOOL.test(tool) && plain.length <= NATIVE_NAME_MAX)
|
|
63
|
+
return plain;
|
|
64
|
+
const head = plain.slice(0, NATIVE_NAME_MAX - HASH_CHARS - 1).replace(/_+$/, "");
|
|
65
|
+
return `${head}_${yield* upwardStep(authCall(shortHash, [`${connectorId}.${toolName}`]))}`;
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
/** Read or write, from the served classification only. */
|
|
69
|
+
function annotationsFor(tool) {
|
|
70
|
+
return tool.classification === "read"
|
|
71
|
+
? { readOnlyHint: true, destructiveHint: false, openWorldHint: true }
|
|
72
|
+
: { readOnlyHint: false, destructiveHint: true, openWorldHint: true };
|
|
73
|
+
}
|
|
74
|
+
/** Object arguments retain every applicable constraint, including local refs. */
|
|
75
|
+
function nativeInputSchema(schema) {
|
|
76
|
+
function supportsObject(value, seen = new Set()) {
|
|
77
|
+
if (value === true)
|
|
78
|
+
return true;
|
|
79
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || seen.has(value))
|
|
80
|
+
return false;
|
|
81
|
+
const node = value;
|
|
82
|
+
const next = new Set(seen).add(value);
|
|
83
|
+
if (node.type !== undefined &&
|
|
84
|
+
node.type !== "object" &&
|
|
85
|
+
!(Array.isArray(node.type) && node.type.includes("object")))
|
|
86
|
+
return false;
|
|
87
|
+
if (node.$ref !== undefined) {
|
|
88
|
+
if (typeof node.$ref !== "string" || !node.$ref.startsWith("#/"))
|
|
89
|
+
return false;
|
|
90
|
+
let target = schema;
|
|
91
|
+
for (const part of node.$ref.slice(2).split("/")) {
|
|
92
|
+
if (!target || typeof target !== "object")
|
|
93
|
+
return false;
|
|
94
|
+
target = target[part.replace(/~1/g, "/").replace(/~0/g, "~")];
|
|
95
|
+
}
|
|
96
|
+
if (!supportsObject(target, next))
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
for (const key of ["anyOf", "oneOf", "allOf"]) {
|
|
100
|
+
const branches = node[key];
|
|
101
|
+
if (Array.isArray(branches) &&
|
|
102
|
+
!(key === "allOf"
|
|
103
|
+
? branches.every((branch) => supportsObject(branch, next))
|
|
104
|
+
: branches.some((branch) => supportsObject(branch, next))))
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
if (!supportsObject(schema))
|
|
110
|
+
return undefined;
|
|
111
|
+
return schema.type === "object" ? schema : { ...schema, type: "object" };
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The downstream input schema, advertised verbatim. Validation accepts
|
|
115
|
+
* anything: invocation validates against the same catalog definition and
|
|
116
|
+
* returns its structured `invalid_args` with repair, as call_tool does.
|
|
117
|
+
*/
|
|
118
|
+
function passThroughSchema(schema) {
|
|
119
|
+
// Listing has already excluded connectors with unrepresentable schemas.
|
|
120
|
+
const json = schema === undefined ? { type: "object" } : nativeInputSchema(schema);
|
|
121
|
+
return {
|
|
122
|
+
"~standard": {
|
|
123
|
+
version: 1,
|
|
124
|
+
vendor: "connecta",
|
|
125
|
+
validate: (value) => ({ value }),
|
|
126
|
+
jsonSchema: { input: () => json, output: () => json },
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function describe(entry, executeCode) {
|
|
131
|
+
const lines = [entry.tool.description?.trim() || entry.tool.title || entry.tool.name];
|
|
132
|
+
if (executeCode)
|
|
133
|
+
lines.push(`Program address: ${entry.connector.id}.${entry.tool.name}`);
|
|
134
|
+
return lines.join("\n\n");
|
|
135
|
+
}
|
|
136
|
+
export class NativeSurface {
|
|
137
|
+
registry;
|
|
138
|
+
options;
|
|
139
|
+
catalog;
|
|
140
|
+
constructor(registry, options) {
|
|
141
|
+
this.registry = registry;
|
|
142
|
+
this.options = options;
|
|
143
|
+
this.catalog = new CatalogService(registry, options.baseUrl, {
|
|
144
|
+
requestScope: options.requestScope,
|
|
145
|
+
requestSignal: options.requestSignal,
|
|
146
|
+
probeTimeoutMs: options.probeTimeoutMs,
|
|
147
|
+
concurrency: options.discoveryConcurrency,
|
|
148
|
+
// Listing omits a read-only endpoint's writes, as search does.
|
|
149
|
+
readOnly: options.readOnly,
|
|
150
|
+
defer: options.defer,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Every listable native tool. A connector whose catalog fails is omitted
|
|
155
|
+
* whole, never partially, with an operator warning; two tools claiming one
|
|
156
|
+
* name are both omitted and stay reachable by address in execute_code.
|
|
157
|
+
*/
|
|
158
|
+
claims(connectors = this.registry.listConnectors()) {
|
|
159
|
+
return runEdge(authOwnMethod(NativeSurface.prototype, this, "claims", [connectors]));
|
|
160
|
+
}
|
|
161
|
+
list() {
|
|
162
|
+
return runEdge(authOwnMethod(NativeSurface.prototype, this, "list", []));
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The downstream tool behind a native name, from the catalogs whose
|
|
166
|
+
* connector prefix the name could carry, including truncated prefixes. Resolution sees
|
|
167
|
+
* writes on a read-only endpoint, so invocation refuses them by code.
|
|
168
|
+
*/
|
|
169
|
+
resolve(name) {
|
|
170
|
+
return runEdge(authOwnMethod(NativeSurface.prototype, this, "resolve", [name]));
|
|
171
|
+
}
|
|
172
|
+
/** A native call: the existing route its current verdict selects. */
|
|
173
|
+
call(name, args, request) {
|
|
174
|
+
return runEdge(authOwnMethod(NativeSurface.prototype, this, "call", [name, args, request]));
|
|
175
|
+
}
|
|
176
|
+
registerTool(server, name, entry) {
|
|
177
|
+
server.registerTool(name, {
|
|
178
|
+
...(entry?.tool.title ? { title: entry.tool.title } : {}),
|
|
179
|
+
...(entry ? { description: describe(entry, this.options.executeCode) } : {}),
|
|
180
|
+
inputSchema: passThroughSchema(entry?.tool.inputSchema),
|
|
181
|
+
...(entry ? { annotations: annotationsFor(entry.tool) } : {}),
|
|
182
|
+
...(entry ? { _meta: { "dev.connecta/address": `${entry.connector.id}.${entry.tool.name}` } } : {}),
|
|
183
|
+
}, authMethod((args, request) => Effect.gen({ self: this }, function* () {
|
|
184
|
+
const result = yield* authInvoke(this, "call", [name, args ?? {}, request]);
|
|
185
|
+
return nativeRecovery(result, this.options.executeCode, name);
|
|
186
|
+
})));
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Register what this request needs before the SDK dispatches it: the whole
|
|
190
|
+
* listing for `tools/list`, the one called name for `tools/call` (resolved
|
|
191
|
+
* when it runs, so a catalog failure comes back as Connecta's structured
|
|
192
|
+
* error rather than "tool not found"), nothing otherwise.
|
|
193
|
+
*/
|
|
194
|
+
register(server, hint) {
|
|
195
|
+
return runEdge(authOwnMethod(NativeSurface.prototype, this, "register", [server, hint]));
|
|
196
|
+
}
|
|
197
|
+
static {
|
|
198
|
+
registerAuthMethod(NativeSurface.prototype, "register", function (server, hint) {
|
|
199
|
+
const self = this;
|
|
200
|
+
return duringViewEffect({
|
|
201
|
+
requestScope: this.options.requestScope,
|
|
202
|
+
signal: this.options.requestSignal,
|
|
203
|
+
}, () => authFlow(function* () {
|
|
204
|
+
if (hint.method === "tools/call") {
|
|
205
|
+
if (NATIVE_NAME.test(hint.name) && !NATIVE_OWN_TOOLS.includes(hint.name))
|
|
206
|
+
self.registerTool(server, hint.name);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (hint.method !== "tools/list")
|
|
210
|
+
return;
|
|
211
|
+
const tools = yield* upwardStep(authInvoke(self, "list", []));
|
|
212
|
+
if (tools.length > MAX_NATIVE_TOOLS) {
|
|
213
|
+
logFailure(self.options.logger, "native tools/list refused a pool over the 1,000-tool native cap", failureRecord({}));
|
|
214
|
+
server.server.setRequestHandler("tools/list", () => {
|
|
215
|
+
throw new ProtocolError(-32603, `This endpoint has ${tools.length} tools, over the ${MAX_NATIVE_TOOLS}-tool native listing cap. Ask the operator to narrow the pool or use the meta surface.`);
|
|
216
|
+
});
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
for (const entry of tools)
|
|
220
|
+
self.registerTool(server, entry.name, entry);
|
|
221
|
+
})).pipe(Effect.provideService(RequestScope, requestOwner(this.options.requestScope)));
|
|
222
|
+
}, NativeSurface.prototype.register);
|
|
223
|
+
registerAuthMethod(NativeSurface.prototype, "call", function (name, args, request) {
|
|
224
|
+
const self = this;
|
|
225
|
+
return duringViewEffect({
|
|
226
|
+
requestScope: this.options.requestScope,
|
|
227
|
+
signal: this.options.requestSignal,
|
|
228
|
+
}, () => authFlow(function* () {
|
|
229
|
+
self.options.bind(request);
|
|
230
|
+
const target = yield* upwardStep(authInvoke(self, "resolve", [name]));
|
|
231
|
+
if (target.kind !== "found") {
|
|
232
|
+
const failure = target.kind === "missing" ? target.failure : undefined;
|
|
233
|
+
const error = failure
|
|
234
|
+
? {
|
|
235
|
+
...classifyCallError(failure.cause, "catalog_lookup_failed"),
|
|
236
|
+
connector: failure.connector.id,
|
|
237
|
+
message: `Connector "${failure.connector.id}" catalog lookup failed.`,
|
|
238
|
+
}
|
|
239
|
+
: {
|
|
240
|
+
code: "unknown_tool",
|
|
241
|
+
message: target.kind === "ambiguous"
|
|
242
|
+
? `Ambiguous native name "${name}". Ask the operator to rename a claimant.`
|
|
243
|
+
: `Unknown native tool "${name}".`,
|
|
244
|
+
retryable: false,
|
|
245
|
+
};
|
|
246
|
+
if (failure && (error.code === "auth_required" || error.code === "downstream_oauth_required")) {
|
|
247
|
+
error.nextAction = {
|
|
248
|
+
tool: "authorize_connector",
|
|
249
|
+
arguments: { connector: failure.connector.id },
|
|
250
|
+
operatorHandoff: "Give the returned handoff to the operator.",
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const result = jsonResult({
|
|
254
|
+
ok: false,
|
|
255
|
+
error: yield* upwardStep(authCall(withAuthorizationHandoff, [self.options.requestScope, error])),
|
|
256
|
+
});
|
|
257
|
+
result.isError = true;
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
const address = `${target.entry.connector.id}.${target.entry.tool.name}`;
|
|
261
|
+
return yield* upwardStep(authInvoke(self.options, "route", [
|
|
262
|
+
target.entry.tool.classification === "read" ? "call_tool" : "call_destructive_tool",
|
|
263
|
+
{ address, args },
|
|
264
|
+
request,
|
|
265
|
+
]));
|
|
266
|
+
})).pipe(Effect.provideService(RequestScope, requestOwner(this.options.requestScope)));
|
|
267
|
+
}, NativeSurface.prototype.call);
|
|
268
|
+
registerAuthMethod(NativeSurface.prototype, "resolve", function (name) {
|
|
269
|
+
const self = this;
|
|
270
|
+
return duringViewEffect({
|
|
271
|
+
requestScope: this.options.requestScope,
|
|
272
|
+
signal: this.options.requestSignal,
|
|
273
|
+
}, () => authFlow(function* () {
|
|
274
|
+
const prefixed = self.registry.listConnectors().filter((connector) => {
|
|
275
|
+
const prefix = `${connectorPart(connector.id)}${SEPARATOR}`;
|
|
276
|
+
// A hashed name can truncate even the connector prefix, and strips
|
|
277
|
+
// trailing underscores from its head. Include every possible claimant.
|
|
278
|
+
const head = prefix.slice(0, NATIVE_NAME_MAX - HASH_CHARS - 1).replace(/_+$/, "");
|
|
279
|
+
// A hashed name carries a stripped head only as exactly `${head}_` + six hex digits.
|
|
280
|
+
return (name.startsWith(prefix) ||
|
|
281
|
+
(name.length === head.length + 1 + HASH_CHARS &&
|
|
282
|
+
name.startsWith(`${head}_`) &&
|
|
283
|
+
/^[0-9a-f]+$/.test(name.slice(-HASH_CHARS))));
|
|
284
|
+
});
|
|
285
|
+
const { loaded, byName } = yield* upwardStep(authInvoke(self, "claims", [prefixed]));
|
|
286
|
+
const found = byName.get(name);
|
|
287
|
+
if (found === null)
|
|
288
|
+
return { kind: "ambiguous" };
|
|
289
|
+
// An unavailable possible claimant cannot prove uniqueness.
|
|
290
|
+
for (const entry of loaded)
|
|
291
|
+
if ("error" in entry)
|
|
292
|
+
return { kind: "missing", failure: { connector: entry.connector, cause: entry.error } };
|
|
293
|
+
return found ? { kind: "found", entry: found } : { kind: "missing" };
|
|
294
|
+
})).pipe(Effect.provideService(RequestScope, requestOwner(this.options.requestScope)));
|
|
295
|
+
}, NativeSurface.prototype.resolve);
|
|
296
|
+
registerAuthMethod(NativeSurface.prototype, "list", function () {
|
|
297
|
+
const self = this;
|
|
298
|
+
return duringViewEffect({
|
|
299
|
+
requestScope: this.options.requestScope,
|
|
300
|
+
signal: this.options.requestSignal,
|
|
301
|
+
}, () => authFlow(function* () {
|
|
302
|
+
const { loaded, byName } = yield* upwardStep(authInvoke(self, "claims", []));
|
|
303
|
+
for (const entry of loaded) {
|
|
304
|
+
// Validate the whole connector before registering any of its tools.
|
|
305
|
+
const tool = "tools" in entry
|
|
306
|
+
? entry.tools.find((tool) => tool.inputSchema !== undefined && !nativeInputSchema(tool.inputSchema))
|
|
307
|
+
: undefined;
|
|
308
|
+
if (!("error" in entry) && !tool)
|
|
309
|
+
continue;
|
|
310
|
+
self.options.catalogErrors.push(entry.connector.id);
|
|
311
|
+
logFailure(self.options.logger, "native catalog unavailable", failureRecord({ connector: entry.connector.id, ...(tool ? { tool } : {}) }, "error" in entry ? entry.error : undefined));
|
|
312
|
+
}
|
|
313
|
+
return [...byName.values()].filter((entry) => entry !== null &&
|
|
314
|
+
!self.options.catalogErrors.includes(entry.connector.id) &&
|
|
315
|
+
(!(self.options.readOnly || entry.connector.readOnly) || entry.tool.classification === "read"));
|
|
316
|
+
})).pipe(Effect.provideService(RequestScope, requestOwner(this.options.requestScope)));
|
|
317
|
+
}, NativeSurface.prototype.list);
|
|
318
|
+
registerAuthMethod(NativeSurface.prototype, "claims", function (connectors) {
|
|
319
|
+
const self = this;
|
|
320
|
+
return duringViewEffect({
|
|
321
|
+
requestScope: this.options.requestScope,
|
|
322
|
+
signal: this.options.requestSignal,
|
|
323
|
+
}, () => authFlow(function* () {
|
|
324
|
+
const loaded = yield* upwardStep(authInvoke(self.catalog, "listCatalogs", [true, connectors ?? self.registry.listConnectors()]));
|
|
325
|
+
const byName = new Map();
|
|
326
|
+
for (const entry of loaded) {
|
|
327
|
+
if (!("tools" in entry))
|
|
328
|
+
continue;
|
|
329
|
+
for (const tool of entry.tools) {
|
|
330
|
+
const name = yield* upwardStep(authCall(nativeToolName, [entry.connector.id, tool.name]));
|
|
331
|
+
if (byName.has(name)) {
|
|
332
|
+
byName.set(name, null);
|
|
333
|
+
logFailure(self.options.logger, "native name collision", failureRecord({ connector: entry.connector.id, tool }));
|
|
334
|
+
}
|
|
335
|
+
else
|
|
336
|
+
byName.set(name, { name, connector: entry.connector, tool });
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return { loaded, byName };
|
|
340
|
+
})).pipe(Effect.provideService(RequestScope, requestOwner(this.options.requestScope)));
|
|
341
|
+
}, NativeSurface.prototype.claims);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
/** Read the method and tool name a request carries, for {@link NativeSurface.register}. */
|
|
345
|
+
export function nativeRequestHint(request, modern) {
|
|
346
|
+
return runEdge(nativeRequestHintNative(request, modern));
|
|
347
|
+
}
|
|
348
|
+
const nativeRequestHintNative = /* @__PURE__ */ registerAuth(nativeRequestHint, function (request, modern) {
|
|
349
|
+
return authFlow(function* () {
|
|
350
|
+
let method;
|
|
351
|
+
let name;
|
|
352
|
+
if (modern) {
|
|
353
|
+
// The SDK checked both headers against the body before calling the factory.
|
|
354
|
+
method = request.headers.get("Mcp-Method")?.trim();
|
|
355
|
+
const header = request.headers.get("Mcp-Name")?.trim();
|
|
356
|
+
const encoded = header && /^=\?base64\?(.*)\?=$/.exec(header);
|
|
357
|
+
try {
|
|
358
|
+
name = encoded
|
|
359
|
+
? new TextDecoder().decode(Uint8Array.from(atob(encoded[1]), (c) => c.charCodeAt(0)))
|
|
360
|
+
: header;
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
name = undefined;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
try {
|
|
368
|
+
const body = (yield* upwardStep(authInvoke(request.clone(), "json", [])));
|
|
369
|
+
method = body?.method;
|
|
370
|
+
name = body?.params?.name;
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
return { method: "other" };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (method === "tools/list")
|
|
377
|
+
return { method };
|
|
378
|
+
if (method === "tools/call" && typeof name === "string")
|
|
379
|
+
return { method, name };
|
|
380
|
+
return { method: "other" };
|
|
381
|
+
});
|
|
382
|
+
});
|
package/dist/node.d.ts
CHANGED
|
@@ -2,9 +2,11 @@ import type { Server } from "node:http";
|
|
|
2
2
|
import type { Connecta } from "./index.js";
|
|
3
3
|
export interface ListenOptions {
|
|
4
4
|
port: number;
|
|
5
|
+
/** Explicit HTTP(S) CONNECT proxy for owned outbound sends. Never read from the environment. */
|
|
6
|
+
proxy?: string;
|
|
5
7
|
/** Interface to bind. Defaults to Node's own (all interfaces). */
|
|
6
8
|
host?: string;
|
|
7
|
-
/**
|
|
9
|
+
/** Pre-auth buffer cap, independent of the authenticated MCP cap. Default 4 MiB. */
|
|
8
10
|
maxBodyBytes?: number;
|
|
9
11
|
/**
|
|
10
12
|
* Stop accepting connections on SIGTERM/SIGINT, let in-flight requests
|
|
@@ -19,7 +21,6 @@ export interface ListenOptions {
|
|
|
19
21
|
/**
|
|
20
22
|
* Serve a Connecta over node:http. Thin adapter: IncomingMessage → Request,
|
|
21
23
|
* Response → ServerResponse, plus the process-level concerns a long-running
|
|
22
|
-
* container needs (body cap, response streaming, graceful shutdown).
|
|
23
|
-
* dependencies beyond node core.
|
|
24
|
+
* container needs (body cap, response streaming, graceful shutdown). Outbound HTTP uses an owned undici dispatcher configured here.
|
|
24
25
|
*/
|
|
25
26
|
export declare function listen(connecta: Connecta, portOrOptions: number | ListenOptions): Server;
|
package/dist/node.js
CHANGED
|
@@ -2,8 +2,11 @@ import { createServer } from "node:http";
|
|
|
2
2
|
import { Readable } from "node:stream";
|
|
3
3
|
import { pipeline } from "node:stream/promises";
|
|
4
4
|
import { failureRecord, logFailure } from "./operator-record.js";
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
import { configureGuardedTransport } from "./guarded-transport.js";
|
|
6
|
+
import { createNodeGuardedTransport } from "./http/node-transport.js";
|
|
7
|
+
import { CONFIG_DEFAULTS } from "./config-defaults.js";
|
|
8
|
+
/** The adapter buffers before auth, bounded by the same default as the core. */
|
|
9
|
+
const DEFAULT_MAX_BODY_BYTES = CONFIG_DEFAULTS.admission.requests.maxBodyBytes;
|
|
7
10
|
const BODY_TOO_LARGE = Symbol("body-too-large");
|
|
8
11
|
async function toRequest(req, maxBodyBytes, signal) {
|
|
9
12
|
const host = req.headers.host ?? "localhost";
|
|
@@ -55,11 +58,11 @@ async function writeResponse(res, response) {
|
|
|
55
58
|
/**
|
|
56
59
|
* Serve a Connecta over node:http. Thin adapter: IncomingMessage → Request,
|
|
57
60
|
* Response → ServerResponse, plus the process-level concerns a long-running
|
|
58
|
-
* container needs (body cap, response streaming, graceful shutdown).
|
|
59
|
-
* dependencies beyond node core.
|
|
61
|
+
* container needs (body cap, response streaming, graceful shutdown). Outbound HTTP uses an owned undici dispatcher configured here.
|
|
60
62
|
*/
|
|
61
63
|
export function listen(connecta, portOrOptions) {
|
|
62
64
|
const opts = typeof portOrOptions === "number" ? { port: portOrOptions } : portOrOptions;
|
|
65
|
+
configureGuardedTransport(createNodeGuardedTransport(opts.proxy));
|
|
63
66
|
const maxBodyBytes = opts.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
|
|
64
67
|
// Node has no ExecutionContext, so deferred work (activity sinks) would
|
|
65
68
|
// otherwise be an untracked floating promise. Track it here and drain it on
|
|
@@ -71,7 +74,31 @@ export function listen(connecta, portOrOptions) {
|
|
|
71
74
|
void promise.finally(() => pending.delete(promise));
|
|
72
75
|
},
|
|
73
76
|
};
|
|
77
|
+
const awaitingRequest = new Set();
|
|
78
|
+
const activeResponses = new Map();
|
|
79
|
+
let shuttingDown = false;
|
|
74
80
|
const server = createServer((req, res) => {
|
|
81
|
+
awaitingRequest.delete(req.socket);
|
|
82
|
+
const socket = req.socket;
|
|
83
|
+
activeResponses.set(socket, (activeResponses.get(socket) ?? 0) + 1);
|
|
84
|
+
const finished = () => {
|
|
85
|
+
res.removeListener("finish", finished);
|
|
86
|
+
res.removeListener("close", finished);
|
|
87
|
+
const remaining = (activeResponses.get(socket) ?? 1) - 1;
|
|
88
|
+
if (remaining > 0)
|
|
89
|
+
activeResponses.set(socket, remaining);
|
|
90
|
+
else {
|
|
91
|
+
activeResponses.delete(socket);
|
|
92
|
+
// finish means the response was handed to the socket; destroySoon
|
|
93
|
+
// flushes its queued bytes before closing, including an SSE trailer.
|
|
94
|
+
// Count pipelined responses so an earlier finish cannot cut a later
|
|
95
|
+
// admitted response off. Before shutdown Node's keepalive is unchanged.
|
|
96
|
+
if (shuttingDown)
|
|
97
|
+
socket.destroySoon();
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
res.once("finish", finished);
|
|
101
|
+
res.once("close", finished);
|
|
75
102
|
void (async () => {
|
|
76
103
|
const controller = new AbortController();
|
|
77
104
|
const abortRequest = () => {
|
|
@@ -133,7 +160,17 @@ export function listen(connecta, portOrOptions) {
|
|
|
133
160
|
});
|
|
134
161
|
if (opts.gracefulShutdown !== false) {
|
|
135
162
|
const timeoutMs = opts.shutdownTimeoutMs ?? 10_000;
|
|
136
|
-
|
|
163
|
+
// closeIdleConnections() only knows sockets that have served a request.
|
|
164
|
+
// A client's spare connection (or incomplete first headers) must not hold
|
|
165
|
+
// shutdown open without ever having been admitted to the application.
|
|
166
|
+
server.on("connection", (socket) => {
|
|
167
|
+
if (shuttingDown) {
|
|
168
|
+
socket.destroy();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
awaitingRequest.add(socket);
|
|
172
|
+
socket.once("close", () => awaitingRequest.delete(socket));
|
|
173
|
+
});
|
|
137
174
|
const shutdown = (signal) => {
|
|
138
175
|
if (shuttingDown)
|
|
139
176
|
return;
|
|
@@ -147,15 +184,15 @@ export function listen(connecta, portOrOptions) {
|
|
|
147
184
|
// Stop queued/running sandbox work now, not after server.close has
|
|
148
185
|
// waited for the very requests those children are holding open.
|
|
149
186
|
const executorClose = connecta.close();
|
|
150
|
-
// close() alone waits on idle keep-alive sockets, so a single browser
|
|
151
|
-
// tab would stall every shutdown until the force-exit deadline.
|
|
152
|
-
server.closeIdleConnections();
|
|
153
187
|
server.close(() => {
|
|
154
188
|
void Promise.allSettled([...pending, executorClose]).then(() => {
|
|
155
189
|
clearTimeout(forced);
|
|
156
190
|
process.exit(0);
|
|
157
191
|
});
|
|
158
192
|
});
|
|
193
|
+
server.closeIdleConnections();
|
|
194
|
+
for (const socket of awaitingRequest)
|
|
195
|
+
socket.destroy();
|
|
159
196
|
};
|
|
160
197
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
161
198
|
process.on("SIGINT", () => shutdown("SIGINT"));
|