@zackbart/connecta 0.37.1 → 0.39.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 +94 -0
- package/README.md +5 -4
- package/bin/connecta.mjs +1 -39
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.js +2 -4
- package/dist/agent-identity.js +1 -1
- package/dist/agent-routes.js +231 -0
- package/dist/auth/clerk-sdk/generated.js +2 -1
- package/dist/auth/downstream-oauth/consent.js +519 -0
- package/dist/auth/downstream-oauth/grant-store.js +439 -0
- package/dist/auth/downstream-oauth/refresh-coordinator.js +562 -0
- package/dist/auth/downstream-oauth/refresh-records.js +447 -0
- package/dist/auth/downstream-oauth/sdk-hooks.js +242 -0
- package/dist/auth/downstream-oauth.js +66 -2516
- package/dist/auth/downstream-revocation.js +1 -1
- package/dist/auth/native.js +27 -0
- package/dist/auth/oauth-grant.js +24 -0
- package/dist/auth/oauth-token-response.js +1 -1
- package/dist/auth/static-oauth.js +153 -131
- package/dist/auth/vercel-agent.js +2 -1
- package/dist/auth-elicitation.js +9 -3
- package/dist/authorization-handoff.js +11 -5
- package/dist/branding.d.ts +1 -15
- package/dist/branding.js +2 -2
- package/dist/bytes.js +42 -0
- package/dist/call-admission-context.js +2 -5
- package/dist/call-recovery.js +0 -3
- package/dist/catalog-cache.js +130 -43
- package/dist/catalog-fingerprint.js +2 -18
- package/dist/catalog-policy.js +18 -0
- package/dist/catalog-service.js +285 -48
- package/dist/catalog.js +18 -2
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +1 -1
- package/dist/config-schema.js +6 -2
- package/dist/config.d.ts +57 -25
- package/dist/config.js +30 -77
- package/dist/connector-caller.d.ts +1 -1
- package/dist/connector-health.js +59 -0
- package/dist/connector-status.js +117 -0
- package/dist/connectors/api-connector.d.ts +7 -2
- package/dist/connectors/api-connector.js +11 -7
- package/dist/connectors/guarded-fetch.d.ts +0 -6
- package/dist/connectors/guarded-fetch.js +1 -11
- package/dist/connectors/negotiation-cache.js +22 -4
- package/dist/connectors/remote-mcp/call.js +164 -0
- package/dist/connectors/remote-mcp/catalog.js +278 -0
- package/dist/connectors/remote-mcp/config.js +263 -0
- package/dist/connectors/remote-mcp/connection.js +671 -0
- package/dist/connectors/remote-mcp/errors.js +488 -0
- package/dist/connectors/remote-mcp/oauth.js +469 -0
- package/dist/connectors/remote-mcp/resources.js +473 -0
- package/dist/connectors/remote-mcp/runtime.js +1 -0
- package/dist/connectors/remote-mcp/transport.js +544 -0
- package/dist/connectors/remote-mcp.js +61 -2898
- package/dist/connectors/resource-uri.js +0 -4
- package/dist/credential-contract.d.ts +23 -0
- package/dist/credential-reads.js +17 -0
- package/dist/credential-verification.js +53 -0
- package/dist/credentials.d.ts +7 -0
- package/dist/credentials.js +60 -25
- package/dist/d1.d.ts +0 -89
- package/dist/d1.js +0 -34
- package/dist/describe-config.d.ts +12 -2
- package/dist/describe-config.js +2 -0
- package/dist/discovery-health.js +199 -0
- package/dist/downstream-input.js +4 -2
- package/dist/errors.d.ts +55 -17
- package/dist/errors.js +95 -41
- package/dist/execute.js +172 -125
- package/dist/executor-result.js +5 -2
- package/dist/executors/quickjs-runtime.js +2 -25
- package/dist/executors/quickjs.js +1 -1
- package/dist/guest-functions.js +83 -0
- package/dist/guest-runtime.js +63 -20
- package/dist/guest-types.d.ts +24 -10
- package/dist/http/argument-problems.js +35 -0
- package/dist/http/client.js +1 -1
- package/dist/http/errors.js +7 -6
- package/dist/http/fields.js +192 -13
- package/dist/http/hatch-response.js +73 -0
- package/dist/http/hatch.js +88 -23
- package/dist/http/json-schema.js +1 -1
- package/dist/http/node-transport.js +0 -1
- package/dist/http/oauth.js +4 -3
- package/dist/http/paging.js +6 -9
- package/dist/http/provider.js +33 -5
- package/dist/http/safety.js +9 -1
- package/dist/http/services.js +1 -8
- package/dist/http/tools.js +45 -30
- package/dist/http/vendor.js +7 -7
- package/dist/identity.js +2 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -2
- package/dist/invocation-auth.js +2 -1
- package/dist/invocation.js +21 -6
- package/dist/membership.d.ts +13 -5
- package/dist/membership.js +34 -2
- package/dist/meta-tools.js +50 -35
- package/dist/native-recovery.js +4 -16
- package/dist/native-tools.js +6 -2
- package/dist/oauth-handoff.js +2 -2
- package/dist/oauth-sealing.js +10 -0
- package/dist/operation-vocabulary.js +136 -14
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +2 -49
- package/dist/operator-ui/contract.d.ts +12 -2
- package/dist/operator-ui/generated.js +3 -3
- package/dist/operator-ui/model.d.ts +22 -1
- package/dist/program-writes.js +3 -8
- package/dist/provider-call.js +0 -7
- package/dist/provider.d.ts +1 -1
- package/dist/providers/_shared/google/kit.js +1 -6
- package/dist/providers/_shared/google/service-account.js +1 -3
- package/dist/providers/_shared/google/workspace.d.ts +1 -1
- package/dist/providers/_shared/guide.js +3 -0
- package/dist/providers/_shared/json.js +12 -0
- package/dist/providers/_shared/rest/compact-index.js +1 -1
- package/dist/providers/_shared/rest/kit.js +1 -3
- package/dist/providers/_shared/rest/operation-index.js +49 -53
- package/dist/providers/_shared/rest/value-safety.js +106 -28
- package/dist/providers/breeze/api.js +9 -8
- package/dist/providers/breeze/index.js +4 -2
- package/dist/providers/breeze/skill.generated.js +1 -1
- package/dist/providers/breeze/tools.js +7 -7
- package/dist/providers/ccb/api.js +3 -7
- package/dist/providers/ccb/index.js +7 -1
- package/dist/providers/ccb/skill.generated.js +1 -1
- package/dist/providers/ccb/tools.js +1 -6
- package/dist/providers/cloudflare/api.js +10 -3
- package/dist/providers/cloudflare/index.js +2 -1
- package/dist/providers/cloudflare/safety.js +3 -3
- package/dist/providers/cloudflare/skill.generated.js +1 -1
- package/dist/providers/docs/lib/schemas.js +24 -22
- package/dist/providers/docs/safety.js +2 -0
- package/dist/providers/docs/skill.generated.js +1 -1
- package/dist/providers/docs/tools.js +4 -14
- package/dist/providers/drive/api.js +1 -0
- package/dist/providers/drive/lib/contracts.js +8 -5
- package/dist/providers/drive/skill.generated.js +1 -1
- package/dist/providers/drive/tools.js +23 -41
- package/dist/providers/forms/api.js +1 -0
- package/dist/providers/forms/skill.generated.js +1 -1
- package/dist/providers/forms/tools.js +11 -16
- package/dist/providers/github/api.js +1 -0
- package/dist/providers/github/lib/discovery.js +1 -1
- package/dist/providers/github/skill.generated.js +1 -1
- package/dist/providers/github/tools.js +61 -17
- package/dist/providers/gmail/lib/schemas.js +60 -50
- package/dist/providers/gmail/skill.generated.js +1 -1
- package/dist/providers/gmail/tools.js +12 -26
- package/dist/providers/granola/index.js +2 -0
- package/dist/providers/granola/skill.generated.js +1 -1
- package/dist/providers/overflow/api.js +8 -4
- package/dist/providers/overflow/index.js +2 -1
- package/dist/providers/overflow/skill.generated.js +1 -1
- package/dist/providers/overflow/tools.js +3 -7
- package/dist/providers/planning-center/api.js +5 -2
- package/dist/providers/planning-center/lib/constants.d.ts +1 -1
- package/dist/providers/planning-center/lib/json-api.js +1 -9
- package/dist/providers/planning-center/skill.generated.js +1 -1
- package/dist/providers/planning-center/tools.js +2 -1
- package/dist/providers/quickbooks/safety.js +80 -39
- package/dist/providers/quickbooks/skill.generated.js +1 -1
- package/dist/providers/sheets/api.js +1 -0
- package/dist/providers/sheets/skill.generated.js +1 -1
- package/dist/providers/sheets/tools.js +14 -23
- package/dist/providers/slides/skill.generated.js +1 -1
- package/dist/providers/slides/tools.js +19 -26
- package/dist/providers/stripe/index.js +2 -1
- package/dist/providers/stripe/safety.js +2 -2
- package/dist/providers/stripe/skill.generated.js +1 -1
- package/dist/providers/tithely/api.js +5 -8
- package/dist/providers/tithely/index.js +3 -2
- package/dist/providers/tithely/skill.generated.js +1 -1
- package/dist/providers/tithely/tools.js +7 -5
- package/dist/providers/vercel/api.js +2 -7
- package/dist/providers/vercel/lib/pin.js +1 -1
- package/dist/providers/vercel/safety.js +1 -1
- package/dist/providers/vercel/skill.generated.js +1 -1
- package/dist/providers/vercel/tools.js +6 -4
- package/dist/redaction-marker.js +2 -2
- package/dist/registry.d.ts +11 -5
- package/dist/registry.js +46 -20
- package/dist/request-principal.js +1 -1
- package/dist/request-state.js +2 -2
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/activity-disclosure.js +1 -1
- package/dist/routes/connect.js +2 -2
- package/dist/routes/credentials.js +130 -61
- package/dist/routes/dispatch.js +5 -1
- package/dist/routes/mcp.js +34 -17
- package/dist/routes/oauth-management.js +1 -1
- package/dist/routes/operator.js +6 -5
- package/dist/routes/shared.d.ts +13 -8
- package/dist/routes/shared.js +15 -9
- package/dist/routes/ui-config.js +3 -3
- package/dist/routes/ui-data.js +126 -20
- package/dist/routes/ui.js +17 -1
- package/dist/runtime/call-admission.js +1 -1
- package/dist/runtime/call.js +8 -3
- package/dist/runtime/native-storage.js +9 -0
- package/dist/runtime/request-reads.js +182 -0
- package/dist/runtime/request-scope.js +16 -0
- package/dist/runtime/settlement.js +1 -1
- package/dist/runtime/shared-read.js +2 -15
- package/dist/runtime/storage.js +12 -0
- package/dist/sent-secrets.d.ts +1 -2
- package/dist/sent-secrets.js +3 -8
- package/dist/server.js +4 -4
- package/dist/skills.js +44 -54
- package/dist/sqlite.d.ts +0 -19
- package/dist/sqlite.js +2 -83
- package/dist/storage/keys.js +69 -128
- package/dist/storage/memory.js +8 -2
- package/dist/storage/sql-native.js +27 -101
- package/dist/storage/sql.d.ts +1 -24
- package/dist/storage/sql.js +13 -14
- package/dist/storage-digest.js +32 -0
- package/dist/timeout.js +8 -0
- package/dist/tool-error.js +4 -0
- package/dist/types.d.ts +33 -0
- package/dist/url-safety.js +0 -4
- package/dist/usage-guide.js +18 -37
- package/dist/validate.d.ts +3 -0
- package/dist/validate.js +220 -158
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +1 -1
- package/dist/write-outcome.js +0 -3
- package/documentation/README.md +19 -55
- package/documentation/architecture.md +436 -515
- package/documentation/auth.md +229 -88
- package/documentation/code-mode.md +201 -94
- package/documentation/deploying.md +25 -43
- package/documentation/integrating.md +18 -19
- package/documentation/meta-tools.md +225 -51
- package/documentation/migration-0.33.md +10 -6
- package/documentation/migration-0.34.md +5 -5
- package/documentation/migration-retired-layouts.md +74 -0
- package/documentation/operator-tests.md +20 -120
- package/documentation/operator-ui.md +108 -16
- package/documentation/pool-membership-upgrade.md +44 -0
- package/documentation/provider-auth-migration.md +4 -10
- package/documentation/provider-migration-0.29.md +4 -15
- package/documentation/providers.md +105 -0
- package/examples/worker/AGENTS.md +14 -16
- package/examples/worker/README.md +24 -118
- package/examples/worker/src/connecta.config.ts +1 -0
- package/guides/connecting-agents.md +9 -31
- package/guides/how-it-works.md +6 -4
- package/guides/safety-and-trust.md +4 -1
- package/package.json +19 -19
- package/templates/node/AGENTS.md +13 -12
- package/templates/node/README.md +9 -26
- package/templates/node/package.json +1 -1
- package/templates/node/src/connecta.config.ts +1 -0
- package/dist/auth/oauth-v2-migration.js +0 -135
- package/dist/provider-call-facts.d.ts +0 -115
- package/dist/provider-call.d.ts +0 -112
- package/dist/storage/d1-copy.js +0 -255
- package/dist/storage/sql-schema.d.ts +0 -6
- package/examples/worker/kv-to-d1.wrangler.jsonc +0 -26
- package/examples/worker/scripts/copy-kv-to-d1.mjs +0 -162
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { failureRecord, logFailure } from "../operator-record.js";
|
|
2
2
|
import { Effect } from "effect";
|
|
3
|
-
import { claimConnectorScopeCleanup
|
|
3
|
+
import { claimConnectorScopeCleanup } from "../connector-scope.js";
|
|
4
4
|
import { credentialTestRule, describeCredentialTestMismatch, storedCredentialShape } from "../credential-rules.js";
|
|
5
|
+
import { credentialFailureClass, credentialTestOutcome, writeVerification, } from "../credential-verification.js";
|
|
6
|
+
import { closeScope } from "../runtime/connector-scope.js";
|
|
7
|
+
import { withDeadlineEffect } from "../runtime/run.js";
|
|
5
8
|
import { authorizedPerson, refuse, serveOperator, managementRegistry } from "./operator.js";
|
|
6
9
|
import { boundedText, mayManageConnector, isSameOrigin, msg, privateJson } from "./shared.js";
|
|
7
10
|
/** The longest body, in characters, a credential write accepts. */
|
|
@@ -51,9 +54,19 @@ function readCredentialInput(request, config) {
|
|
|
51
54
|
// `null` parses, but a credential cannot be read from it.
|
|
52
55
|
if (body === null)
|
|
53
56
|
return yield* refuse("invalid JSON body", 400);
|
|
54
|
-
|
|
57
|
+
if (body.verify !== undefined && body.verify !== "test" && body.verify !== "skip") {
|
|
58
|
+
return yield* refuse('verify must be "test" or "skip"', 400);
|
|
59
|
+
}
|
|
60
|
+
const input = yield* credentialInput(body, config);
|
|
61
|
+
return { input, verify: body.verify ?? "skip" };
|
|
55
62
|
});
|
|
56
63
|
}
|
|
64
|
+
/** The test hook's refusal when the declared shape picks none, in the page's words. */
|
|
65
|
+
function untestable(mismatch) {
|
|
66
|
+
return refuse(mismatch
|
|
67
|
+
? "this connector cannot test its credential: " + describeCredentialTestMismatch(mismatch)
|
|
68
|
+
: "this connector does not support credential testing", 400);
|
|
69
|
+
}
|
|
57
70
|
function credentialRequest(context, connectorId, action) {
|
|
58
71
|
const { request, baseUrl, opts } = context;
|
|
59
72
|
return Effect.gen(function* () {
|
|
@@ -79,6 +92,64 @@ function credentialRequest(context, connectorId, action) {
|
|
|
79
92
|
const updatedBy = authz.identity.principal
|
|
80
93
|
? `${authz.identity.principal.namespace}:${authz.identity.principal.id}`
|
|
81
94
|
: (authz.actor.id ?? authz.actor.kind);
|
|
95
|
+
/**
|
|
96
|
+
* Run the connector's test hook on `values` — the stored credential or an
|
|
97
|
+
* unsaved candidate, which stays in this request's memory — under the
|
|
98
|
+
* deployment's probe deadline. Settles to an outcome class and never
|
|
99
|
+
* fails: whatever goes wrong is the test's result, not the route's. Each
|
|
100
|
+
* run gets its own context and request scope, so concurrent tests share
|
|
101
|
+
* no connection.
|
|
102
|
+
*
|
|
103
|
+
* Only the class leaves. A hook's message, and anything it throws, can
|
|
104
|
+
* quote the downstream's reply, and that reply can quote the credential
|
|
105
|
+
* it just rejected — so neither the page nor the log repeats it (INV-6):
|
|
106
|
+
* the page says fixed copy for the class, and the log records a
|
|
107
|
+
* failure's class.
|
|
108
|
+
*/
|
|
109
|
+
const runTest = (mode, values) => {
|
|
110
|
+
const timeoutError = new Error("credential test timed out");
|
|
111
|
+
return withDeadlineEffect((signal) => {
|
|
112
|
+
const ctx = registry.contextFor(connectorId, baseUrl, {}, { signal });
|
|
113
|
+
const releaseCleanup = claimConnectorScopeCleanup(ctx, connectorId);
|
|
114
|
+
return Effect.tryPromise({
|
|
115
|
+
try: async () => {
|
|
116
|
+
const result = mode === "multiple"
|
|
117
|
+
? await connector.testCredentials(values, ctx)
|
|
118
|
+
: // The single-value shape guarantees this key.
|
|
119
|
+
await connector.testCredential(values.value, ctx);
|
|
120
|
+
// A hook that turned the deadline's abort into a result still ran out of time.
|
|
121
|
+
return signal.reason === timeoutError ? "timeout" : credentialTestOutcome(result);
|
|
122
|
+
},
|
|
123
|
+
catch: (error) => (signal.reason === timeoutError ? timeoutError : error),
|
|
124
|
+
}).pipe(
|
|
125
|
+
// The scope closes here, once, however the test ends: a hook that
|
|
126
|
+
// never settles is interrupted by the deadline, and a cleanup in
|
|
127
|
+
// its Promise would never run (INV-7).
|
|
128
|
+
Effect.ensuring(Effect.suspend(() => {
|
|
129
|
+
releaseCleanup();
|
|
130
|
+
return closeScope(connector, ctx, context.defer);
|
|
131
|
+
})));
|
|
132
|
+
}, { timeoutMs: opts.config.discovery.probeTimeoutMs, timeoutError }).pipe(Effect.catch((error) => {
|
|
133
|
+
if (error === timeoutError)
|
|
134
|
+
return Effect.succeed("timeout");
|
|
135
|
+
logFailure(opts.config.logger, "credential test threw", failureRecord({ connector: connectorId }, error));
|
|
136
|
+
return Effect.succeed(credentialFailureClass(error));
|
|
137
|
+
}), Effect.map((outcome) => {
|
|
138
|
+
if (outcome !== "ok")
|
|
139
|
+
logFailure(opts.config.logger, "credential test failed", failureRecord({ connector: connectorId }));
|
|
140
|
+
return outcome;
|
|
141
|
+
}));
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Record what was observed about the save with `revision`, or (`null`)
|
|
145
|
+
* forget it, in the connector's own owner-partitioned storage. A save
|
|
146
|
+
* without a revision (a vault that keeps none) is never recorded. Best
|
|
147
|
+
* effort: a lost record only leaves status saying the credential is not
|
|
148
|
+
* verified, so it never fails the request.
|
|
149
|
+
*/
|
|
150
|
+
const record = (outcome, revision) => Effect.promise(async () => outcome && !revision
|
|
151
|
+
? undefined
|
|
152
|
+
: writeVerification(registry.contextFor(connectorId, baseUrl).storage, outcome, revision).catch((error) => void logFailure(opts.config.logger, "credential verification record failed", failureRecord({ connector: connectorId }, error))));
|
|
82
153
|
if (action === "test") {
|
|
83
154
|
if (request.method !== "POST")
|
|
84
155
|
return yield* refuse("method not allowed", 405);
|
|
@@ -86,79 +157,74 @@ function credentialRequest(context, connectorId, action) {
|
|
|
86
157
|
// Credentials page asks for its Test affordance, so a shown button reaches
|
|
87
158
|
// that reads the shape the credential was stored in.
|
|
88
159
|
const { mode, mismatch } = credentialTestRule(connector);
|
|
89
|
-
if (!mode)
|
|
90
|
-
return yield*
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
// rejected — so neither the page nor the log repeats it (INV-6): the
|
|
99
|
-
// page says a fixed sentence for the outcome, and the log records a
|
|
100
|
-
// failure's class.
|
|
101
|
-
return yield* Effect.tryPromise({
|
|
102
|
-
try: async () => {
|
|
103
|
-
const values = await vault.getAll(connectorId, owner);
|
|
104
|
-
const shape = storedCredentialShape(credential, values);
|
|
105
|
-
// Both refusals are the route's own words, and each carries the
|
|
106
|
-
// problem kind the page keys its copy and fix prompt off.
|
|
107
|
-
if (shape.state === "missing") {
|
|
108
|
-
return privateJson({
|
|
109
|
-
error: mode === "multiple"
|
|
110
|
-
? "configure the credentials before testing them"
|
|
111
|
-
: "configure the credential before testing it",
|
|
112
|
-
problem: "credential_required",
|
|
113
|
-
}, { status: 409 });
|
|
114
|
-
}
|
|
115
|
-
if (shape.state === "mismatch") {
|
|
116
|
-
return privateJson({ error: shape.message, problem: "credential_mismatch" }, { status: 409 });
|
|
117
|
-
}
|
|
118
|
-
const storedValues = values;
|
|
119
|
-
const ctx = registry.contextFor(connectorId, baseUrl);
|
|
120
|
-
const releaseCleanup = claimConnectorScopeCleanup(ctx, connectorId);
|
|
121
|
-
try {
|
|
122
|
-
const result = mode === "multiple"
|
|
123
|
-
? await connector.testCredentials(storedValues, ctx)
|
|
124
|
-
: await connector.testCredential(
|
|
125
|
-
// The single-value shape check above guarantees this key.
|
|
126
|
-
storedValues.value, ctx);
|
|
127
|
-
const ok = result?.ok === true;
|
|
128
|
-
if (!ok)
|
|
129
|
-
logFailure(opts.config.logger, "credential test failed", failureRecord({ connector: connectorId }, undefined));
|
|
130
|
-
return privateJson({ ok });
|
|
131
|
-
}
|
|
132
|
-
finally {
|
|
133
|
-
releaseCleanup();
|
|
134
|
-
await closeConnectorScope(connector, ctx, context.defer);
|
|
135
|
-
}
|
|
136
|
-
},
|
|
160
|
+
if (!mode)
|
|
161
|
+
return yield* untestable(mismatch);
|
|
162
|
+
// One envelope read gives both the values tested and the revision of
|
|
163
|
+
// the save they came from, so the record can never vouch for another
|
|
164
|
+
// save. A vault without snapshots is tested but never recorded.
|
|
165
|
+
const stored = yield* Effect.tryPromise({
|
|
166
|
+
try: async () => vault.snapshot
|
|
167
|
+
? ((await vault.snapshot(connectorId, owner)) ?? { values: null })
|
|
168
|
+
: { values: await vault.getAll(connectorId, owner) },
|
|
137
169
|
catch: (error) => error,
|
|
138
170
|
}).pipe(Effect.catch((error) => {
|
|
139
171
|
logFailure(opts.config.logger, "credential test threw", failureRecord({ connector: connectorId }, error));
|
|
140
|
-
return Effect.succeed(
|
|
172
|
+
return Effect.succeed(undefined);
|
|
141
173
|
}));
|
|
174
|
+
if (!stored)
|
|
175
|
+
return privateJson({ ok: false, failure: "failed" });
|
|
176
|
+
const shape = storedCredentialShape(credential, stored.values);
|
|
177
|
+
// Both refusals are the route's own words, and each carries the
|
|
178
|
+
// problem kind the page keys its copy and fix prompt off.
|
|
179
|
+
if (shape.state === "missing") {
|
|
180
|
+
return privateJson({
|
|
181
|
+
error: mode === "multiple"
|
|
182
|
+
? "configure the credentials before testing them"
|
|
183
|
+
: "configure the credential before testing it",
|
|
184
|
+
problem: "credential_required",
|
|
185
|
+
}, { status: 409 });
|
|
186
|
+
}
|
|
187
|
+
if (shape.state === "mismatch") {
|
|
188
|
+
return privateJson({ error: shape.message, problem: "credential_mismatch" }, { status: 409 });
|
|
189
|
+
}
|
|
190
|
+
const outcome = yield* runTest(mode, stored.values);
|
|
191
|
+
if ("metadata" in stored)
|
|
192
|
+
yield* record(outcome, stored.metadata.revision);
|
|
193
|
+
return privateJson(outcome === "ok" ? { ok: true } : { ok: false, failure: outcome });
|
|
142
194
|
}
|
|
143
195
|
if (action)
|
|
144
196
|
return yield* refuse("not found", 404);
|
|
145
197
|
if (request.method === "PUT") {
|
|
146
|
-
const input = yield* readCredentialInput(request, credential);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
198
|
+
const { input, verify } = yield* readCredentialInput(request, credential);
|
|
199
|
+
const values = input.kind === "single" ? { value: input.value } : input.values;
|
|
200
|
+
if (verify === "test") {
|
|
201
|
+
// Test then save: the candidate is tested in memory and stored only
|
|
202
|
+
// if the test passes. A failure stores nothing and names its class;
|
|
203
|
+
// the operator may still save it unverified with `verify: "skip"`,
|
|
204
|
+
// so an outage at the vendor never blocks a replacement.
|
|
205
|
+
const { mode, mismatch } = credentialTestRule(connector);
|
|
206
|
+
if (!mode)
|
|
207
|
+
return yield* untestable(mismatch);
|
|
208
|
+
const outcome = yield* runTest(mode, values);
|
|
209
|
+
if (outcome !== "ok")
|
|
210
|
+
return privateJson({ saved: false, failure: outcome });
|
|
211
|
+
}
|
|
212
|
+
const metadata = yield* Effect.tryPromise({
|
|
213
|
+
try: () => input.kind === "single"
|
|
214
|
+
? vault.set(connectorId, input.value, updatedBy, owner)
|
|
215
|
+
: vault.setAll(connectorId, input.values, updatedBy, owner),
|
|
155
216
|
catch: (error) => error,
|
|
156
217
|
}).pipe(Effect.catch((error) => refuse(msg(error), 400)));
|
|
218
|
+
// The write's own revision: a concurrent save cannot lend it one.
|
|
219
|
+
const { revision, ...saved } = metadata;
|
|
220
|
+
const verification = yield* record(verify === "test" ? "ok" : "unverified", revision);
|
|
221
|
+
yield* Effect.promise(() => registry.invalidateStored(connectorId));
|
|
222
|
+
return privateJson({ saved: true, credential: saved, ...(verification ? { verification } : {}) });
|
|
157
223
|
}
|
|
158
224
|
if (request.method === "DELETE") {
|
|
159
225
|
// Clearing nothing is refused, as testing nothing is, so the route
|
|
160
226
|
// agrees with the operator contract, which offers `clear` only once a
|
|
161
|
-
// value is stored in the caller's partition
|
|
227
|
+
// value is stored in the caller's partition. A value that cannot
|
|
162
228
|
// be read may still be cleared: removing it is how one recovers.
|
|
163
229
|
const stored = yield* Effect.tryPromise({
|
|
164
230
|
try: () => vault.metadata(connectorId, owner),
|
|
@@ -168,6 +234,9 @@ function credentialRequest(context, connectorId, action) {
|
|
|
168
234
|
return privateJson({ error: "no credential is stored to clear", problem: "credential_required" }, { status: 409 });
|
|
169
235
|
}
|
|
170
236
|
yield* Effect.promise(() => vault.delete(connectorId, owner));
|
|
237
|
+
// A record about a value no longer stored would match nothing anyway;
|
|
238
|
+
// removing it keeps the partition clean.
|
|
239
|
+
yield* record(null);
|
|
171
240
|
yield* Effect.promise(() => registry.invalidateStored(connectorId));
|
|
172
241
|
return new Response(null, {
|
|
173
242
|
status: 204,
|
package/dist/routes/dispatch.js
CHANGED
|
@@ -28,7 +28,11 @@ export function createRouteDispatcher(mcp, metadata, health) {
|
|
|
28
28
|
});
|
|
29
29
|
const afterUi = (program) => (context) => Effect.gen(function* () {
|
|
30
30
|
const ui = context.opts.config.ui;
|
|
31
|
-
|
|
31
|
+
// The operator surface is a Promise edge that owns the request's
|
|
32
|
+
// signal: when the caller leaves, its routes stop on their own. Wait
|
|
33
|
+
// for it to settle rather than abandoning it, so writes it has
|
|
34
|
+
// already started end inside this request (INV-7).
|
|
35
|
+
const response = ui ? yield* Effect.uninterruptible(Effect.promise(() => ui.handle(context))) : null;
|
|
32
36
|
if (response?.status === 401) {
|
|
33
37
|
const authorizationHeader = authorizationCredential(context.request).kind;
|
|
34
38
|
logFailure(context.opts.config.logger, "operator_refused", failureRecord({
|
package/dist/routes/mcp.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { routeNames } from "../agent-routes.js";
|
|
1
2
|
import { upwardStep } from "../upward.js";
|
|
3
|
+
import { bindDiscoveryEndpoint } from "../discovery-health.js";
|
|
2
4
|
import { authCall, authInvoke, authFlow, authMethod } from "../auth/native.js";
|
|
3
5
|
import { registryProgram } from "../runtime/registry.js";
|
|
4
6
|
import { requestOwner, RequestScope } from "../runtime/request-scope.js";
|
|
@@ -20,7 +22,7 @@ import { failureRecord, logFailure } from "../operator-record.js";
|
|
|
20
22
|
import { redactAgentOutput, sentSecretsForRequest } from "../sent-secrets.js";
|
|
21
23
|
import { detach } from "../runtime/run.js";
|
|
22
24
|
import { agentEndpointPool, isAgentPath } from "./agent-path.js";
|
|
23
|
-
import { admitsMaintainer, agentPool, authorize, authorizeAgent, mayManageConnector, memberPool, } from "./shared.js";
|
|
25
|
+
import { admitsMaintainer, agentPool, authorize, authorizeAgent, mayManageConnector, memberPool, personPools, } from "./shared.js";
|
|
24
26
|
/**
|
|
25
27
|
* Core checks admission.requests.maxBodyBytes after authentication and endpoint
|
|
26
28
|
* admission, before any SDK/body-hint reader. The SDK's separate 4 MiB default
|
|
@@ -32,7 +34,7 @@ const deadlineResponses = new WeakSet();
|
|
|
32
34
|
/**
|
|
33
35
|
* The most a `subscriptions/listen` classification reads. A listen is a small
|
|
34
36
|
* JSON-RPC request; this read happens before request admission, so it is
|
|
35
|
-
* bounded by this fixed cap rather than by the host's body limit
|
|
37
|
+
* bounded by this fixed cap rather than by the host's body limit.
|
|
36
38
|
*/
|
|
37
39
|
export const MAX_LISTEN_CLASSIFICATION_BYTES = 64 * 1024;
|
|
38
40
|
/**
|
|
@@ -286,7 +288,7 @@ function admitted(controller, signal) {
|
|
|
286
288
|
}), (lease) => Effect.sync(() => lease.release()), { interruptible: true });
|
|
287
289
|
}
|
|
288
290
|
/**
|
|
289
|
-
* 404 for any `?toolkit=` value, kept after the feature's retirement
|
|
291
|
+
* 404 for any `?toolkit=` value, kept after the feature's retirement.
|
|
290
292
|
*
|
|
291
293
|
* Toolkits are gone, but the URLs that named them are not: clients were handed
|
|
292
294
|
* MCP endpoint URLs with `?toolkit=` baked in, and nothing about upgrading the
|
|
@@ -396,8 +398,11 @@ function serveMcp(request, requestSignal, requestScope, opts, baseUrl, actor, re
|
|
|
396
398
|
...(runtimeContext?.waitUntil ? { defer: runtimeContext.waitUntil.bind(runtimeContext) } : {}),
|
|
397
399
|
});
|
|
398
400
|
const diagnostics = native ? { ...native, catalogErrors: [] } : undefined;
|
|
401
|
+
// A native pool's text uses the native contract's wording.
|
|
402
|
+
const routes = native ? routeNames(native.executeCode ? "native-code" : "native") : opts.routes;
|
|
399
403
|
const nativeRoute = registerMetaTools(server, registry, {
|
|
400
404
|
native: diagnostics,
|
|
405
|
+
routes,
|
|
401
406
|
authElicitation,
|
|
402
407
|
client,
|
|
403
408
|
baseUrl,
|
|
@@ -409,6 +414,7 @@ function serveMcp(request, requestSignal, requestScope, opts, baseUrl, actor, re
|
|
|
409
414
|
credentialHandoffUrl: opts.config.ui?.credentialHandoffUrl(baseUrl),
|
|
410
415
|
...(activity ? { activity } : {}),
|
|
411
416
|
defaultToolTimeoutMs: opts.config.calls.defaultTimeoutMs ?? opts.config.execute.hostCallTimeoutMs,
|
|
417
|
+
maxProgramResultChars: opts.config.execute.maxResultChars,
|
|
412
418
|
probeTimeoutMs: opts.config.discovery.probeTimeoutMs,
|
|
413
419
|
discoveryConcurrency: opts.config.discovery.concurrency,
|
|
414
420
|
requestSignal,
|
|
@@ -429,6 +435,7 @@ function serveMcp(request, requestSignal, requestScope, opts, baseUrl, actor, re
|
|
|
429
435
|
...executeLimits(opts.config),
|
|
430
436
|
readOnly,
|
|
431
437
|
native: native !== undefined,
|
|
438
|
+
routes,
|
|
432
439
|
});
|
|
433
440
|
}
|
|
434
441
|
if (native && nativeRoute && hint) {
|
|
@@ -596,6 +603,9 @@ export function createMcpRoute(opts) {
|
|
|
596
603
|
const requestScope = {};
|
|
597
604
|
const sentSecrets = sentSecretsForRequest(requestScope, sentSecretsForRequest(request));
|
|
598
605
|
const poolName = endpoint.kind === "pool" || endpoint.kind === "agent" ? endpoint.pool : undefined;
|
|
606
|
+
// Discovery health is kept per admitted endpoint: the same person and
|
|
607
|
+
// pool at `/mcp` and `/mcp/<pool>` keep separate records.
|
|
608
|
+
bindDiscoveryEndpoint(requestScope, JSON.stringify([endpoint.kind, poolName ?? null]));
|
|
599
609
|
const agentEndpoint = endpoint.kind === "agent";
|
|
600
610
|
// Access attaches `ctx.access` even to a path it bypasses; an agent
|
|
601
611
|
// endpoint never reads it, so nothing past this point receives it.
|
|
@@ -638,7 +648,7 @@ export function createMcpRoute(opts) {
|
|
|
638
648
|
}));
|
|
639
649
|
// A request naming `subscriptions/listen` defers its permit: nothing of
|
|
640
650
|
// its body is read until auth and endpoint admission pass, and then at
|
|
641
|
-
// most MAX_LISTEN_CLASSIFICATION_BYTES
|
|
651
|
+
// most MAX_LISTEN_CLASSIFICATION_BYTES. Only a body-confirmed
|
|
642
652
|
// modern listen goes without a permit; trusting the header alone would
|
|
643
653
|
// let a legacy tools/call bypass the pool, so anything else takes one
|
|
644
654
|
// after classification. Every other request takes its permit here,
|
|
@@ -718,13 +728,17 @@ export function createMcpRoute(opts) {
|
|
|
718
728
|
false,
|
|
719
729
|
opts.config.logger,
|
|
720
730
|
]));
|
|
721
|
-
// Plain /mcp
|
|
731
|
+
// Plain /mcp serves maintainers the whole catalog and other people their unique pool; a pool
|
|
722
732
|
// admits its members, people at /mcp/<pool> and agents at
|
|
723
733
|
// /agent/<pool>, and serves exactly its tools (INV-4).
|
|
724
734
|
const scopeFor = (admitted) => {
|
|
725
735
|
if (poolName === undefined) {
|
|
726
|
-
|
|
727
|
-
|
|
736
|
+
if (admitsMaintainer(opts, admitted))
|
|
737
|
+
return { access: { connectorIds: "all" }, readOnly: opts.config.readOnly };
|
|
738
|
+
// Exactly one membership routes; none or several fail closed.
|
|
739
|
+
const [match, ...more] = personPools(opts, admitted.identity);
|
|
740
|
+
return match && !more.length
|
|
741
|
+
? { pool: match[0], access: match[1].access, readOnly: match[1].readOnly }
|
|
728
742
|
: undefined;
|
|
729
743
|
}
|
|
730
744
|
const pool = agentEndpoint
|
|
@@ -734,6 +748,7 @@ export function createMcpRoute(opts) {
|
|
|
734
748
|
// same caller facts (#837 P5: agents act as the pool owner).
|
|
735
749
|
return pool
|
|
736
750
|
? {
|
|
751
|
+
pool: poolName,
|
|
737
752
|
access: pool.access,
|
|
738
753
|
readOnly: pool.readOnly,
|
|
739
754
|
...(pool.owner !== undefined ? { poolOwner: pool.owner } : {}),
|
|
@@ -779,14 +794,16 @@ export function createMcpRoute(opts) {
|
|
|
779
794
|
? "MCP agent endpoint request denied"
|
|
780
795
|
: "MCP pool request denied", failureRecord({
|
|
781
796
|
reason: poolName === undefined
|
|
782
|
-
?
|
|
797
|
+
? personPools(opts, authz.identity).length > 1
|
|
798
|
+
? "ambiguous_pool"
|
|
799
|
+
: "not_maintainer"
|
|
783
800
|
: opts.pools.has(poolName)
|
|
784
801
|
? "not_pool_member"
|
|
785
802
|
: "pool_not_declared",
|
|
786
803
|
}));
|
|
787
804
|
return notFound();
|
|
788
805
|
}
|
|
789
|
-
const { access, readOnly
|
|
806
|
+
const { access, readOnly } = scope;
|
|
790
807
|
// Admission, not completed-call activity. This fact carries no live
|
|
791
808
|
// identity or credential and never changes the admission verdict.
|
|
792
809
|
if (agentEndpoint && authz.identity.agent && opts.config.agents.includes(authz.identity.agent)) {
|
|
@@ -811,15 +828,15 @@ export function createMcpRoute(opts) {
|
|
|
811
828
|
}
|
|
812
829
|
// The caller rides on the view, and the paging recheck builds the
|
|
813
830
|
// same one: an agent's Workspace visibility depends on the owner.
|
|
814
|
-
const callerFor = (identity,
|
|
831
|
+
const callerFor = (identity, admittedScope) => ({
|
|
815
832
|
identity,
|
|
816
833
|
// `authorize` admits an open deployment's every request as the
|
|
817
834
|
// anonymous actor; only a provider's `ok` is an authentication.
|
|
818
835
|
authenticated: opts.config.auth.length > 0,
|
|
819
836
|
// Both pool endpoints build the caller here from the same
|
|
820
837
|
// admitted scope, so an agent at /agent/<pool> carries the owner.
|
|
821
|
-
...(
|
|
822
|
-
...(
|
|
838
|
+
...(admittedScope.pool !== undefined ? { pool: admittedScope.pool } : {}),
|
|
839
|
+
...(admittedScope.poolOwner !== undefined ? { poolOwner: admittedScope.poolOwner } : {}),
|
|
823
840
|
});
|
|
824
841
|
let scopedRegistry;
|
|
825
842
|
try {
|
|
@@ -843,14 +860,14 @@ export function createMcpRoute(opts) {
|
|
|
843
860
|
return false;
|
|
844
861
|
// Paging re-admits the caller at this endpoint, as a new request would.
|
|
845
862
|
const currentScope = scopeFor(current);
|
|
846
|
-
if (!currentScope)
|
|
863
|
+
if (!currentScope || currentScope.pool !== scope.pool)
|
|
847
864
|
return false;
|
|
848
|
-
const { access: currentAccess, readOnly: currentReadOnly
|
|
865
|
+
const { access: currentAccess, readOnly: currentReadOnly } = currentScope;
|
|
849
866
|
const view = opts.registry.scoped({
|
|
850
867
|
...currentAccess,
|
|
851
868
|
...(current.subjectKey ? { subjectKey: current.subjectKey } : {}),
|
|
852
869
|
...(current.principalKey ? { principalKey: current.principalKey } : {}),
|
|
853
|
-
caller: callerFor(current.identity,
|
|
870
|
+
caller: callerFor(current.identity, currentScope),
|
|
854
871
|
});
|
|
855
872
|
if (classification === "write" && currentReadOnly)
|
|
856
873
|
return false;
|
|
@@ -897,7 +914,7 @@ export function createMcpRoute(opts) {
|
|
|
897
914
|
}
|
|
898
915
|
}
|
|
899
916
|
})),
|
|
900
|
-
caller: callerFor(authz.identity,
|
|
917
|
+
caller: callerFor(authz.identity, scope),
|
|
901
918
|
});
|
|
902
919
|
}
|
|
903
920
|
catch {
|
|
@@ -909,7 +926,7 @@ export function createMcpRoute(opts) {
|
|
|
909
926
|
return cors(yield* serveMcp(localRequest, localAbort.signal, requestScope, opts, baseUrl, authz.actor, scopedRegistry, (id) => {
|
|
910
927
|
const connector = scopedRegistry.getConnector(id);
|
|
911
928
|
return Boolean(connector && mayManageConnector(opts, authz, connector));
|
|
912
|
-
}, authz.principalKey, runtimeContext, readOnly,
|
|
929
|
+
}, authz.principalKey, runtimeContext, readOnly, scope.pool, authz.identity.principal
|
|
913
930
|
? {
|
|
914
931
|
kind: authz.actor.kind,
|
|
915
932
|
id: authz.identity.principal.id,
|
|
@@ -23,7 +23,7 @@ function oauthManagementRequest(context, connectorId) {
|
|
|
23
23
|
const registry = yield* managementRegistry(context, authz);
|
|
24
24
|
const connector = registry.getConnector(connectorId);
|
|
25
25
|
const disconnectAuth = connector?.disconnectAuth?.bind(connector);
|
|
26
|
-
// Each verb needs only its own hooks
|
|
26
|
+
// Each verb needs only its own hooks: connecting needs the whole
|
|
27
27
|
// flow, through the callback's state check; disconnecting needs only
|
|
28
28
|
// disconnectAuth, so a grant provisioned out of band can still be dropped.
|
|
29
29
|
const connects = Boolean(connector?.startAuth && connector.finishAuth && connector.verifyState);
|
package/dist/routes/operator.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bytesToHex } from "../bytes.js";
|
|
1
2
|
// The Effect plumbing the operator and activity data routes share.
|
|
2
3
|
//
|
|
3
4
|
// Each JSON route under /ui/* is one Effect program, run at its Promise edge
|
|
@@ -84,7 +85,7 @@ export function operatorViewerKey(context, authz) {
|
|
|
84
85
|
]));
|
|
85
86
|
const signingKey = key;
|
|
86
87
|
const digest = yield* attempt(() => crypto.subtle.sign("HMAC", signingKey, identity));
|
|
87
|
-
return
|
|
88
|
+
return bytesToHex(new Uint8Array(digest));
|
|
88
89
|
}), {
|
|
89
90
|
timeoutMs: 500,
|
|
90
91
|
signal: context.request.signal,
|
|
@@ -92,16 +93,16 @@ export function operatorViewerKey(context, authz) {
|
|
|
92
93
|
}).pipe(Effect.catch(() => refuse("operator identity could not be read", 503)));
|
|
93
94
|
}
|
|
94
95
|
/** The partition an identity carries into a registry view of the given connectors. */
|
|
95
|
-
export function scopeFor(authz, authenticated, connectorIds = "all") {
|
|
96
|
+
export function scopeFor(authz, authenticated, connectorIds = "all", pool) {
|
|
96
97
|
return {
|
|
97
98
|
connectorIds,
|
|
98
99
|
...(authz.subjectKey ? { subjectKey: authz.subjectKey } : {}),
|
|
99
100
|
...(authz.principalKey ? { principalKey: authz.principalKey } : {}),
|
|
100
|
-
operatorCaller: { identity: authz.identity, authenticated },
|
|
101
|
+
operatorCaller: { identity: authz.identity, authenticated, ...(pool !== undefined ? { pool } : {}) },
|
|
101
102
|
};
|
|
102
103
|
}
|
|
103
104
|
/**
|
|
104
|
-
* The whole registry, for a maintainer only
|
|
105
|
+
* The whole registry, for a maintainer only. Everyone else gets the
|
|
105
106
|
* private 404 plain `/mcp` gives them.
|
|
106
107
|
*/
|
|
107
108
|
export function visibleRegistry({ opts }, authz) {
|
|
@@ -111,7 +112,7 @@ export function visibleRegistry({ opts }, authz) {
|
|
|
111
112
|
return Effect.succeed(opts.registry.scoped(scopeFor(authz, opts.config.auth.length > 0)));
|
|
112
113
|
}
|
|
113
114
|
/**
|
|
114
|
-
* The connectors a connector-auth route may resolve
|
|
115
|
+
* The connectors a connector-auth route may resolve: every connector
|
|
115
116
|
* for a maintainer, the connectors of their pools for a pool member, and the
|
|
116
117
|
* private 404 for anyone else. mayManageConnector still decides each action.
|
|
117
118
|
*/
|
package/dist/routes/shared.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AgentRouteNames } from "../types.js";
|
|
1
2
|
import type { ActivityActor } from "../activity.js";
|
|
2
3
|
import type { DeferredWork } from "../connector-scope.js";
|
|
3
4
|
import type { AdmissionController } from "../executor-admission.js";
|
|
@@ -28,6 +29,8 @@ export interface ServerOptions {
|
|
|
28
29
|
executorName?: string | undefined;
|
|
29
30
|
/** Global FIFO boundary for all non-preflight `/mcp` requests. */
|
|
30
31
|
requestAdmission: AdmissionController;
|
|
32
|
+
/** Route names agent text uses outside native pools: meta, or program form for the eval code arm only. */
|
|
33
|
+
routes: AgentRouteNames;
|
|
31
34
|
}
|
|
32
35
|
export interface RuntimeExecutionContext extends InboundAuthRuntimeContext {
|
|
33
36
|
waitUntil(promise: Promise<unknown>): void;
|
|
@@ -78,7 +81,7 @@ export declare function authorize(request: Request, baseUrl: string, auth: reado
|
|
|
78
81
|
response: Response;
|
|
79
82
|
}>;
|
|
80
83
|
/**
|
|
81
|
-
* Admission at an agent endpoint, `/agent/<pool
|
|
84
|
+
* Admission at an agent endpoint, `/agent/<pool>`. Only the built-in
|
|
82
85
|
* agent credential adapters are consulted: Clerk and Access never run, cookies and Clerk
|
|
83
86
|
* handshake parameters are stripped, and the runtime context, which carries
|
|
84
87
|
* Access's `ctx.access` even on a bypassed path, is never passed. Anything but
|
|
@@ -88,31 +91,33 @@ export declare function authorizeAgent(request: Request, baseUrl: string, auth:
|
|
|
88
91
|
/** Human routes reject machine credentials before any verification or storage I/O. */
|
|
89
92
|
export declare function authorizeUiIdentity(request: Request, baseUrl: string, auth: readonly InboundAuth[], purpose: string, runtimeContext?: RuntimeExecutionContext, logger?: Logger): Promise<Awaited<ReturnType<typeof authorize>>>;
|
|
90
93
|
export declare function isSameOrigin(request: Request, baseUrl: string): boolean;
|
|
91
|
-
export declare function withSecurityHeaders(response: Response, requestUrl: URL
|
|
94
|
+
export declare function withSecurityHeaders(response: Response, requestUrl: URL): Response;
|
|
92
95
|
type AuthorizedIdentity = Extract<Awaited<ReturnType<typeof authorize>>, {
|
|
93
96
|
ok: true;
|
|
94
97
|
}>;
|
|
95
98
|
/**
|
|
96
|
-
*
|
|
99
|
+
* Every operator route admits a maintainer. An open
|
|
97
100
|
* deployment has no inbound auth and so no maintainers: its one anonymous
|
|
98
101
|
* caller keeps the whole default endpoint, as the startup warning says.
|
|
99
102
|
*/
|
|
100
103
|
export declare function admitsMaintainer(opts: ServerOptions, authz: AuthorizedIdentity): boolean;
|
|
101
104
|
/**
|
|
102
105
|
* The declared pool `/mcp/<name>` serves to this person, or undefined for the
|
|
103
|
-
* same 404. An agent is never admitted here, even when listed
|
|
106
|
+
* same 404. An agent is never admitted here, even when listed.
|
|
104
107
|
*/
|
|
105
108
|
export declare function memberPool(opts: ServerOptions, name: string, identity: Readonly<AuthenticatedIdentity>): ResolvedPool | undefined;
|
|
109
|
+
/** Human memberships, matched only against the resolved namespaced principal. */
|
|
110
|
+
export declare function personPools(opts: ServerOptions, identity: Readonly<AuthenticatedIdentity>): Array<[string, ResolvedPool]>;
|
|
106
111
|
/** The declared pool `/agent/<name>` serves to this agent, or undefined for the same 404. */
|
|
107
112
|
export declare function agentPool(opts: ServerOptions, name: string, identity: Readonly<AuthenticatedIdentity>): ResolvedPool | undefined;
|
|
108
113
|
/**
|
|
109
|
-
* The connectors of every pool this identity is a member of
|
|
114
|
+
* The connectors of every pool this identity is a member of: what a
|
|
110
115
|
* pool member may ever manage, and only their personal ones among these.
|
|
111
116
|
*/
|
|
112
117
|
export declare function memberConnectors(opts: ServerOptions, identity: Readonly<AuthenticatedIdentity>): "all" | ReadonlySet<string>;
|
|
113
118
|
/**
|
|
114
119
|
* Whether this identity may start, finish, or remove a connector's auth
|
|
115
|
-
*
|
|
120
|
+
* Only an interactive person ever may; an agent or any other machine
|
|
116
121
|
* credential manages nothing. Shared auth is deployment-wide, so it takes a
|
|
117
122
|
* maintainer. A personal connector takes a stable principal that is a
|
|
118
123
|
* maintainer, whose plain `/mcp` includes it, or a member of a pool that
|
|
@@ -124,10 +129,10 @@ export declare function mayManageConnector(opts: ServerOptions, authz: Authorize
|
|
|
124
129
|
id: string;
|
|
125
130
|
authScope?: "shared" | "personal";
|
|
126
131
|
}): boolean;
|
|
127
|
-
/** Agent token list, issue, rotate, and revoke: an interactive maintainer only
|
|
132
|
+
/** Agent token list, issue, rotate, and revoke: an interactive maintainer only. */
|
|
128
133
|
export declare function managesAgentTokens(opts: ServerOptions, authz: AuthorizedIdentity): boolean;
|
|
129
134
|
/**
|
|
130
|
-
* Activity reads: a maintainer principal, interactive or not
|
|
135
|
+
* Activity reads: a maintainer principal, interactive or not. Members
|
|
131
136
|
* and agents read none, and an open deployment has no maintainer.
|
|
132
137
|
*/
|
|
133
138
|
export declare function readsActivity(opts: ServerOptions, authz: AuthorizedIdentity): boolean;
|