@zackbart/connecta 0.37.0 → 0.38.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 +80 -0
- package/README.md +2 -2
- 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/auth/clerk-sdk/generated.js +2 -1
- package/dist/auth/cloudflare-access.d.ts +3 -1
- package/dist/auth/cloudflare-access.js +22 -11
- package/dist/auth/downstream-oauth.js +195 -270
- package/dist/auth/downstream-revocation.js +1 -1
- package/dist/auth/oauth-grant.js +11 -0
- package/dist/auth/oauth-token-response.js +1 -1
- package/dist/auth/vercel-agent.js +2 -1
- package/dist/auth-elicitation.js +2 -2
- 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-admission.js +5 -0
- package/dist/call-recovery.js +0 -3
- package/dist/catalog-cache.js +26 -7
- package/dist/catalog-fingerprint.js +2 -18
- package/dist/catalog-revision.js +9 -0
- package/dist/catalog-service.js +51 -20
- 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 +41 -14
- package/dist/config.js +21 -71
- package/dist/connector-caller.js +1 -1
- package/dist/connectors/api-connector.js +4 -3
- package/dist/connectors/guarded-fetch.d.ts +0 -6
- package/dist/connectors/guarded-fetch.js +1 -11
- package/dist/connectors/remote-mcp.js +93 -69
- package/dist/connectors/resource-uri.js +0 -4
- package/dist/credentials.js +3 -0
- package/dist/d1.d.ts +0 -89
- package/dist/d1.js +0 -34
- package/dist/downstream-input.js +2 -2
- package/dist/execute.js +161 -116
- package/dist/executor-result.js +2 -1
- package/dist/executors/quickjs-runtime.js +2 -25
- package/dist/executors/quickjs.js +1 -1
- package/dist/guest-runtime.js +5 -3
- package/dist/guest-types.d.ts +5 -5
- package/dist/http/client.js +1 -1
- package/dist/http/errors.js +2 -2
- package/dist/http/json-schema.js +1 -1
- package/dist/http/node-transport.js +0 -1
- package/dist/http/oauth.js +1 -1
- package/dist/http/paging.js +6 -9
- package/dist/http/provider.js +1 -1
- package/dist/http/safety.js +1 -1
- package/dist/http/services.js +1 -8
- package/dist/http/tools.js +1 -7
- package/dist/http/vendor.js +7 -7
- package/dist/identity.js +2 -6
- package/dist/index.js +1 -2
- package/dist/invocation-auth.js +2 -1
- package/dist/invocation.js +8 -2
- package/dist/meta-tools.js +27 -27
- package/dist/native-tools.js +2 -1
- package/dist/oauth-handoff.js +2 -2
- package/dist/operation-vocabulary.js +1 -1
- package/dist/operator-record.d.ts +8 -2
- package/dist/operator-record.js +18 -50
- package/dist/operator-ui/contract.d.ts +4 -2
- package/dist/operator-ui/generated.js +3 -3
- package/dist/operator-ui/model.d.ts +7 -2
- 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 +1 -3
- package/dist/providers/_shared/rest/value-safety.js +26 -28
- package/dist/providers/breeze/api.js +2 -7
- package/dist/providers/breeze/index.js +4 -2
- package/dist/providers/breeze/tools.js +3 -4
- package/dist/providers/ccb/api.js +1 -6
- package/dist/providers/ccb/index.js +2 -1
- package/dist/providers/ccb/tools.js +1 -6
- package/dist/providers/cloudflare/api.js +2 -2
- package/dist/providers/cloudflare/index.js +2 -1
- package/dist/providers/cloudflare/safety.js +3 -3
- package/dist/providers/docs/tools.js +2 -10
- package/dist/providers/drive/tools.js +12 -25
- package/dist/providers/forms/tools.js +1 -9
- package/dist/providers/gmail/tools.js +5 -21
- package/dist/providers/overflow/api.js +1 -3
- package/dist/providers/overflow/index.js +2 -1
- package/dist/providers/overflow/tools.js +3 -7
- package/dist/providers/planning-center/api.js +2 -1
- 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/tools.js +2 -1
- package/dist/providers/sheets/tools.js +1 -9
- package/dist/providers/slides/tools.js +4 -13
- package/dist/providers/stripe/index.js +2 -1
- package/dist/providers/stripe/safety.js +2 -2
- package/dist/providers/tithely/api.js +2 -7
- package/dist/providers/tithely/index.js +3 -2
- package/dist/providers/tithely/tools.js +7 -5
- package/dist/providers/vercel/api.js +1 -6
- package/dist/providers/vercel/lib/pin.js +1 -1
- package/dist/providers/vercel/safety.js +1 -1
- package/dist/providers/vercel/tools.js +2 -1
- package/dist/redaction-marker.js +2 -2
- package/dist/registry.d.ts +4 -1
- package/dist/registry.js +35 -22
- package/dist/request-principal.js +15 -2
- 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 +1 -1
- package/dist/routes/dispatch.js +10 -0
- package/dist/routes/mcp.js +23 -21
- package/dist/routes/oauth-management.js +1 -1
- package/dist/routes/operator.js +53 -7
- package/dist/routes/shared.d.ts +8 -8
- package/dist/routes/shared.js +22 -15
- package/dist/routes/ui-config.js +6 -6
- package/dist/routes/ui-data.js +2 -2
- package/dist/routes/ui.js +9 -4
- package/dist/runtime/call-admission.js +1 -1
- package/dist/runtime/call.js +1 -1
- package/dist/runtime/settlement.js +1 -1
- package/dist/runtime/shared-read.js +2 -15
- package/dist/sent-secrets.d.ts +14 -6
- package/dist/sent-secrets.js +231 -85
- package/dist/server.js +4 -4
- package/dist/skills.js +8 -35
- package/dist/sqlite.d.ts +0 -19
- package/dist/sqlite.js +2 -83
- package/dist/storage/keys.js +29 -124
- package/dist/storage/sql-native.js +14 -111
- package/dist/storage/sql.d.ts +1 -24
- package/dist/storage/sql.js +1 -12
- package/dist/tool-error.js +4 -0
- package/dist/types.d.ts +4 -3
- package/dist/ui.js +3 -0
- package/dist/url-safety.js +0 -4
- package/dist/usage-guide.js +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +1 -1
- package/documentation/README.md +6 -42
- package/documentation/architecture.md +190 -507
- package/documentation/auth.md +172 -58
- package/documentation/code-mode.md +98 -66
- package/documentation/deploying.md +19 -40
- package/documentation/integrating.md +8 -19
- package/documentation/meta-tools.md +23 -6
- package/documentation/migration-0.33.md +5 -5
- package/documentation/migration-0.34.md +2 -4
- package/documentation/migration-retired-layouts.md +74 -0
- package/documentation/operator-tests.md +19 -119
- package/documentation/operator-ui.md +46 -8
- package/documentation/provider-auth-migration.md +4 -10
- package/documentation/provider-migration-0.29.md +4 -15
- package/examples/worker/AGENTS.md +4 -6
- package/examples/worker/README.md +20 -115
- package/guides/connecting-agents.md +7 -30
- package/guides/how-it-works.md +2 -2
- package/guides/safety-and-trust.md +4 -1
- package/package.json +19 -19
- package/templates/node/README.md +9 -26
- package/templates/node/package.json +1 -1
- 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
package/dist/registry.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
import { wrapViewCredentials } from "./runtime/call.js";
|
|
2
|
+
import { sha256Hex } from "./bytes.js";
|
|
3
|
+
import { catalogRevision } from "./catalog-revision.js";
|
|
1
4
|
import { authCall, authInvoke } from "./auth/native.js";
|
|
2
5
|
import { Clock, Duration, Effect, Random } from "effect";
|
|
3
6
|
import { activityRequest } from "./activity-request.js";
|
|
4
7
|
import { aggregateCallAdmissionSnapshots, ConnectorCallAdmissionController, } from "./call-admission.js";
|
|
5
|
-
import { attachCatalogCache, catalogExpiry, catalogFetchedAt, customCatalogFallback, invalidateCatalogCache, observeUncachedCatalogRefresh, storeCustomCatalogFallback, } from "./catalog-cache.js";
|
|
8
|
+
import { attachCatalogCache, catalogExpiry, catalogFetchedAt, customCatalogFallback, invalidateCatalogCache, observeUncachedCatalogRefresh, retainedCatalogIdentity, storeCustomCatalogFallback, } from "./catalog-cache.js";
|
|
6
9
|
import { carryCatalogFreshness, markCatalogFreshness } from "./catalog-freshness.js";
|
|
7
10
|
import { MAX_CATALOG_TOOLS, MAX_SERIALIZED_CATALOG_BYTES } from "./catalog-limits.js";
|
|
8
11
|
import { CONFIG_DEFAULTS } from "./config-defaults.js";
|
|
9
12
|
import { usesCallerIdentity } from "./connector-caller.js";
|
|
13
|
+
import { forwardsPrincipal } from "./request-principal.js";
|
|
10
14
|
import { canonicalJson } from "./canonical-json.js";
|
|
11
15
|
import { storedCredentialShape } from "./credential-rules.js";
|
|
12
16
|
import { ConnectorCallError } from "./errors.js";
|
|
@@ -21,7 +25,7 @@ import { causeError, runEdge } from "./runtime/run.js";
|
|
|
21
25
|
import { Storage } from "./runtime/services.js";
|
|
22
26
|
import { SharedRead } from "./runtime/shared-read.js";
|
|
23
27
|
import { runOnPartition, storageCompareAndSet, storageDelete, storageGet, storageSet, storageList, } from "./runtime/storage.js";
|
|
24
|
-
import { redactCatalog, sentSecretsFor, sentSecretsForRequest
|
|
28
|
+
import { redactCatalog, sentSecretsFor, sentSecretsForRequest } from "./sent-secrets.js";
|
|
25
29
|
import { GUIDE_SUMMARY_LENGTH, normalizeGuideSummary } from "./skills.js";
|
|
26
30
|
import { jsonCodec, OAUTH_HANDOFF_TTL_SECONDS, oauthHandoffKeys, resultKeys, scopes, stashExpiryKeys, stashLedgerKeys, } from "./storage/keys.js";
|
|
27
31
|
import { assertStaticToolNames, hasControlCharacters } from "./tool-name.js";
|
|
@@ -132,6 +136,8 @@ const STASH_LEDGER_BACKOFF_CAP_MS = 250;
|
|
|
132
136
|
const STASH_WRITE_TIMEOUT_MS = 30_000;
|
|
133
137
|
/** Total cleanup/release or settlement budget, including ledger retries. */
|
|
134
138
|
const STASH_COMPLETION_TIMEOUT_MS = 15_000;
|
|
139
|
+
/** Reservation, chunk writes and completion share one finite outer budget. */
|
|
140
|
+
const STASH_TIMEOUT_MS = STASH_WRITE_TIMEOUT_MS + STASH_COMPLETION_TIMEOUT_MS;
|
|
135
141
|
/** Stop waiting without starting background storage work after the deadline. */
|
|
136
142
|
function completeStash(effect) {
|
|
137
143
|
return effect.pipe(Effect.asVoid, Effect.timeoutOrElse({ duration: Duration.millis(STASH_COMPLETION_TIMEOUT_MS), orElse: () => Effect.void }),
|
|
@@ -228,10 +234,6 @@ function swapStashLedger(plan) {
|
|
|
228
234
|
}).pipe(Effect.catch(() => Effect.succeed({ status: "exhausted" })));
|
|
229
235
|
});
|
|
230
236
|
}
|
|
231
|
-
async function sha256Hex(value) {
|
|
232
|
-
const bytes = new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(value)));
|
|
233
|
-
return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
234
|
-
}
|
|
235
237
|
/**
|
|
236
238
|
* Holds the connector set, resolves addresses, and coalesces tool listings
|
|
237
239
|
* within one request. Remote MCP clients own persisted response caching. Connector failures are isolated: a broken
|
|
@@ -449,6 +451,9 @@ export class Registry {
|
|
|
449
451
|
}
|
|
450
452
|
contextFor(id, baseUrl, requestScope = {}, callOptions = {}, scope) {
|
|
451
453
|
const connector = this.connectors.get(id);
|
|
454
|
+
if (!scope?.caller && scope?.operatorCaller && connector && forwardsPrincipal(connector)) {
|
|
455
|
+
scope = { ...scope, caller: scope.operatorCaller };
|
|
456
|
+
}
|
|
452
457
|
const credentialConfig = connector?.credential;
|
|
453
458
|
let credentialAccess;
|
|
454
459
|
let credentialProgram;
|
|
@@ -494,6 +499,7 @@ export class Registry {
|
|
|
494
499
|
attachCatalogCache(context, {
|
|
495
500
|
storage: this.opts.catalogStorage ?? this.opts.storage,
|
|
496
501
|
partition: this.catalogPartition(scope),
|
|
502
|
+
credentialRevision: () => catalogRevision(this.opts.credentialVault),
|
|
497
503
|
sharedPartition: JSON.stringify([baseUrl, this.opts.publicUrl ?? null, scope?.caller?.pool ?? null]),
|
|
498
504
|
defaultTtlMs: (this.opts.toolCacheTtlSeconds ?? CONFIG_DEFAULTS.discovery.catalogTtlSeconds) * 1000,
|
|
499
505
|
minTtlMs: (this.opts.catalogMinTtlSeconds ?? CONFIG_DEFAULTS.discovery.catalogMinTtlSeconds) * 1000,
|
|
@@ -510,7 +516,7 @@ export class Registry {
|
|
|
510
516
|
: {}),
|
|
511
517
|
});
|
|
512
518
|
sentSecretsFor(context);
|
|
513
|
-
|
|
519
|
+
wrapViewCredentials(context);
|
|
514
520
|
attachOAuthPartition(context, this.oauthPartition);
|
|
515
521
|
// Downstream OAuth state is sealed under the vault key, bound to this
|
|
516
522
|
// connector and owner. The sealer rides beside the context, not on it.
|
|
@@ -539,12 +545,13 @@ export class Registry {
|
|
|
539
545
|
}
|
|
540
546
|
/** The admitted identity fields used by both catalog caching and reporting. */
|
|
541
547
|
catalogPartition(scope) {
|
|
548
|
+
const caller = scope?.caller ?? scope?.operatorCaller;
|
|
542
549
|
return JSON.stringify([
|
|
543
550
|
scope?.principalKey ?? this.opts.credentialOwner ?? null,
|
|
544
551
|
scope?.subjectKey ?? null,
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
552
|
+
caller?.identity ?? null,
|
|
553
|
+
caller?.authenticated ?? false,
|
|
554
|
+
caller?.pool ?? null,
|
|
548
555
|
]);
|
|
549
556
|
}
|
|
550
557
|
/** Bounded, in-memory tokens; no principal or catalog data is persisted. */
|
|
@@ -572,7 +579,7 @@ export class Registry {
|
|
|
572
579
|
*
|
|
573
580
|
* `chunks[n]` lands on `resultKeys.chunk(id, n)` inside `partition` — the
|
|
574
581
|
* layout connecta.result reads back, so a page fetches only the chunks it covers
|
|
575
|
-
* instead of the whole stored result
|
|
582
|
+
* instead of the whole stored result. Chunking is a read-cost
|
|
576
583
|
* decision, not a capacity one: however many keys an envelope occupies, it
|
|
577
584
|
* is one stash entry charged its total ASCII length.
|
|
578
585
|
*
|
|
@@ -580,6 +587,7 @@ export class Registry {
|
|
|
580
587
|
* row in one ledger record in storage, booked by compare-and-set before any
|
|
581
588
|
* chunk is written, so every isolate and process sharing the store sees the
|
|
582
589
|
* same entries and bytes. A pending write keeps its finite charge reserved.
|
|
590
|
+
* The whole operation, including reservation, stops after 45 seconds.
|
|
583
591
|
* Writes stop after 30 seconds; absolute chunk expiries cannot pass the
|
|
584
592
|
* booked deadline even on late commit. Rejected responses leave writes
|
|
585
593
|
* unconfirmed and retain their charge. Cleanup/release and settlement each
|
|
@@ -669,13 +677,17 @@ export class Registry {
|
|
|
669
677
|
yield* authCall(observe, [ctx, id, facts]).pipe(Effect.catch((error) => Effect.sync(() => logFailure(this.opts.logger, "catalog refresh observation failed", failureRecord({ connector: id }, error)))));
|
|
670
678
|
}
|
|
671
679
|
}
|
|
672
|
-
this.catalogObservedAt.set(id, catalogFetchedAt(ctx) ?? Date.now());
|
|
673
680
|
const provenance = catalogExpiry(ctx);
|
|
674
681
|
const freshUntil = provenance && !provenance.staleFallback ? provenance.expiresAt : 0;
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
682
|
+
// Principal-forwarded listings belong to one caller, never to the shared
|
|
683
|
+
// operator observation store. The actual catalog cache is identity-bound.
|
|
684
|
+
if (!forwardsPrincipal(connector)) {
|
|
685
|
+
this.catalogObservedAt.set(id, catalogFetchedAt(ctx) ?? Date.now());
|
|
686
|
+
this.catalogAccess.set(id, {
|
|
687
|
+
state: freshUntil > Date.now() ? "fresh" : "stale",
|
|
688
|
+
observedAt: new Date().toISOString(),
|
|
689
|
+
});
|
|
690
|
+
}
|
|
679
691
|
return markCatalogFreshness(accepted, freshUntil);
|
|
680
692
|
});
|
|
681
693
|
}
|
|
@@ -863,7 +875,7 @@ export class Registry {
|
|
|
863
875
|
else
|
|
864
876
|
yield* completeStash(settle);
|
|
865
877
|
return written;
|
|
866
|
-
}).pipe(Effect.provideService(Storage, self.opts.storage)),
|
|
878
|
+
}).pipe(Effect.timeoutOrElse({ duration: Duration.millis(STASH_TIMEOUT_MS), orElse: () => Effect.succeed(false) }), Effect.provideService(Storage, self.opts.storage)),
|
|
867
879
|
recheckResultAccess: () => Effect.succeed(true),
|
|
868
880
|
storeOAuthHandoff: (self, connectorId, state, principalKey) => Effect.gen(function* () {
|
|
869
881
|
const key = oauthHandoffKeys.handoff(connectorId, yield* Effect.tryPromise({ try: () => sha256Hex(state), catch: (error) => error }));
|
|
@@ -907,16 +919,17 @@ export class Registry {
|
|
|
907
919
|
if (!self.connectors.has(id))
|
|
908
920
|
return Effect.fail(new Error(`Unknown connector "${id}"`));
|
|
909
921
|
const owner = self.contextFor(id, baseUrl, requestScope, callOptions, scope);
|
|
910
|
-
return duringViewEffect(owner, (signal) => {
|
|
922
|
+
return duringViewEffect(owner, (signal) => Effect.gen(function* () {
|
|
911
923
|
if (!requestScope)
|
|
912
|
-
return self.loadTools(id, baseUrl, owner.requestScope, { ...callOptions, signal }, scope);
|
|
924
|
+
return yield* self.loadTools(id, baseUrl, owner.requestScope, { ...callOptions, signal }, scope);
|
|
925
|
+
const identity = self.connectors.get(id)?.staticTools ? "static" : retainedCatalogIdentity(owner);
|
|
913
926
|
let loads = self.requestCatalogLoads.get(requestScope);
|
|
914
927
|
if (!loads) {
|
|
915
928
|
loads = new Map();
|
|
916
929
|
self.requestCatalogLoads.set(requestScope, loads);
|
|
917
930
|
}
|
|
918
931
|
const requestLoads = loads;
|
|
919
|
-
const key = JSON.stringify([id, scope?.principalKey, scope?.subjectKey, scope?.caller]);
|
|
932
|
+
const key = JSON.stringify([id, scope?.principalKey, scope?.subjectKey, scope?.caller, identity]);
|
|
920
933
|
let load = requestLoads.get(key);
|
|
921
934
|
if (!load) {
|
|
922
935
|
const started = new SharedRead((signal) => self.loadTools(id, baseUrl, requestScope, { ...callOptions, signal }, scope), () => {
|
|
@@ -928,8 +941,8 @@ export class Registry {
|
|
|
928
941
|
requestLoads.set(key, started);
|
|
929
942
|
load = started;
|
|
930
943
|
}
|
|
931
|
-
return load.join(signal);
|
|
932
|
-
});
|
|
944
|
+
return yield* load.join(signal);
|
|
945
|
+
}));
|
|
933
946
|
}),
|
|
934
947
|
credentialDriftFor: (self, id) => Effect.gen(function* () {
|
|
935
948
|
const credential = self.connectors.get(id)?.credential;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The admitted principal a request-auth resolver may receive
|
|
1
|
+
// The admitted principal a request-auth resolver may receive.
|
|
2
2
|
//
|
|
3
3
|
// Only the caller core attached from the admitting authorization counts
|
|
4
4
|
// (`callerOf`, see connector-caller.ts). No argument, program or header can
|
|
@@ -9,13 +9,26 @@
|
|
|
9
9
|
import { isAgentName } from "./agent-identity.js";
|
|
10
10
|
import { callerOf } from "./connector-caller.js";
|
|
11
11
|
import { validIdentityReference } from "./identity.js";
|
|
12
|
+
const forwardingConnectors = new WeakSet();
|
|
13
|
+
/** Internal construction tag; never inferred from request data or descriptions. */
|
|
14
|
+
export function tagPrincipalForwarding(connector) {
|
|
15
|
+
forwardingConnectors.add(connector);
|
|
16
|
+
return connector;
|
|
17
|
+
}
|
|
18
|
+
export function forwardsPrincipal(connector) {
|
|
19
|
+
for (let current = connector; current; current = Object.getPrototypeOf(current)) {
|
|
20
|
+
if (forwardingConnectors.has(current))
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
12
25
|
/**
|
|
13
26
|
* The principal this operation's admitted caller is. Anything else is refused
|
|
14
27
|
* through `refuse`, the connector's own `auth_required` error.
|
|
15
28
|
*/
|
|
16
29
|
export function requestPrincipalFor(policy, ctx, refuse) {
|
|
17
30
|
const caller = callerOf(ctx);
|
|
18
|
-
//
|
|
31
|
+
// A context no request admitted, or an open deployment's anonymous caller.
|
|
19
32
|
if (!caller?.authenticated)
|
|
20
33
|
throw refuse("acts as the admitted caller, and this operation has none");
|
|
21
34
|
const { identity } = caller;
|
package/dist/request-state.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sha256Hex } from "./bytes.js";
|
|
1
2
|
import { ProtocolError, INVALID_PARAMS } from "@modelcontextprotocol/server";
|
|
2
3
|
export const REQUEST_STATE_TTL_MS = 10 * 60_000;
|
|
3
4
|
export const MAX_INPUT_ROUNDS = 3;
|
|
@@ -16,6 +17,5 @@ export function canonicalState(value) {
|
|
|
16
17
|
: item);
|
|
17
18
|
}
|
|
18
19
|
export async function requestDigest(tool, args) {
|
|
19
|
-
|
|
20
|
-
return Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
20
|
+
return sha256Hex(canonicalState({ tool, args }));
|
|
21
21
|
}
|
|
@@ -67,7 +67,7 @@ export async function routeAccessTokens(context, manager) {
|
|
|
67
67
|
const admin = await authorizeUiIdentity(request, baseUrl, opts.config.auth, "access token management", context.runtimeContext, opts.config.logger);
|
|
68
68
|
if (!admin.ok)
|
|
69
69
|
return admin.response;
|
|
70
|
-
// Interactive maintainers alone manage agent tokens
|
|
70
|
+
// Interactive maintainers alone manage agent tokens; anyone else
|
|
71
71
|
// gets the same private 404 every operator route gives them.
|
|
72
72
|
if (!managesAgentTokens(opts, admin) || !admin.identity.principal) {
|
|
73
73
|
return privateJson({ error: "not found" }, { status: 404 });
|
|
@@ -6,7 +6,7 @@ function activityToolVisible(access, event) {
|
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
8
|
* One disclosure rule for the timeline and the UI's last-call overlay. Only
|
|
9
|
-
* maintainers read activity
|
|
9
|
+
* maintainers read activity, and only after the optional
|
|
10
10
|
* read gate. They may see other principals' shared-connector history,
|
|
11
11
|
* including request IDs, client name/version, and actor kind/id/namespace, on
|
|
12
12
|
* plain /mcp and in every declared pool, within the tools that pool declares.
|
package/dist/routes/connect.js
CHANGED
|
@@ -88,7 +88,7 @@ async function connect(context) {
|
|
|
88
88
|
if (!authz.ok) {
|
|
89
89
|
// Access sign-in is enforced at the edge. Clerk needs its own sign-in page.
|
|
90
90
|
// The sign-in page returns to this link, which then sends a maintainer on
|
|
91
|
-
// to the operator UI. Pool members cannot use that UI yet
|
|
91
|
+
// to the operator UI. Pool members cannot use that UI yet, so
|
|
92
92
|
// signing in there would strand them.
|
|
93
93
|
if (authz.response.status === 401 &&
|
|
94
94
|
authorizationCredential(request).kind === "absent" &&
|
|
@@ -107,7 +107,7 @@ async function connect(context) {
|
|
|
107
107
|
if (authz.principalKey !== handoff.principal || !mayManageConnector(opts, authz, connector)) {
|
|
108
108
|
return refuse("Sign in as the user who requested this connection and has permission to manage it.");
|
|
109
109
|
}
|
|
110
|
-
// The operator UI serves maintainers only until member views ship
|
|
110
|
+
// The operator UI serves maintainers only until member views ship.
|
|
111
111
|
// A member's OAuth link starts here directly, as it does without a UI.
|
|
112
112
|
const viaUi = Boolean(opts.config.ui) && admitsMaintainer(opts, authz);
|
|
113
113
|
if (!connector.startAuth) {
|
|
@@ -158,7 +158,7 @@ function credentialRequest(context, connectorId, action) {
|
|
|
158
158
|
if (request.method === "DELETE") {
|
|
159
159
|
// Clearing nothing is refused, as testing nothing is, so the route
|
|
160
160
|
// agrees with the operator contract, which offers `clear` only once a
|
|
161
|
-
// value is stored in the caller's partition
|
|
161
|
+
// value is stored in the caller's partition. A value that cannot
|
|
162
162
|
// be read may still be cleared: removing it is how one recovers.
|
|
163
163
|
const stored = yield* Effect.tryPromise({
|
|
164
164
|
try: () => vault.metadata(connectorId, owner),
|
package/dist/routes/dispatch.js
CHANGED
|
@@ -4,6 +4,8 @@ import * as HttpServerError from "effect/http/HttpServerError";
|
|
|
4
4
|
import * as HttpServerRequest from "effect/http/HttpServerRequest";
|
|
5
5
|
import * as HttpServerResponse from "effect/http/HttpServerResponse";
|
|
6
6
|
import { notFoundResponse } from "../branding.js";
|
|
7
|
+
import { authorizationCredential } from "../inbound-credential.js";
|
|
8
|
+
import { failureRecord, logFailure } from "../operator-record.js";
|
|
7
9
|
import { isAgentPath } from "./agent-path.js";
|
|
8
10
|
import { MCP_CORS_HEADERS } from "./mcp.js";
|
|
9
11
|
import { routeConnect } from "./connect.js";
|
|
@@ -27,6 +29,14 @@ export function createRouteDispatcher(mcp, metadata, health) {
|
|
|
27
29
|
const afterUi = (program) => (context) => Effect.gen(function* () {
|
|
28
30
|
const ui = context.opts.config.ui;
|
|
29
31
|
const response = ui ? yield* Effect.promise(() => ui.handle(context)) : null;
|
|
32
|
+
if (response?.status === 401) {
|
|
33
|
+
const authorizationHeader = authorizationCredential(context.request).kind;
|
|
34
|
+
logFailure(context.opts.config.logger, "operator_refused", failureRecord({
|
|
35
|
+
reason: authorizationHeader === "invalid" ? "authorization_header_invalid" : "authentication_failed",
|
|
36
|
+
accessContext: Boolean(context.runtimeContext?.access),
|
|
37
|
+
authorizationHeader,
|
|
38
|
+
}, response));
|
|
39
|
+
}
|
|
30
40
|
return response ?? (yield* program(context));
|
|
31
41
|
});
|
|
32
42
|
const optional = (program) => (context) => Effect.flatMap(program(context), (response) => (response ? Effect.succeed(response) : fallback(context)));
|
package/dist/routes/mcp.js
CHANGED
|
@@ -32,7 +32,7 @@ const deadlineResponses = new WeakSet();
|
|
|
32
32
|
/**
|
|
33
33
|
* The most a `subscriptions/listen` classification reads. A listen is a small
|
|
34
34
|
* 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
|
|
35
|
+
* bounded by this fixed cap rather than by the host's body limit.
|
|
36
36
|
*/
|
|
37
37
|
export const MAX_LISTEN_CLASSIFICATION_BYTES = 64 * 1024;
|
|
38
38
|
/**
|
|
@@ -286,7 +286,7 @@ function admitted(controller, signal) {
|
|
|
286
286
|
}), (lease) => Effect.sync(() => lease.release()), { interruptible: true });
|
|
287
287
|
}
|
|
288
288
|
/**
|
|
289
|
-
* 404 for any `?toolkit=` value, kept after the feature's retirement
|
|
289
|
+
* 404 for any `?toolkit=` value, kept after the feature's retirement.
|
|
290
290
|
*
|
|
291
291
|
* Toolkits are gone, but the URLs that named them are not: clients were handed
|
|
292
292
|
* MCP endpoint URLs with `?toolkit=` baked in, and nothing about upgrading the
|
|
@@ -409,6 +409,7 @@ function serveMcp(request, requestSignal, requestScope, opts, baseUrl, actor, re
|
|
|
409
409
|
credentialHandoffUrl: opts.config.ui?.credentialHandoffUrl(baseUrl),
|
|
410
410
|
...(activity ? { activity } : {}),
|
|
411
411
|
defaultToolTimeoutMs: opts.config.calls.defaultTimeoutMs ?? opts.config.execute.hostCallTimeoutMs,
|
|
412
|
+
maxProgramResultChars: opts.config.execute.maxResultChars,
|
|
412
413
|
probeTimeoutMs: opts.config.discovery.probeTimeoutMs,
|
|
413
414
|
discoveryConcurrency: opts.config.discovery.concurrency,
|
|
414
415
|
requestSignal,
|
|
@@ -504,20 +505,7 @@ function serveMcp(request, requestSignal, requestScope, opts, baseUrl, actor, re
|
|
|
504
505
|
if (!response.body)
|
|
505
506
|
return response;
|
|
506
507
|
const body = yield* upwardStep(authInvoke(response, "text", []));
|
|
507
|
-
|
|
508
|
-
if (response.headers.get("Content-Type")?.includes("application/json")) {
|
|
509
|
-
// Structured redaction also joins text blocks before a secret split
|
|
510
|
-
// across them can cross the serialization boundary.
|
|
511
|
-
try {
|
|
512
|
-
text = JSON.stringify(redactAgentOutput(sentSecrets, JSON.parse(body)));
|
|
513
|
-
}
|
|
514
|
-
catch {
|
|
515
|
-
/* Non-JSON diagnostics still pass through the text boundary. */
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
// Parsed JSON already passes every string through the boundary. A second
|
|
519
|
-
// serialized pass would mistake a blob's encoding for credential bytes.
|
|
520
|
-
text ??= redactAgentOutput(sentSecrets, body);
|
|
508
|
+
const text = sentSecrets.serializedAgentOutput(body, response.headers.get("Content-Type")?.includes("application/json") ?? false);
|
|
521
509
|
const headers = new Headers(response.headers);
|
|
522
510
|
headers.delete("Content-Length");
|
|
523
511
|
return new Response(text, { status: response.status, statusText: response.statusText, headers });
|
|
@@ -651,7 +639,7 @@ export function createMcpRoute(opts) {
|
|
|
651
639
|
}));
|
|
652
640
|
// A request naming `subscriptions/listen` defers its permit: nothing of
|
|
653
641
|
// its body is read until auth and endpoint admission pass, and then at
|
|
654
|
-
// most MAX_LISTEN_CLASSIFICATION_BYTES
|
|
642
|
+
// most MAX_LISTEN_CLASSIFICATION_BYTES. Only a body-confirmed
|
|
655
643
|
// modern listen goes without a permit; trusting the header alone would
|
|
656
644
|
// let a legacy tools/call bypass the pool, so anything else takes one
|
|
657
645
|
// after classification. Every other request takes its permit here,
|
|
@@ -662,15 +650,21 @@ export function createMcpRoute(opts) {
|
|
|
662
650
|
const refuseAdmission = (error) => {
|
|
663
651
|
if (!(error instanceof ExecutorAdmissionError))
|
|
664
652
|
return Effect.fail(error);
|
|
665
|
-
if (error.code === "executor_cancelled")
|
|
653
|
+
if (error.code === "executor_cancelled") {
|
|
654
|
+
// The lifetime timer can withdraw a fallback waiter before the
|
|
655
|
+
// enclosing timeout settles. Keep its public deadline response.
|
|
656
|
+
if (localAbort.signal.aborted && !request.signal.aborted) {
|
|
657
|
+
return Effect.succeed(cors(mcpRefusal(sentSecrets, 504, -33003, "MCP request lifetime exceeded.")));
|
|
658
|
+
}
|
|
666
659
|
return Effect.fail(request.signal.reason ?? error);
|
|
660
|
+
}
|
|
667
661
|
if (error.code === "executor_overloaded")
|
|
668
662
|
warnAdmissionRejected(error);
|
|
669
663
|
return Effect.succeed(cors(requestAdmissionFailure(sentSecrets, error)));
|
|
670
664
|
};
|
|
671
665
|
const admission = listenHeader
|
|
672
666
|
? Result.succeed(undefined)
|
|
673
|
-
: yield* Effect.result(admitted(opts.requestAdmission,
|
|
667
|
+
: yield* Effect.result(admitted(opts.requestAdmission, localAbort.signal));
|
|
674
668
|
if (Result.isFailure(admission))
|
|
675
669
|
return yield* refuseAdmission(admission.failure);
|
|
676
670
|
const remainingMs = admission.success
|
|
@@ -687,7 +681,15 @@ export function createMcpRoute(opts) {
|
|
|
687
681
|
localAbort.abort(new Error("MCP request lifetime exceeded."));
|
|
688
682
|
}, remainingMs);
|
|
689
683
|
}
|
|
690
|
-
|
|
684
|
+
// Pass the body explicitly: inheriting it makes Node add an identity
|
|
685
|
+
// TransformStream whose pull-ahead can exceed the inspection bound.
|
|
686
|
+
const localRequest = new Request(request, {
|
|
687
|
+
signal: localAbort.signal,
|
|
688
|
+
// This is an inbound SDK view, not an outgoing keepalive or no-cors
|
|
689
|
+
// fetch; Node rejects an explicit stream with either.
|
|
690
|
+
...(request.body ? { body: request.body, duplex: "half", keepalive: false } : {}),
|
|
691
|
+
...(request.body && request.mode === "no-cors" ? { mode: "cors" } : {}),
|
|
692
|
+
});
|
|
691
693
|
if (admission.success && admission.success.waitMs > 0) {
|
|
692
694
|
opts.config.logger.debug("[connecta] MCP request admitted after queue wait", {
|
|
693
695
|
waitMs: admission.success.waitMs,
|
|
@@ -803,7 +805,7 @@ export function createMcpRoute(opts) {
|
|
|
803
805
|
return cors(mcpRefusal(sentSecrets, 413, -33006, "MCP request body too large."));
|
|
804
806
|
}
|
|
805
807
|
if (listenHeader && verdict === "other") {
|
|
806
|
-
const late = yield* Effect.result(admitted(opts.requestAdmission,
|
|
808
|
+
const late = yield* Effect.result(admitted(opts.requestAdmission, localAbort.signal));
|
|
807
809
|
if (Result.isFailure(late))
|
|
808
810
|
return yield* refuseAdmission(late.failure);
|
|
809
811
|
}
|
|
@@ -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
|
|
@@ -21,7 +22,8 @@
|
|
|
21
22
|
//
|
|
22
23
|
// Imported by the /ui and /activity graphs only; never from the root entry.
|
|
23
24
|
import { Effect } from "effect";
|
|
24
|
-
import {
|
|
25
|
+
import { operatorViewerKeys } from "../storage/keys.js";
|
|
26
|
+
import { runEdge, withDeadlineEffect } from "../runtime/run.js";
|
|
25
27
|
import { isMaintainer } from "../membership.js";
|
|
26
28
|
import { admitsMaintainer, authorize, authorizeUiIdentity, memberConnectors, privateJson, } from "./shared.js";
|
|
27
29
|
/** A response the route has already decided on. */
|
|
@@ -47,36 +49,80 @@ export function authorized({ request, baseUrl, opts, runtimeContext }, partition
|
|
|
47
49
|
export function authorizedPerson({ request, baseUrl, opts, runtimeContext }, purpose) {
|
|
48
50
|
return Effect.suspend(() => admitted(authorizeUiIdentity(request, baseUrl, opts.config.auth, purpose, runtimeContext, opts.config.logger)));
|
|
49
51
|
}
|
|
52
|
+
// Cache resolved keys only; request promises and identities never outlive a read.
|
|
53
|
+
const viewerSigningKeys = new WeakMap();
|
|
54
|
+
/** Stable across isolates, opaque even for guessable Access email identities. */
|
|
55
|
+
export function operatorViewerKey(context, authz) {
|
|
56
|
+
const attempt = (read) => Effect.tryPromise({ try: read, catch: (error) => error });
|
|
57
|
+
return withDeadlineEffect(() => Effect.gen(function* () {
|
|
58
|
+
const storage = context.opts.config.storage;
|
|
59
|
+
let key = viewerSigningKeys.get(storage);
|
|
60
|
+
if (!key) {
|
|
61
|
+
let raw = yield* attempt(() => storage.get(operatorViewerKeys.signingKey));
|
|
62
|
+
if (raw === null) {
|
|
63
|
+
const candidate = crypto.randomUUID();
|
|
64
|
+
raw = (yield* attempt(() => storage.compareAndSet(operatorViewerKeys.signingKey, null, candidate)))
|
|
65
|
+
? candidate
|
|
66
|
+
: yield* attempt(() => storage.get(operatorViewerKeys.signingKey));
|
|
67
|
+
}
|
|
68
|
+
if (!raw || !/^[0-9a-f-]{36}$/.test(raw))
|
|
69
|
+
return yield* Effect.fail(new Error("Operator viewer key unavailable"));
|
|
70
|
+
const bytes = new TextEncoder().encode(raw);
|
|
71
|
+
key = yield* attempt(() => crypto.subtle.importKey("raw", bytes, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]));
|
|
72
|
+
viewerSigningKeys.set(storage, key);
|
|
73
|
+
}
|
|
74
|
+
const { principal, subject, actor, interactive, agent } = authz.identity;
|
|
75
|
+
const identity = new TextEncoder().encode(JSON.stringify([
|
|
76
|
+
principal?.namespace,
|
|
77
|
+
principal?.id,
|
|
78
|
+
subject?.namespace,
|
|
79
|
+
subject?.id,
|
|
80
|
+
actor.kind,
|
|
81
|
+
actor.namespace,
|
|
82
|
+
actor.id,
|
|
83
|
+
interactive,
|
|
84
|
+
agent,
|
|
85
|
+
]));
|
|
86
|
+
const signingKey = key;
|
|
87
|
+
const digest = yield* attempt(() => crypto.subtle.sign("HMAC", signingKey, identity));
|
|
88
|
+
return bytesToHex(new Uint8Array(digest));
|
|
89
|
+
}), {
|
|
90
|
+
timeoutMs: 500,
|
|
91
|
+
signal: context.request.signal,
|
|
92
|
+
timeoutError: new Error("Operator viewer key timed out"),
|
|
93
|
+
}).pipe(Effect.catch(() => refuse("operator identity could not be read", 503)));
|
|
94
|
+
}
|
|
50
95
|
/** The partition an identity carries into a registry view of the given connectors. */
|
|
51
|
-
export function scopeFor(authz, connectorIds = "all") {
|
|
96
|
+
export function scopeFor(authz, authenticated, connectorIds = "all") {
|
|
52
97
|
return {
|
|
53
98
|
connectorIds,
|
|
54
99
|
...(authz.subjectKey ? { subjectKey: authz.subjectKey } : {}),
|
|
55
100
|
...(authz.principalKey ? { principalKey: authz.principalKey } : {}),
|
|
101
|
+
operatorCaller: { identity: authz.identity, authenticated },
|
|
56
102
|
};
|
|
57
103
|
}
|
|
58
104
|
/**
|
|
59
|
-
* The whole registry, for a maintainer only
|
|
105
|
+
* The whole registry, for a maintainer only. Everyone else gets the
|
|
60
106
|
* private 404 plain `/mcp` gives them.
|
|
61
107
|
*/
|
|
62
108
|
export function visibleRegistry({ opts }, authz) {
|
|
63
109
|
// Operator catalogs describe the default endpoint's view, never a pool view.
|
|
64
110
|
if (!admitsMaintainer(opts, authz))
|
|
65
111
|
return refuse("not found", 404);
|
|
66
|
-
return Effect.succeed(opts.registry.scoped(scopeFor(authz)));
|
|
112
|
+
return Effect.succeed(opts.registry.scoped(scopeFor(authz, opts.config.auth.length > 0)));
|
|
67
113
|
}
|
|
68
114
|
/**
|
|
69
|
-
* The connectors a connector-auth route may resolve
|
|
115
|
+
* The connectors a connector-auth route may resolve: every connector
|
|
70
116
|
* for a maintainer, the connectors of their pools for a pool member, and the
|
|
71
117
|
* private 404 for anyone else. mayManageConnector still decides each action.
|
|
72
118
|
*/
|
|
73
119
|
export function managementRegistry({ opts }, authz) {
|
|
74
120
|
if (isMaintainer(opts.maintainers, authz.identity))
|
|
75
|
-
return Effect.succeed(opts.registry.scoped(scopeFor(authz)));
|
|
121
|
+
return Effect.succeed(opts.registry.scoped(scopeFor(authz, opts.config.auth.length > 0)));
|
|
76
122
|
const ids = memberConnectors(opts, authz.identity);
|
|
77
123
|
if (ids !== "all" && ids.size === 0)
|
|
78
124
|
return refuse("not found", 404);
|
|
79
|
-
return Effect.succeed(opts.registry.scoped(scopeFor(authz, ids === "all" ? "all" : [...ids])));
|
|
125
|
+
return Effect.succeed(opts.registry.scoped(scopeFor(authz, opts.config.auth.length > 0, ids === "all" ? "all" : [...ids])));
|
|
80
126
|
}
|
|
81
127
|
/**
|
|
82
128
|
* Run one route program to its Response.
|
package/dist/routes/shared.d.ts
CHANGED
|
@@ -78,7 +78,7 @@ export declare function authorize(request: Request, baseUrl: string, auth: reado
|
|
|
78
78
|
response: Response;
|
|
79
79
|
}>;
|
|
80
80
|
/**
|
|
81
|
-
* Admission at an agent endpoint, `/agent/<pool
|
|
81
|
+
* Admission at an agent endpoint, `/agent/<pool>`. Only the built-in
|
|
82
82
|
* agent credential adapters are consulted: Clerk and Access never run, cookies and Clerk
|
|
83
83
|
* handshake parameters are stripped, and the runtime context, which carries
|
|
84
84
|
* Access's `ctx.access` even on a bypassed path, is never passed. Anything but
|
|
@@ -88,31 +88,31 @@ export declare function authorizeAgent(request: Request, baseUrl: string, auth:
|
|
|
88
88
|
/** Human routes reject machine credentials before any verification or storage I/O. */
|
|
89
89
|
export declare function authorizeUiIdentity(request: Request, baseUrl: string, auth: readonly InboundAuth[], purpose: string, runtimeContext?: RuntimeExecutionContext, logger?: Logger): Promise<Awaited<ReturnType<typeof authorize>>>;
|
|
90
90
|
export declare function isSameOrigin(request: Request, baseUrl: string): boolean;
|
|
91
|
-
export declare function withSecurityHeaders(response: Response, requestUrl: URL
|
|
91
|
+
export declare function withSecurityHeaders(response: Response, requestUrl: URL): Response;
|
|
92
92
|
type AuthorizedIdentity = Extract<Awaited<ReturnType<typeof authorize>>, {
|
|
93
93
|
ok: true;
|
|
94
94
|
}>;
|
|
95
95
|
/**
|
|
96
|
-
* Plain `/mcp` and every operator route admit a maintainer
|
|
96
|
+
* Plain `/mcp` and every operator route admit a maintainer. An open
|
|
97
97
|
* deployment has no inbound auth and so no maintainers: its one anonymous
|
|
98
98
|
* caller keeps the whole default endpoint, as the startup warning says.
|
|
99
99
|
*/
|
|
100
100
|
export declare function admitsMaintainer(opts: ServerOptions, authz: AuthorizedIdentity): boolean;
|
|
101
101
|
/**
|
|
102
102
|
* 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
|
|
103
|
+
* same 404. An agent is never admitted here, even when listed.
|
|
104
104
|
*/
|
|
105
105
|
export declare function memberPool(opts: ServerOptions, name: string, identity: Readonly<AuthenticatedIdentity>): ResolvedPool | undefined;
|
|
106
106
|
/** The declared pool `/agent/<name>` serves to this agent, or undefined for the same 404. */
|
|
107
107
|
export declare function agentPool(opts: ServerOptions, name: string, identity: Readonly<AuthenticatedIdentity>): ResolvedPool | undefined;
|
|
108
108
|
/**
|
|
109
|
-
* The connectors of every pool this identity is a member of
|
|
109
|
+
* The connectors of every pool this identity is a member of: what a
|
|
110
110
|
* pool member may ever manage, and only their personal ones among these.
|
|
111
111
|
*/
|
|
112
112
|
export declare function memberConnectors(opts: ServerOptions, identity: Readonly<AuthenticatedIdentity>): "all" | ReadonlySet<string>;
|
|
113
113
|
/**
|
|
114
114
|
* Whether this identity may start, finish, or remove a connector's auth
|
|
115
|
-
*
|
|
115
|
+
* Only an interactive person ever may; an agent or any other machine
|
|
116
116
|
* credential manages nothing. Shared auth is deployment-wide, so it takes a
|
|
117
117
|
* maintainer. A personal connector takes a stable principal that is a
|
|
118
118
|
* maintainer, whose plain `/mcp` includes it, or a member of a pool that
|
|
@@ -124,10 +124,10 @@ export declare function mayManageConnector(opts: ServerOptions, authz: Authorize
|
|
|
124
124
|
id: string;
|
|
125
125
|
authScope?: "shared" | "personal";
|
|
126
126
|
}): boolean;
|
|
127
|
-
/** Agent token list, issue, rotate, and revoke: an interactive maintainer only
|
|
127
|
+
/** Agent token list, issue, rotate, and revoke: an interactive maintainer only. */
|
|
128
128
|
export declare function managesAgentTokens(opts: ServerOptions, authz: AuthorizedIdentity): boolean;
|
|
129
129
|
/**
|
|
130
|
-
* Activity reads: a maintainer principal, interactive or not
|
|
130
|
+
* Activity reads: a maintainer principal, interactive or not. Members
|
|
131
131
|
* and agents read none, and an open deployment has no maintainer.
|
|
132
132
|
*/
|
|
133
133
|
export declare function readsActivity(opts: ServerOptions, authz: AuthorizedIdentity): boolean;
|