@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
package/dist/http/vendor.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Provider kit
|
|
1
|
+
// Provider kit. Unreachable from src/index.ts.
|
|
2
2
|
//
|
|
3
3
|
// A vendor is an `HttpApi`: endpoints with Schemas for path params, query,
|
|
4
4
|
// and payload, from which requests are encoded and the endpoint manifest is
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
// empty 200. Every answer, from a declared operation or a hatch's path, takes
|
|
14
14
|
// one route: bounded read, JSON, closed public classifier facts captured,
|
|
15
15
|
// the request's sent secrets redacted from
|
|
16
|
-
// every value of the whole body before anything can cut or extract from it
|
|
17
|
-
//
|
|
16
|
+
// every value of the whole body before anything can cut or extract from it,
|
|
17
|
+
// its keys kept for value safety to match by name, status (with
|
|
18
18
|
// its operation, so value safety decides what a failure may quote), the
|
|
19
19
|
// vendor's `result` hook, then the connection's ResultSafety. A provider
|
|
20
20
|
// cannot reach a body earlier. A Content-Type redaction altered selects the
|
|
21
21
|
// format the vendor reads as JSON, so no altered type routes a body around
|
|
22
|
-
// ResultSafety as text or bytes
|
|
22
|
+
// ResultSafety as text or bytes.
|
|
23
23
|
import { Context, Effect } from "effect";
|
|
24
24
|
import * as HttpClientRequest from "effect/http/HttpClientRequest";
|
|
25
25
|
import * as HttpApi from "effect/http-api/HttpApi";
|
|
@@ -233,12 +233,12 @@ export function connect(vendor, guarded, options) {
|
|
|
233
233
|
: defect)));
|
|
234
234
|
});
|
|
235
235
|
// Header-derived format decisions see only redacted Content-Type. Names are protocol
|
|
236
|
-
// constants `Headers` must accept, so only the values are redacted, together
|
|
236
|
+
// constants `Headers` must accept, so only the values are redacted, together.
|
|
237
237
|
const entries = [...reply.headers];
|
|
238
238
|
const values = secrets.redact(entries.map(([, value]) => value));
|
|
239
239
|
const headers = new Headers(entries.map(([name], index) => [name, values[index]]));
|
|
240
240
|
// Whole, before a detail line trims it or a projection extracts from it. Values only:
|
|
241
|
-
// result safety and projections match fields by their vendor names
|
|
241
|
+
// result safety and projections match fields by their vendor names.
|
|
242
242
|
let classifierFacts;
|
|
243
243
|
const parsed = reply.bytes !== undefined
|
|
244
244
|
? {
|
|
@@ -400,7 +400,7 @@ export function connect(vendor, guarded, options) {
|
|
|
400
400
|
/**
|
|
401
401
|
* The Content-Type a format decision reads. One redaction altered reads as
|
|
402
402
|
* `application/json`: a vendor's text or binary choice for it would hand
|
|
403
|
-
* result safety a body it cannot read field by field
|
|
403
|
+
* result safety a body it cannot read field by field.
|
|
404
404
|
*/
|
|
405
405
|
function formatType(redacted) {
|
|
406
406
|
return redactionAltered(redacted) ? "application/json" : redacted.toLowerCase();
|
package/dist/identity.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { sha256Hex } from "./bytes.js";
|
|
1
2
|
const IDENTITY_PART_RE = /^[\x21-\x7e]{1,256}$/;
|
|
2
|
-
const encoder = new TextEncoder();
|
|
3
3
|
export function validIdentityReference(value) {
|
|
4
4
|
return Boolean(value &&
|
|
5
5
|
typeof value.namespace === "string" &&
|
|
@@ -7,11 +7,7 @@ export function validIdentityReference(value) {
|
|
|
7
7
|
IDENTITY_PART_RE.test(value.namespace) &&
|
|
8
8
|
IDENTITY_PART_RE.test(value.id));
|
|
9
9
|
}
|
|
10
|
-
function bytesToHex(bytes) {
|
|
11
|
-
return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
12
|
-
}
|
|
13
10
|
/** Deterministic pseudonymous partition; raw emails and ids do not enter keys. */
|
|
14
11
|
export async function identityStorageKey(identity) {
|
|
15
|
-
|
|
16
|
-
return bytesToHex(new Uint8Array(digest));
|
|
12
|
+
return sha256Hex(`${identity.namespace}\n${identity.id}`);
|
|
17
13
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -41,6 +41,6 @@ export type { Registry } from "./registry.js";
|
|
|
41
41
|
export type { RemoteMcpOptions, RemoteMcpAuth, RemoteMcpRequestAuth, RemoteMcpRedirectPolicy, } from "./connectors/remote-mcp.js";
|
|
42
42
|
export type { AgentRequestPrincipal, HumanRequestPrincipal, PrincipalForwarding, PrincipalRequestToken, RequestPrincipal, RequestPrincipalPolicy, RequestToken, RequestTokenInput, } from "./request-token.js";
|
|
43
43
|
export type { ApiHandlerContext, ApiOAuthAccess, ApiOAuthClientAuthentication, ApiOAuthConfig, ApiOptions, ApiTool, } from "./connectors/api.js";
|
|
44
|
-
export type { ConnectaBranding, ConnectaTheme, Connector, ConnectorCallAdmissionInput, ConnectorCallAdmissionPolicy, ConnectorCallAdmissionRule, ConnectorRollingWindowBudget, ConnectorCredentialAccess, ConnectorCredentialConfig, ConnectorCredentialFieldConfig, ConnectorCredentialValues, ConnectorContext, ConnectorAuthDescription, ConnectorDescription, ConnectorToolDescription, ConnectorUsageGuide, ConnectorSkill, ConnectorSkillResourceContents, DescribedEndpoint, ConnectorStatus, CredentialTestResult, AdmittingExecutor, AdmissionSnapshot, ExecuteResult, Executor, ExecutorLease, ExecutorProvider, InboundAuth, InboundAuthRuntimeContext, UiAuthConfig, AuthResult, AuthenticatedIdentity, IdentityReference, JsonSchema, KVStorage, Logger, ToolDef, ToolAnnotations, } from "./types.js";
|
|
44
|
+
export type { ConnectaBranding, ConnectaTheme, Connector, ConnectorCallAdmissionInput, ConnectorCallAdmissionPolicy, ConnectorCallAdmissionRule, ConnectorRollingWindowBudget, ConnectorCredentialAccess, ConnectorCredentialConfig, ConnectorCredentialFieldConfig, ConnectorCredentialValues, AgentRouteNames, ConnectorContext, ConnectorAuthDescription, ConnectorDescription, ConnectorToolDescription, ConnectorUsageGuide, ConnectorSkill, ConnectorSkillResourceContents, DescribedEndpoint, ConnectorStatus, CredentialFailureClass, CredentialTestResult, AdmittingExecutor, AdmissionSnapshot, ExecuteResult, Executor, ExecutorLease, ExecutorProvider, InboundAuth, InboundAuthRuntimeContext, UiAuthConfig, AuthResult, AuthenticatedIdentity, IdentityReference, JsonSchema, KVStorage, Logger, ToolDef, ToolAnnotations, } from "./types.js";
|
|
45
45
|
export type { ActivityActor, ActivityCatalogChange, ActivityCallSource, ActivityOutcome, ActivityPage, ActivityReadActor, ActivityReadEvent, ActivityReader, ActivityReadGate, ActivityReadPage, ActivitySink, ActivityStore, AgentFriction, ToolCallActivityEvent, } from "./activity.js";
|
|
46
46
|
export { META_TOOL_NAMES } from "./meta-tool-names.js";
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { deploymentRoutes } from "./agent-routes.js";
|
|
1
2
|
import { describeConfigSources } from "./config-value-sources.js";
|
|
2
3
|
import { credentialTestRule, describeCredentialTestMismatch } from "./credential-rules.js";
|
|
3
4
|
import { Registry } from "./registry.js";
|
|
4
5
|
import { parseConnectorAccess, POOL_NAME_RE } from "./connector-access.js";
|
|
5
6
|
import { assertsAgents } from "./agent-identity.js";
|
|
6
7
|
import { ConfigError } from "./config-schema.js";
|
|
7
|
-
import { memberContext, resolveMaintainers,
|
|
8
|
+
import { memberContext, peopleInSeveralPools, resolveMaintainers, resolvePoolMembers, } from "./membership.js";
|
|
8
9
|
import { createFetchHandler } from "./server.js";
|
|
9
10
|
import { droppedBrandingUrls, droppedThemeTokens, droppedUiAuthUrls } from "./branding.js";
|
|
10
11
|
import { readConfig } from "./config.js";
|
|
@@ -68,7 +69,7 @@ function resolvePools(pools, readOnly, registry, members) {
|
|
|
68
69
|
resolved.set(name, Object.freeze({
|
|
69
70
|
access,
|
|
70
71
|
readOnly: pool.readOnly ?? readOnly,
|
|
71
|
-
members: Object.freeze(
|
|
72
|
+
members: Object.freeze(resolvePoolMembers(pool.members, path, members)),
|
|
72
73
|
...(pool.owner !== undefined ? { owner: pool.owner.trim() } : {}),
|
|
73
74
|
...(pool.description !== undefined ? { description: pool.description.trim() } : {}),
|
|
74
75
|
...(pool.surface === "native" ? { native: { executeCode: pool.executeCode ?? true } } : {}),
|
|
@@ -185,6 +186,7 @@ export function createConnecta(config) {
|
|
|
185
186
|
// Every structural check runs here, before any work: what comes back is the
|
|
186
187
|
// configuration the deployment runs with, defaults applied once.
|
|
187
188
|
const { input, resolved } = readConfig(config);
|
|
189
|
+
const routes = deploymentRoutes(config);
|
|
188
190
|
const { logger } = resolved;
|
|
189
191
|
const storage = resolved.storage;
|
|
190
192
|
const registry = new Registry([...resolved.connectors], {
|
|
@@ -208,10 +210,14 @@ export function createConnecta(config) {
|
|
|
208
210
|
maxResultBytes: resolved.calls.maxResultBytes,
|
|
209
211
|
results: resolved.results,
|
|
210
212
|
classification: resolved.classification,
|
|
213
|
+
routes,
|
|
211
214
|
});
|
|
212
215
|
const members = memberContext(resolved.auth, resolved.agents);
|
|
213
216
|
const maintainers = resolveMaintainers(input.maintainers, resolved.auth.some((provider) => !assertsAgents(provider)), members);
|
|
214
217
|
const pools = resolvePools(resolved.pools, resolved.readOnly, registry, members);
|
|
218
|
+
for (const person of peopleInSeveralPools(pools, maintainers)) {
|
|
219
|
+
logger.warn("[connecta] Plain /mcp refuses a person in several pools", person);
|
|
220
|
+
}
|
|
215
221
|
warnInsecureConfig(resolved);
|
|
216
222
|
const requestAdmission = new AdmissionController(resolved.admission.requests);
|
|
217
223
|
let codeAdmission;
|
|
@@ -249,6 +255,7 @@ export function createConnecta(config) {
|
|
|
249
255
|
executor,
|
|
250
256
|
executorName: configuredExecutorName,
|
|
251
257
|
requestAdmission,
|
|
258
|
+
routes,
|
|
252
259
|
});
|
|
253
260
|
let closePromise;
|
|
254
261
|
return {
|
package/dist/invocation-auth.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bytesToHex } from "./bytes.js";
|
|
1
2
|
import { authCall, authFlow, registerAuth } from "./auth/native.js";
|
|
2
3
|
import { upwardStep } from "./upward.js";
|
|
3
4
|
import { runEdge } from "./runtime/run.js";
|
|
@@ -34,7 +35,7 @@ export async function classificationDigest(definition) {
|
|
|
34
35
|
annotations,
|
|
35
36
|
schema,
|
|
36
37
|
}));
|
|
37
|
-
return
|
|
38
|
+
return bytesToHex(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)));
|
|
38
39
|
}
|
|
39
40
|
catch {
|
|
40
41
|
// A digest refusal blocks recovery, not an ordinary call.
|
package/dist/invocation.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { routePhrases } from "./agent-routes.js";
|
|
1
2
|
import { authCall, registerAuthMethod } from "./auth/native.js";
|
|
2
3
|
import { withAuthorizationHandoffEffect } from "./authorization-handoff.js";
|
|
3
4
|
import { hasControlCharacters } from "./tool-name.js";
|
|
@@ -9,7 +10,7 @@ import { downstreamValue } from "./mcp-result.js";
|
|
|
9
10
|
import { carryFailureFacts, classifiedFailure, failureRecord, logFailure, recordedToolName, } from "./operator-record.js";
|
|
10
11
|
import { splitAddress } from "./registry.js";
|
|
11
12
|
import { causeError, runEdge, withDeadlineEffect } from "./runtime/run.js";
|
|
12
|
-
import { validateCatalogToolInput } from "./validate.js";
|
|
13
|
+
import { schemaExample, validateCatalogToolInput } from "./validate.js";
|
|
13
14
|
import { sentSecretsFor, sentSecretsForRequest } from "./sent-secrets.js";
|
|
14
15
|
import { recoveryRecord, recordRecoveryFacts } from "./call-recovery.js";
|
|
15
16
|
import { callFromContext, contextView, viewServices, Credentials } from "./runtime/call.js";
|
|
@@ -141,6 +142,7 @@ export class InvocationFailure extends Error {
|
|
|
141
142
|
}
|
|
142
143
|
}
|
|
143
144
|
keepErrorName(InvocationFailure, "InvocationFailure");
|
|
145
|
+
const withoutExample = ({ example: _example, ...stated }) => stated;
|
|
144
146
|
/**
|
|
145
147
|
* Shared downstream invocation engine. Address/catalog resolution is delegated
|
|
146
148
|
* to the request-local CatalogService; every remaining call semantic lives
|
|
@@ -406,6 +408,14 @@ export class InvocationService {
|
|
|
406
408
|
return error;
|
|
407
409
|
return {
|
|
408
410
|
...error,
|
|
411
|
+
// An example comes only from the published schema, never a connector's copy of the
|
|
412
|
+
// call, unless the connector states why there is none.
|
|
413
|
+
validation: {
|
|
414
|
+
...withoutExample(error.validation),
|
|
415
|
+
...(error.validation.exampleUnavailable === undefined && resolved?.definition.inputSchema
|
|
416
|
+
? schemaExample(resolved.definition.inputSchema)
|
|
417
|
+
: {}),
|
|
418
|
+
},
|
|
409
419
|
connector: target.connector.id,
|
|
410
420
|
operation: `${target.connector.id}.${target.toolName}`,
|
|
411
421
|
nextAction: this.catalog.searchRecovery({
|
|
@@ -552,7 +562,7 @@ export class InvocationService {
|
|
|
552
562
|
return framingError("read_only_endpoint", `Tool "${canonicalAddress}" is a write, and ${connectorReadOnly ? `connector "${target.connector.id}"` : "this endpoint"} is read-only. Nothing here can send it.`);
|
|
553
563
|
}
|
|
554
564
|
if (refusal === "destructive_tool_requires_approval") {
|
|
555
|
-
return framingError("destructive_tool_requires_approval", `Tool "${canonicalAddress}" is a write.
|
|
565
|
+
return framingError("destructive_tool_requires_approval", `Tool "${canonicalAddress}" is a write. ${routePhrases(context).writeNeedsApproval}`);
|
|
556
566
|
}
|
|
557
567
|
// Remote MCP tools advertise their input schema in the catalog. Validate
|
|
558
568
|
// against that same request-local definition before admission or provider
|
|
@@ -580,7 +590,12 @@ export class InvocationService {
|
|
|
580
590
|
return yield* Effect.fail(callSignal.reason);
|
|
581
591
|
attempts = 1;
|
|
582
592
|
const call = (permit) => Effect.gen({ self: this }, function* () {
|
|
583
|
-
const operationContext = this.registry.contextFor(target.connector.id, this.catalog.baseUrl, this.catalog.requestScope, defined({
|
|
593
|
+
const operationContext = this.registry.contextFor(target.connector.id, this.catalog.baseUrl, this.catalog.requestScope, defined({
|
|
594
|
+
signal: callSignal,
|
|
595
|
+
timeoutMs: context.timeoutMs,
|
|
596
|
+
defer: this.catalog.defer,
|
|
597
|
+
routes: context.routes,
|
|
598
|
+
}));
|
|
584
599
|
const lease = callFromContext(operationContext, write ? "write" : "read", {
|
|
585
600
|
admitted: true,
|
|
586
601
|
// An invocation deadline aborts when its call finishes. A
|
|
@@ -604,11 +619,11 @@ export class InvocationService {
|
|
|
604
619
|
yield* Effect.gen(function* () {
|
|
605
620
|
if (target.connector.credential) {
|
|
606
621
|
if (!connectorContext.credential) {
|
|
607
|
-
throw new ConnectorCallError("auth_required", "Operator-managed credential storage is not configured.
|
|
608
|
-
|
|
622
|
+
throw new ConnectorCallError("auth_required", "Operator-managed credential storage is not configured. " +
|
|
623
|
+
routePhrases(connectorContext).authorizeCall(target.connector.id));
|
|
609
624
|
}
|
|
610
625
|
if (!(yield* Context.get(lease.services, Credentials).values)) {
|
|
611
|
-
throw new ConnectorCallError("auth_required", `Connector "${target.connector.id}" has no stored credential.
|
|
626
|
+
throw new ConnectorCallError("auth_required", `Connector "${target.connector.id}" has no stored credential. ${routePhrases(connectorContext).authorizeCall()}`);
|
|
612
627
|
}
|
|
613
628
|
}
|
|
614
629
|
yield* resolveInvocationAuthEffect(connector, callTool, connectorContext);
|
package/dist/membership.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export interface ResolvedPool {
|
|
|
22
22
|
readOnly: boolean;
|
|
23
23
|
/** Exactly the pool's `tools`; nothing intersects or widens it. */
|
|
24
24
|
access: ConnectorAccess;
|
|
25
|
+
/** Non-empty and homogeneous: all people or all agents. */
|
|
25
26
|
members: readonly Member[];
|
|
26
27
|
/** Trimmed `owner`: the Workspace address agents in this pool act as. */
|
|
27
28
|
owner?: string;
|
|
@@ -40,11 +41,8 @@ export interface MemberContext {
|
|
|
40
41
|
}
|
|
41
42
|
/** The namespaces bare ids may resolve in, read from the resolved providers. */
|
|
42
43
|
export declare function memberContext(auth: readonly InboundAuth[], agents: readonly string[]): MemberContext;
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
-
* ambiguous, unknown, or repeated (INV-11).
|
|
46
|
-
*/
|
|
47
|
-
export declare function resolveMembers(value: unknown, path: string, context: MemberContext): Member[];
|
|
44
|
+
/** Resolve a pool's members, which are all people or all agents (INV-11). */
|
|
45
|
+
export declare function resolvePoolMembers(value: unknown, path: string, context: MemberContext): Member[];
|
|
48
46
|
/**
|
|
49
47
|
* `maintainers`: people only. Required whenever an inbound provider can admit
|
|
50
48
|
* a person, which is any provider but the built-in agent-token adapter. With
|
|
@@ -53,5 +51,15 @@ export declare function resolveMembers(value: unknown, path: string, context: Me
|
|
|
53
51
|
export declare function resolveMaintainers(value: unknown, admitsPeople: boolean, context: MemberContext): readonly IdentityReference[];
|
|
54
52
|
/** Whether an admitted identity is this member. An agent never matches a person. */
|
|
55
53
|
export declare function isMember(member: Member, identity: Readonly<AuthenticatedIdentity>): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Non-maintainer people listed in several pools, in declaration order. Plain
|
|
56
|
+
* `/mcp` routes a person by their single pool, so it refuses each of these.
|
|
57
|
+
* An open deployment admits no people, so it has none.
|
|
58
|
+
*/
|
|
59
|
+
export declare function peopleInSeveralPools(pools: ReadonlyMap<string, ResolvedPool>, maintainers: readonly IdentityReference[]): Array<{
|
|
60
|
+
namespace: string;
|
|
61
|
+
id: string;
|
|
62
|
+
pools: string[];
|
|
63
|
+
}>;
|
|
56
64
|
/** Whether an admitted identity is a listed maintainer. */
|
|
57
65
|
export declare function isMaintainer(maintainers: readonly IdentityReference[], identity: Readonly<AuthenticatedIdentity>): boolean;
|
package/dist/membership.js
CHANGED
|
@@ -65,7 +65,7 @@ const memberKey = (member) => member.kind === "agent" ? `agent\0${member.name}`
|
|
|
65
65
|
* Resolve one list of references, throwing on any that is malformed,
|
|
66
66
|
* ambiguous, unknown, or repeated (INV-11).
|
|
67
67
|
*/
|
|
68
|
-
|
|
68
|
+
function resolveMembers(value, path, context) {
|
|
69
69
|
if (!Array.isArray(value)) {
|
|
70
70
|
throw new ConfigError(`${path} must be an array of principal ids, { namespace, id }, or "agent:<name>".`);
|
|
71
71
|
}
|
|
@@ -79,6 +79,14 @@ export function resolveMembers(value, path, context) {
|
|
|
79
79
|
return Object.freeze(member);
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
|
+
/** Resolve a pool's members, which are all people or all agents (INV-11). */
|
|
83
|
+
export function resolvePoolMembers(value, path, context) {
|
|
84
|
+
const members = resolveMembers(value, path, context);
|
|
85
|
+
if (!members.every((member) => member.kind === members[0].kind)) {
|
|
86
|
+
throw new ConfigError(`${path} mixes people and agents; list only one kind.`);
|
|
87
|
+
}
|
|
88
|
+
return members;
|
|
89
|
+
}
|
|
82
90
|
/**
|
|
83
91
|
* `maintainers`: people only. Required whenever an inbound provider can admit
|
|
84
92
|
* a person, which is any provider but the built-in agent-token adapter. With
|
|
@@ -88,7 +96,7 @@ export function resolveMaintainers(value, admitsPeople, context) {
|
|
|
88
96
|
const path = "ConnectaConfig.maintainers";
|
|
89
97
|
if (value === undefined || (Array.isArray(value) && value.length === 0)) {
|
|
90
98
|
if (admitsPeople) {
|
|
91
|
-
throw new ConfigError(`${path} is required with inbound auth: list the people who
|
|
99
|
+
throw new ConfigError(`${path} is required with inbound auth: list the people who receive the whole catalog at plain /mcp, administer shared ` +
|
|
92
100
|
"credentials, manage agent tokens, and read activity.");
|
|
93
101
|
}
|
|
94
102
|
return Object.freeze([]);
|
|
@@ -115,6 +123,30 @@ export function isMember(member, identity) {
|
|
|
115
123
|
principal.namespace === member.namespace &&
|
|
116
124
|
principal.id === member.id);
|
|
117
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Non-maintainer people listed in several pools, in declaration order. Plain
|
|
128
|
+
* `/mcp` routes a person by their single pool, so it refuses each of these.
|
|
129
|
+
* An open deployment admits no people, so it has none.
|
|
130
|
+
*/
|
|
131
|
+
export function peopleInSeveralPools(pools, maintainers) {
|
|
132
|
+
if (maintainers.length === 0)
|
|
133
|
+
return [];
|
|
134
|
+
const people = new Map();
|
|
135
|
+
for (const [name, pool] of pools) {
|
|
136
|
+
for (const member of pool.members) {
|
|
137
|
+
if (member.kind !== "human")
|
|
138
|
+
continue;
|
|
139
|
+
if (maintainers.some((m) => m.namespace === member.namespace && m.id === member.id))
|
|
140
|
+
continue;
|
|
141
|
+
const key = JSON.stringify([member.namespace, member.id]);
|
|
142
|
+
const person = people.get(key) ?? { namespace: member.namespace, id: member.id, pools: [] };
|
|
143
|
+
if (!person.pools.includes(name))
|
|
144
|
+
person.pools.push(name);
|
|
145
|
+
people.set(key, person);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return [...people.values()].filter((person) => person.pools.length > 1);
|
|
149
|
+
}
|
|
118
150
|
/** Whether an admitted identity is a listed maintainer. */
|
|
119
151
|
export function isMaintainer(maintainers, identity) {
|
|
120
152
|
return maintainers.some((maintainer) => isMember({ kind: "human", ...maintainer }, identity));
|
package/dist/meta-tools.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { META_ROUTES } from "./agent-routes.js";
|
|
2
|
+
import { toolError } from "./tool-error.js";
|
|
1
3
|
import { upwardStep } from "./upward.js";
|
|
2
4
|
import { authCall, authInvoke, authFlow, authMethod, authStorage, registerAuth } from "./auth/native.js";
|
|
3
5
|
import { registryProgram } from "./runtime/registry.js";
|
|
@@ -20,7 +22,7 @@ import { AUTHORIZE_OUTPUT, SEARCH_OUTPUT, SKILLS_OUTPUT } from "./meta-output.js
|
|
|
20
22
|
import { RESULT_TTL_SECONDS, resultKeys } from "./storage/keys.js";
|
|
21
23
|
import { agentOutputOperations, sentSecretsForRequest } from "./sent-secrets.js";
|
|
22
24
|
import { captureDownstreamInput } from "./downstream-input.js";
|
|
23
|
-
import { PROGRAM_RESULT_WIRE_BYTES } from "./program-result.js";
|
|
25
|
+
import { PROGRAM_RESULT_PAGE_BYTES, PROGRAM_RESULT_WIRE_BYTES } from "./program-result.js";
|
|
24
26
|
export { MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES, MAX_SEARCH_LIMIT };
|
|
25
27
|
const enc = new TextEncoder();
|
|
26
28
|
// Each page is a slice of payload bytes, so a leading U+FEFF is data.
|
|
@@ -37,15 +39,7 @@ function errorResult(message) {
|
|
|
37
39
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
38
40
|
}
|
|
39
41
|
function discoveryErrorResult(error) {
|
|
40
|
-
|
|
41
|
-
error: {
|
|
42
|
-
code: error.code,
|
|
43
|
-
message: error.message,
|
|
44
|
-
retryable: false,
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
result.isError = true;
|
|
48
|
-
return result;
|
|
42
|
+
return toolError({ code: error.code, message: error.message, retryable: false });
|
|
49
43
|
}
|
|
50
44
|
const discoveryResult = function (operation, hint) {
|
|
51
45
|
return authFlow(function* () {
|
|
@@ -86,8 +80,8 @@ function isValidResultOffset(value) {
|
|
|
86
80
|
* Backwards, never forwards: re-serving a few bytes the caller already has is
|
|
87
81
|
* recoverable, silently skipping the rest of a character is not. Offsets the
|
|
88
82
|
* server itself produced (`nextOffset`) are already boundaries and come back
|
|
89
|
-
* unchanged, so this only moves an offset a client computed on its own
|
|
90
|
-
*
|
|
83
|
+
* unchanged, so this only moves an offset a client computed on its own.
|
|
84
|
+
* An offset at or past `bytes.length` is left alone — there is no
|
|
91
85
|
* character there to split.
|
|
92
86
|
*/
|
|
93
87
|
function alignStartToCharBoundary(bytes, offset) {
|
|
@@ -149,6 +143,15 @@ function base64Of(bytes) {
|
|
|
149
143
|
}
|
|
150
144
|
return out;
|
|
151
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* The page a notice's nextAction asks a program to read: it fits the program's
|
|
148
|
+
* return even if every byte escapes to six JSON characters, with room for the
|
|
149
|
+
* page envelope, and never asks for more than the stash or bridge will serve.
|
|
150
|
+
*/
|
|
151
|
+
function programPageBytes(results) {
|
|
152
|
+
const fits = Math.floor(((results.programChars ?? MAX_EXECUTE_RESULT_CHARS) - 1024) / 6);
|
|
153
|
+
return Math.max(4, Math.min(fits, results.cap, PROGRAM_RESULT_PAGE_BYTES));
|
|
154
|
+
}
|
|
152
155
|
/** Opens a write's notice, ahead of any paging instruction. */
|
|
153
156
|
const WRITE_ALREADY_RAN = "This write already ran: do not call it again to see its result.";
|
|
154
157
|
/** UTF-8 cannot represent lone UTF-16 surrogates without changing them. */
|
|
@@ -176,7 +179,7 @@ function isWellFormedText(text) {
|
|
|
176
179
|
* reported the decoy near the top instead. A write never gets this: repeating
|
|
177
180
|
* it is the one thing its notice forbids.
|
|
178
181
|
*/
|
|
179
|
-
const
|
|
182
|
+
const readReduceFirst = (routes) => `To find something specific, repeat this read inside ${routes.program} with connecta.call and filter or search the result there, returning only what matters`;
|
|
180
183
|
function pagingHint(results, totalBytes, preview) {
|
|
181
184
|
if (results.pagingTool === "read_result") {
|
|
182
185
|
const shown = preview.kind === "prefix"
|
|
@@ -188,13 +191,13 @@ function pagingHint(results, totalBytes, preview) {
|
|
|
188
191
|
}
|
|
189
192
|
if (!results.write) {
|
|
190
193
|
if (results.binding.program)
|
|
191
|
-
return
|
|
194
|
+
return `filter/map/slice data inside ${results.routes.program} and return only what you need; page this completed return with connecta.result using nextAction.`;
|
|
192
195
|
const [shown, full] = preview.kind === "prefix"
|
|
193
196
|
? [`Bytes 0-${preview.bytes} of ${totalBytes} follow.`, "to read it in full, page the rest"]
|
|
194
197
|
: preview.kind === "text-of-envelope"
|
|
195
198
|
? ["Its text follows.", `to read the full ${totalBytes}-byte content array as JSON, page it`]
|
|
196
199
|
: [`The result is ${totalBytes} bytes.`, "to read it in full, page it"];
|
|
197
|
-
return `${shown} ${
|
|
200
|
+
return `${shown} ${readReduceFirst(results.routes)}; ${full} with connecta.result using nextAction.`;
|
|
198
201
|
}
|
|
199
202
|
const body = preview.kind === "prefix"
|
|
200
203
|
? `Bytes 0-${preview.bytes} of ${totalBytes} follow; page the rest with connecta.result using nextAction.`
|
|
@@ -229,7 +232,7 @@ const stashResult = function (bytes, results, preview, rawText) {
|
|
|
229
232
|
try {
|
|
230
233
|
// Base64 permits byte-range decoding, and splitting the envelope across
|
|
231
234
|
// keys keeps a page's storage read proportional to the page instead of to
|
|
232
|
-
// the whole result
|
|
235
|
+
// the whole result. Chunk 0 carries the header; the connecta.result
|
|
233
236
|
// reader below maps a byte offset back to chunk index and base64 quad.
|
|
234
237
|
const chunkBytes = resultChunkBytes(totalBytes);
|
|
235
238
|
const binding = base64Of(enc.encode(JSON.stringify(results.binding)));
|
|
@@ -261,7 +264,7 @@ const stashResult = function (bytes, results, preview, rawText) {
|
|
|
261
264
|
? `Paging is unavailable. ${WRITE_ALREADY_RAN}`
|
|
262
265
|
: results.pagingTool
|
|
263
266
|
? "Paging is unavailable."
|
|
264
|
-
:
|
|
267
|
+
: `Paging is unavailable. Use ${results.routes.program} to reduce read-only results before returning them.`,
|
|
265
268
|
};
|
|
266
269
|
}
|
|
267
270
|
return {
|
|
@@ -280,7 +283,7 @@ const stashResult = function (bytes, results, preview, rawText) {
|
|
|
280
283
|
// A byte may cost six JSON characters when escaped. Leave room for the
|
|
281
284
|
// result id, page counters, and continuation metadata in the returned page.
|
|
282
285
|
arguments: {
|
|
283
|
-
code: `async () => await connecta.result(${JSON.stringify(id)}, { offset: ${preview.kind === "prefix" ? preview.bytes : 0}, maxBytes: ${
|
|
286
|
+
code: `async () => await connecta.result(${JSON.stringify(id)}, { offset: ${preview.kind === "prefix" ? preview.bytes : 0}, maxBytes: ${programPageBytes(results)} })`,
|
|
284
287
|
},
|
|
285
288
|
},
|
|
286
289
|
};
|
|
@@ -300,6 +303,8 @@ const pageProgramResultNative = /* @__PURE__ */ registerAuth(pageProgramResult,
|
|
|
300
303
|
const bytes = enc.encode(text);
|
|
301
304
|
const write = resolved.definition.classification !== "read";
|
|
302
305
|
const notice = yield* upwardStep(stashResult(bytes, {
|
|
306
|
+
// Program-call pages name only execute_code, listed wherever programs run.
|
|
307
|
+
routes: META_ROUTES,
|
|
303
308
|
binding: {
|
|
304
309
|
identity: registry.resultIdentity(),
|
|
305
310
|
baseUrl,
|
|
@@ -325,10 +330,10 @@ const pageProgramResultNative = /* @__PURE__ */ registerAuth(pageProgramResult,
|
|
|
325
330
|
});
|
|
326
331
|
});
|
|
327
332
|
/** Program returns share the direct stash, with all host-admitted call dependencies. */
|
|
328
|
-
export function guardProgramReturn(value, registry, baseUrl, dependencies, secrets, logger, maxChars, completedWrite) {
|
|
329
|
-
return runEdge(guardProgramReturnNative(value, registry, baseUrl, dependencies, secrets, logger, maxChars, completedWrite));
|
|
333
|
+
export function guardProgramReturn(value, registry, baseUrl, dependencies, secrets, logger, maxChars, completedWrite, programChars) {
|
|
334
|
+
return runEdge(guardProgramReturnNative(value, registry, baseUrl, dependencies, secrets, logger, maxChars, completedWrite, programChars));
|
|
330
335
|
}
|
|
331
|
-
const guardProgramReturnNative = /* @__PURE__ */ registerAuth(guardProgramReturn, function (value, registry, baseUrl, dependencies, secrets, logger, maxChars, completedWrite) {
|
|
336
|
+
const guardProgramReturnNative = /* @__PURE__ */ registerAuth(guardProgramReturn, function (value, registry, baseUrl, dependencies, secrets, logger, maxChars, completedWrite, programChars) {
|
|
332
337
|
return authFlow(function* () {
|
|
333
338
|
const serialized = serializeResultText(value);
|
|
334
339
|
if (serialized.length <= maxChars)
|
|
@@ -339,6 +344,8 @@ const guardProgramReturnNative = /* @__PURE__ */ registerAuth(guardProgramReturn
|
|
|
339
344
|
const cap = Math.min(registry.maxResultBytes, ...dependencies.map((dependency) => resolveMaxResultBytes(registry.getConnector(dependency.connector)?.maxResultBytes, registry.maxResultBytes)));
|
|
340
345
|
const results = {
|
|
341
346
|
secrets,
|
|
347
|
+
// A program return names only execute_code, which every surface running programs lists.
|
|
348
|
+
routes: META_ROUTES,
|
|
342
349
|
binding: {
|
|
343
350
|
identity: registry.resultIdentity(),
|
|
344
351
|
baseUrl,
|
|
@@ -347,6 +354,7 @@ const guardProgramReturnNative = /* @__PURE__ */ registerAuth(guardProgramReturn
|
|
|
347
354
|
},
|
|
348
355
|
write: completedWrite,
|
|
349
356
|
cap,
|
|
357
|
+
...(programChars !== undefined ? { programChars } : {}),
|
|
350
358
|
set: authMethod((id, chunks, ttl) => registryProgram(registry, "stashResult", id, chunks, ttl)),
|
|
351
359
|
warn: () => logFailure(logger, "result paging unavailable", failureRecord({})),
|
|
352
360
|
};
|
|
@@ -453,16 +461,11 @@ function escapedHeadOf(bytes, cap) {
|
|
|
453
461
|
* Bound a downstream MCP `content` array by `cap`.
|
|
454
462
|
*
|
|
455
463
|
* A lone text block is measured, stashed, previewed, and paged as its text:
|
|
456
|
-
* its envelope adds nothing but a wrapper and a second layer of JSON escaping
|
|
457
|
-
* which made a JSON payload's preview unreadable and inflated every page.
|
|
464
|
+
* its envelope adds nothing but a wrapper and a second layer of JSON escaping.
|
|
458
465
|
*
|
|
459
466
|
* Anything else is measured as the serialized envelope — the same string that
|
|
460
467
|
* gets stashed and paged, and the one that counts every block rather than only
|
|
461
|
-
* the text ones.
|
|
462
|
-
* meant an oversized all-image result scored zero bytes and was returned inline
|
|
463
|
-
* unbounded, with no `resultId` to page from; and measuring one string while
|
|
464
|
-
* truncating another left `totalBytes` describing something the cap was never
|
|
465
|
-
* compared against.
|
|
468
|
+
* the text ones. Measure the same representation that gets truncated and paged.
|
|
466
469
|
*
|
|
467
470
|
* Over the cap, what a client gets depends on whether a preview is usable.
|
|
468
471
|
* Several text blocks preview as their text joined by newlines, while
|
|
@@ -543,12 +546,13 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
543
546
|
// identity lets remote connectors reuse one downstream client inside that
|
|
544
547
|
// request without leaking request-bound I/O into the next one.
|
|
545
548
|
const requestScope = opts.requestScope ?? {};
|
|
549
|
+
const routes = opts.routes ?? META_ROUTES;
|
|
546
550
|
if (opts.canManageAuth || opts.oauthConnectUrl || opts.credentialHandoffUrl || opts.oauthConnectUnavailable) {
|
|
547
551
|
bindAuthorizationHandoffs(requestScope, authMethod((connector) => authCall(authorizationHandoff, [
|
|
548
552
|
registry,
|
|
549
553
|
baseUrl,
|
|
550
554
|
requestScope,
|
|
551
|
-
opts,
|
|
555
|
+
{ ...opts, routes },
|
|
552
556
|
{ connector },
|
|
553
557
|
])));
|
|
554
558
|
}
|
|
@@ -569,6 +573,7 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
569
573
|
requestSignal: opts.requestSignal,
|
|
570
574
|
// searchRoute keeps its top-level default. In-program callers use a
|
|
571
575
|
// separate CatalogService configured for connecta.search.
|
|
576
|
+
routes,
|
|
572
577
|
});
|
|
573
578
|
const invocation = new InvocationService(registry, catalog, opts.activity);
|
|
574
579
|
/** MCP adapter: shared invocation semantics plus MCP-only result shaping. */
|
|
@@ -586,6 +591,7 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
586
591
|
activitySource: opts.activitySource,
|
|
587
592
|
sentSecrets,
|
|
588
593
|
readOnly: opts.readOnly,
|
|
594
|
+
routes,
|
|
589
595
|
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
590
596
|
...(opts.requestSignal !== undefined ? { requestSignal: opts.requestSignal } : {}),
|
|
591
597
|
unwrapResult: call.resultMode === "value",
|
|
@@ -614,6 +620,7 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
614
620
|
const cap = resolveMaxResultBytes(resolved.connector.maxResultBytes, globalCap);
|
|
615
621
|
const results = {
|
|
616
622
|
secrets,
|
|
623
|
+
routes,
|
|
617
624
|
binding: {
|
|
618
625
|
identity: registry.resultIdentity(),
|
|
619
626
|
baseUrl,
|
|
@@ -624,6 +631,7 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
624
631
|
write: resolved.definition.classification !== "read",
|
|
625
632
|
cap,
|
|
626
633
|
...(opts.pagingTool ? { pagingTool: opts.pagingTool } : {}),
|
|
634
|
+
...(opts.maxProgramResultChars !== undefined ? { programChars: opts.maxProgramResultChars } : {}),
|
|
627
635
|
set: authMethod((id, value, ttlSeconds) => registryProgram(registry, "stashResult", id, value, ttlSeconds)),
|
|
628
636
|
// The catalog entry, never `toolName`: a record names a tool
|
|
629
637
|
// only through the grammar check (src/operator-record.ts).
|
|
@@ -670,7 +678,7 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
670
678
|
const structuredRecovery = outcome.error.nextAction !== undefined;
|
|
671
679
|
const recoveryRequired = structuredRecovery ||
|
|
672
680
|
// Sanitized `unavailable` diagnostics ride the structured shape too;
|
|
673
|
-
// the plain-text path would drop them
|
|
681
|
+
// the plain-text path would drop them.
|
|
674
682
|
outcome.error.details !== undefined ||
|
|
675
683
|
// So does a conflict's `current`: where things stand is the retry.
|
|
676
684
|
outcome.error.current !== undefined ||
|
|
@@ -733,7 +741,7 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
733
741
|
content: [
|
|
734
742
|
{
|
|
735
743
|
type: "text",
|
|
736
|
-
text:
|
|
744
|
+
text: "Available skills. Fetch one by exact name with this tool.\n\n" +
|
|
737
745
|
listing.map((skill) => `- \`${skill.name}\` — ${skill.description}`).join("\n"),
|
|
738
746
|
},
|
|
739
747
|
],
|
|
@@ -837,6 +845,7 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
837
845
|
binding.identity.subject !== identity.subject ||
|
|
838
846
|
binding.identity.principal !== identity.principal ||
|
|
839
847
|
binding.identity.endpoint !== identity.endpoint ||
|
|
848
|
+
binding.identity.pool !== identity.pool ||
|
|
840
849
|
binding.identity.origin !== identity.origin ||
|
|
841
850
|
(binding.program === undefined
|
|
842
851
|
? typeof binding.connector !== "string" || typeof binding.tool !== "string"
|
|
@@ -949,7 +958,7 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
949
958
|
// client that computes its own offsets can land inside a multi-byte
|
|
950
959
|
// character, which would decode as U+FFFD; the offset actually served is
|
|
951
960
|
// the boundary at or before it, and it is what the response reports back
|
|
952
|
-
// as `offset
|
|
961
|
+
// as `offset`.
|
|
953
962
|
const offset = start + alignStartToCharBoundary(bytes, requestedOffset - start);
|
|
954
963
|
// Both sides of `maxBytes` are validated by now — the argument above, the
|
|
955
964
|
// cap at intake — so `offset + maxBytes` always reaches past `offset`.
|
|
@@ -991,11 +1000,15 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
991
1000
|
})),
|
|
992
1001
|
};
|
|
993
1002
|
}
|
|
994
|
-
|
|
995
|
-
|
|
1003
|
+
// Each description states only its own tool's scope; the full routing rule
|
|
1004
|
+
// lives once in the server instructions. A client that drops instructions
|
|
1005
|
+
// still routes correctly from these scopes plus execute_code's terse lead.
|
|
1006
|
+
// Only the meta surface lists these tools, so they use its names.
|
|
1007
|
+
const SEARCH_DESC = (readOnly) => `${readOnly ? "Catalog inspection; this read-only endpoint omits write tools." : `Catalog inspection, or finding a write before ${META_ROUTES.write}.`} For read work, search inside one ${META_ROUTES.program} program that also calls and returns the answer. Use 2–4 action/object terms and includeSchemas="compact"; the default limit is ${DEFAULT_SEARCH_LIMIT}. Set connector when known. safety="readOnly" selects reads; "approvalRequired" selects the fail-closed write complement; neither grants authority. Empty query browses. Read catalogErrors and absence before selecting a tool. Compact schemas have schemaFormat="text".`;
|
|
1008
|
+
const CALL_DESC = (readOnly) => `Call one known-address read: a tool explicitly annotated readOnlyHint: true. Other read work: ${META_ROUTES.program}. ${readOnly ? "This endpoint is read-only and refuses every write." : `Unannotated or write-capable tools fail closed to ${META_ROUTES.write}.`} A truncated result carries a connecta.result action.`;
|
|
996
1009
|
const CALL_DESTRUCTIVE_DESC = "Call any tool not explicitly annotated readOnlyHint: true. Include a short reason for the human reviewer after checking the schema and consequences. The reason grants no authority and is not sent downstream.";
|
|
997
1010
|
/** A read-only endpoint keeps the six-tool surface, so this tool says why it never writes here. */
|
|
998
|
-
const CALL_DESTRUCTIVE_READ_ONLY_DESC =
|
|
1011
|
+
const CALL_DESTRUCTIVE_READ_ONLY_DESC = `This endpoint is read-only: search and describe omit write tools, and every write call here is refused. Reads still run; prefer ${META_ROUTES.read} or ${META_ROUTES.program} for them.`;
|
|
999
1012
|
const AUTHORIZE_DESC = "Use after auth_required. Returns an OAuth or operator-credential handoff, or required deployment configuration. A configured credential reports presence only: if a call just failed with an authentication or operator-configuration error, give the retained handoff to the operator; otherwise proceed with the call. force=true requests an OAuth restart when the verified user opens the /connect URL; this tool never accepts credentials.";
|
|
1000
1013
|
const SKILLS_DESC = "List or fetch on-demand guidance. Fetch usage only when the always-loaded instructions are insufficient or a program needs repair.";
|
|
1001
1014
|
/**
|
|
@@ -1084,6 +1097,8 @@ const AUTHORIZE_INPUT = advertisedSchema(z.object({
|
|
|
1084
1097
|
*/
|
|
1085
1098
|
export function registerMetaTools(server, registry, ctx) {
|
|
1086
1099
|
const mt = createMetaTools(registry, ctx.baseUrl, {
|
|
1100
|
+
maxProgramResultChars: ctx.maxProgramResultChars,
|
|
1101
|
+
routes: ctx.routes,
|
|
1087
1102
|
...(ctx.native && !ctx.native.executeCode ? { pagingTool: "read_result" } : {}),
|
|
1088
1103
|
...(ctx.native ? { activitySource: "native" } : {}),
|
|
1089
1104
|
downstreamInput: Boolean(ctx.authElicitation),
|
package/dist/native-recovery.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Adapt only Connecta error envelopes; successful downstream payloads stay untouched. */
|
|
2
|
-
export function nativeRecovery(result,
|
|
2
|
+
export function nativeRecovery(result, wireName) {
|
|
3
3
|
const response = result;
|
|
4
4
|
const body = response?.structuredContent;
|
|
5
5
|
if (!response?.isError || !body || typeof body.error !== "object" || !body.error)
|
|
@@ -28,22 +28,10 @@ export function nativeRecovery(result, executeCode, wireName) {
|
|
|
28
28
|
}
|
|
29
29
|
: { host: "search", purpose: action.purpose };
|
|
30
30
|
}
|
|
31
|
-
//
|
|
32
|
-
|
|
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]);
|
|
31
|
+
// Recovery text already uses the native wording: native pools build it with
|
|
32
|
+
// native route names. Only the address in retry text becomes the wire name.
|
|
42
33
|
if (wireName && typeof error.operation === "string" && typeof error.retry === "string")
|
|
43
|
-
error.retry = error.retry.replaceAll(
|
|
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
|
-
}
|
|
34
|
+
error.retry = error.retry.replaceAll(error.operation, wireName);
|
|
47
35
|
return {
|
|
48
36
|
...response,
|
|
49
37
|
structuredContent: { ...body, error },
|