@zackbart/connecta 0.33.0 → 0.34.1
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 +65 -0
- package/README.md +100 -128
- package/SECURITY.md +1 -1
- package/bin/version.mjs +1 -1
- package/dist/activity.d.ts +3 -0
- package/dist/branding.d.ts +7 -0
- package/dist/branding.js +76 -9
- package/dist/catalog-drift.d.ts +30 -26
- package/dist/catalog-drift.js +169 -84
- package/dist/catalog-service.js +16 -2
- package/dist/config-value-sources.js +6 -1
- package/dist/config.d.ts +13 -2
- package/dist/config.js +36 -2
- package/dist/connector-caller.js +2 -1
- package/dist/connectors/api-connector.d.ts +3 -0
- package/dist/connectors/api-connector.js +5 -1
- package/dist/connectors/option-shapes.js +1 -1
- package/dist/connectors/remote-mcp.d.ts +2 -0
- package/dist/connectors/remote-mcp.js +5 -1
- package/dist/describe-config.d.ts +5 -1
- package/dist/describe-config.js +2 -0
- package/dist/execute.js +1 -11
- package/dist/fix-prompt.js +2 -2
- package/dist/index.js +1 -0
- package/dist/invocation.js +3 -2
- package/dist/membership.d.ts +2 -0
- package/dist/meta-tools.js +3 -2
- package/dist/oauth-callback-outcome.js +2 -2
- package/dist/operator-ui/agents.d.ts +1 -1
- package/dist/operator-ui/agents.js +16 -16
- package/dist/operator-ui/config-summary.js +565 -0
- package/dist/operator-ui/connection-map.js +376 -0
- package/dist/operator-ui/endpoints.js +45 -16
- package/dist/operator-ui/fix-prompts.js +1 -1
- package/dist/operator-ui/generated.js +4 -4
- package/dist/operator-ui/health.js +52 -0
- package/dist/operator-ui/member-page.js +13 -28
- package/dist/operator-ui/overview.js +123 -0
- package/dist/operator-ui/view.d.ts +26 -22
- package/dist/operator-ui/view.js +130 -96
- package/dist/page-styles.js +5 -3
- package/dist/provider.d.ts +6 -1
- package/dist/provider.js +18 -1
- package/dist/providers/_shared/google/workspace.d.ts +9 -4
- package/dist/providers/_shared/google/workspace.js +7 -3
- package/dist/providers/basecamp/index.d.ts +2 -0
- package/dist/providers/breeze/index.d.ts +2 -0
- package/dist/providers/ccb/index.d.ts +2 -0
- package/dist/providers/ccb/index.js +2 -2
- package/dist/providers/cloudflare/index.d.ts +2 -0
- package/dist/providers/cloudflare/index.js +5 -1
- package/dist/providers/cloudflare/openapi.generated.js +3296 -3206
- package/dist/providers/cloudflare/rest.js +19 -0
- package/dist/providers/cloudflare/skill.generated.js +2 -2
- package/dist/providers/cloudflare/value-safety.js +51 -18
- package/dist/providers/docs/index.js +1 -1
- package/dist/providers/docs/skill.generated.js +2 -2
- package/dist/providers/drive/index.js +1 -1
- package/dist/providers/drive/skill.generated.js +2 -2
- package/dist/providers/forms/index.js +1 -1
- package/dist/providers/forms/skill.generated.js +2 -2
- package/dist/providers/gmail/index.js +1 -1
- package/dist/providers/gmail/skill.generated.js +2 -2
- package/dist/providers/intercom/index.d.ts +24 -0
- package/dist/providers/intercom/index.js +121 -0
- package/dist/providers/intercom/skill.generated.js +8 -0
- package/dist/providers/linear/index.js +204 -19
- package/dist/providers/linear/skill.generated.js +2 -2
- package/dist/providers/mixpanel/index.d.ts +2 -0
- package/dist/providers/mixpanel/index.js +21 -17
- package/dist/providers/mixpanel/skill.generated.js +3 -3
- package/dist/providers/notion/index.d.ts +2 -0
- package/dist/providers/notion/index.js +1 -1
- package/dist/providers/notion/skill.generated.js +1 -1
- package/dist/providers/overflow/index.d.ts +2 -0
- package/dist/providers/overflow/index.js +1 -1
- package/dist/providers/overflow/skill.generated.js +2 -2
- package/dist/providers/planning-center/index.d.ts +2 -0
- package/dist/providers/planning-center/index.js +1 -1
- package/dist/providers/planning-center/skill.generated.js +4 -4
- package/dist/providers/revenuecat/index.d.ts +5 -3
- package/dist/providers/revenuecat/index.js +147 -7
- package/dist/providers/revenuecat/skill.generated.js +3 -3
- package/dist/providers/sheets/index.js +1 -1
- package/dist/providers/sheets/skill.generated.js +2 -2
- package/dist/providers/slides/index.js +1 -1
- package/dist/providers/slides/skill.generated.js +2 -2
- package/dist/providers/stripe/index.d.ts +2 -0
- package/dist/providers/stripe/index.js +50 -30
- package/dist/providers/stripe/skill.generated.js +2 -1
- package/dist/providers/tithely/index.d.ts +2 -0
- package/dist/providers/tithely/index.js +1 -1
- package/dist/providers/vercel/index.d.ts +2 -0
- package/dist/providers/vercel/index.js +2 -2
- package/dist/providers/vercel/openapi.generated.js +391 -374
- package/dist/providers/vercel/skill.generated.js +2 -2
- package/dist/providers/vercel/value-safety.js +14 -4
- package/dist/registry.d.ts +10 -14
- package/dist/registry.js +77 -62
- package/dist/routes/mcp.js +20 -13
- package/dist/routes/oauth.js +2 -2
- package/dist/routes/ui-data.js +8 -2
- package/dist/skills.js +18 -2
- package/dist/storage/sql.js +5 -1
- package/dist/types.d.ts +36 -11
- package/dist/ui.js +9 -4
- package/dist/usage-guide.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/README.md +2 -2
- package/documentation/architecture.md +66 -18
- package/documentation/auth.md +183 -67
- package/documentation/code-mode.md +30 -10
- package/documentation/deploying.md +5 -1
- package/documentation/integrating.md +64 -7
- package/documentation/meta-tools.md +38 -24
- package/documentation/migration-0.33.md +21 -13
- package/documentation/migration-0.34.md +67 -0
- package/documentation/operating.md +1 -1
- package/documentation/operator-tests.md +52 -20
- package/documentation/operator-ui.md +341 -104
- package/documentation/provider-auth-migration.md +22 -0
- package/examples/worker/AGENTS.md +15 -9
- package/examples/worker/README.md +41 -14
- package/examples/worker/src/connecta.config.ts +5 -4
- package/examples/worker/wrangler.jsonc +1 -1
- package/package.json +23 -19
- package/templates/node/AGENTS.md +13 -7
- package/templates/node/README.md +14 -11
- package/templates/node/package.json +1 -1
- package/templates/node/src/connecta.config.ts +2 -2
package/dist/config.js
CHANGED
|
@@ -184,6 +184,31 @@ function assertFlag(value, path) {
|
|
|
184
184
|
if (typeof value !== "boolean")
|
|
185
185
|
throw new ConfigError(`${path} must be true or false.`);
|
|
186
186
|
}
|
|
187
|
+
/** Longest pool `description`, in characters, after trimming. */
|
|
188
|
+
const MAX_POOL_DESCRIPTION_LENGTH = 500;
|
|
189
|
+
/**
|
|
190
|
+
* A pool description is one line of prose for the model. C0 and C1 controls
|
|
191
|
+
* (including NEL), plus Unicode line and paragraph separators, are refused.
|
|
192
|
+
* A scan, as in remote-mcp.ts,
|
|
193
|
+
* because a character class over the control range trips `no-control-regex`.
|
|
194
|
+
*/
|
|
195
|
+
function hasLineBreakOrControlCharacter(value) {
|
|
196
|
+
for (let index = 0; index < value.length; index++) {
|
|
197
|
+
const code = value.charCodeAt(index);
|
|
198
|
+
if (code <= 0x1f || (code >= 0x7f && code <= 0x9f) || code === 0x2028 || code === 0x2029)
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
function assertPoolDescription(value, path) {
|
|
204
|
+
const trimmed = typeof value === "string" ? value.trim() : undefined;
|
|
205
|
+
if (trimmed === undefined ||
|
|
206
|
+
trimmed.length === 0 ||
|
|
207
|
+
[...trimmed].length > MAX_POOL_DESCRIPTION_LENGTH ||
|
|
208
|
+
hasLineBreakOrControlCharacter(trimmed)) {
|
|
209
|
+
throw new ConfigError(`${path} must be text of 1 to ${MAX_POOL_DESCRIPTION_LENGTH} characters after trimming, with no line breaks or control characters.`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
187
212
|
/** Shape only; each reference resolves against auth and agents in createConnecta. */
|
|
188
213
|
function assertMemberList(value, path) {
|
|
189
214
|
if (!Array.isArray(value)) {
|
|
@@ -217,8 +242,9 @@ const pool = {
|
|
|
217
242
|
* The Google Workspace address agents in this pool act as, on a Workspace
|
|
218
243
|
* connector whose `subject` is a mapping function. Humans always act as
|
|
219
244
|
* themselves through that mapping, and a fixed `subject` address applies to
|
|
220
|
-
* everyone. Without an owner,
|
|
221
|
-
*
|
|
245
|
+
* everyone. Without an owner, a mapping-subject Workspace connector is
|
|
246
|
+
* absent for this pool's agents, as a personal connector is; people in the
|
|
247
|
+
* pool still see it.
|
|
222
248
|
*/
|
|
223
249
|
owner: opaque({
|
|
224
250
|
check: (value, path) => {
|
|
@@ -227,6 +253,14 @@ const pool = {
|
|
|
227
253
|
}
|
|
228
254
|
},
|
|
229
255
|
}),
|
|
256
|
+
/**
|
|
257
|
+
* What this pool is for, in plain words for the model: appended to the MCP
|
|
258
|
+
* `initialize` instructions at `/mcp/<name>` and `/agent/<name>`, so a
|
|
259
|
+
* caller learns its scope ("Team tier: shared tools only."). Trimmed, 1 to
|
|
260
|
+
* 500 characters, no line breaks or control characters. It grants and hides nothing; plain
|
|
261
|
+
* `/mcp` never shows it.
|
|
262
|
+
*/
|
|
263
|
+
description: opaque({ check: assertPoolDescription }),
|
|
230
264
|
};
|
|
231
265
|
const icon = {
|
|
232
266
|
src: required(text()),
|
package/dist/connector-caller.js
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
// Workspace through domain-wide delegation (`src/providers/_shared/google/`) — which
|
|
10
10
|
// hand the identity to a deployment-config function that names the
|
|
11
11
|
// downstream subject (PRINCIPLES.md INV-3, #678), or, for an agent, use the
|
|
12
|
-
// admitted pool's configured owner (decisions/0006)
|
|
12
|
+
// admitted pool's configured owner (decisions/0006), and the scoped registry
|
|
13
|
+
// view hides the connector from an agent whose pool has none. The provider never
|
|
13
14
|
// decides whose account to open; config does, from this.
|
|
14
15
|
//
|
|
15
16
|
// Core sets it from the authorization that admitted the request, on the scoped
|
|
@@ -112,6 +112,8 @@ export interface ApiOptions {
|
|
|
112
112
|
* construct.
|
|
113
113
|
*/
|
|
114
114
|
maxResultBytes?: number;
|
|
115
|
+
/** Serve only read-classified tools on every endpoint. Defaults to false. */
|
|
116
|
+
readOnly?: boolean;
|
|
115
117
|
/** Optional per-runtime downstream call-admission policy. */
|
|
116
118
|
callAdmission?: ConnectorCallAdmissionPolicy;
|
|
117
119
|
/**
|
|
@@ -146,6 +148,7 @@ export interface ApiOptions {
|
|
|
146
148
|
/** The closed options api() accepts; see `assertKnownOptions`. */
|
|
147
149
|
export declare const API_OPTIONS: import("../config-schema.js").Field<unknown, unknown, true> & {
|
|
148
150
|
readonly shape: {
|
|
151
|
+
readOnly: import("../config-schema.js").Field<boolean, boolean | undefined, true>;
|
|
149
152
|
callAdmission: Omit<import("../config-schema.js").Field<unknown, ConnectorCallAdmissionPolicy | undefined, true>, "parse"> & {
|
|
150
153
|
parse(value: unknown, path: string): ConnectorCallAdmissionPolicy;
|
|
151
154
|
readonly shape: import("../config-schema.js").Shape & {
|
|
@@ -3,7 +3,7 @@ import { attachFailureFacts, carryFailureFacts, errorLabel } from "../operator-r
|
|
|
3
3
|
import { compileValidator, validateCatalogToolInput } from "../validate.js";
|
|
4
4
|
import { array, assertKnownOptions, instance, keys, optionsOf, strings } from "../config-schema.js";
|
|
5
5
|
import { describedEndpoint, describedOrigin, describedTools } from "../described.js";
|
|
6
|
-
import { CALL_ADMISSION, CREDENTIAL, USAGE_GUIDE } from "./option-shapes.js";
|
|
6
|
+
import { READ_ONLY, CALL_ADMISSION, CREDENTIAL, USAGE_GUIDE } from "./option-shapes.js";
|
|
7
7
|
import { assertStaticToolNames } from "../tool-name.js";
|
|
8
8
|
import { registerInvocationAuth } from "../invocation-auth.js";
|
|
9
9
|
import { redactCatalog, redactSentSecrets, sentSecretsFetch, trackCredentialReads } from "../sent-secrets.js";
|
|
@@ -41,6 +41,7 @@ function checkToolContract(id, tool) {
|
|
|
41
41
|
/** The closed options api() accepts; see `assertKnownOptions`. */
|
|
42
42
|
export const API_OPTIONS = optionsOf()({
|
|
43
43
|
...keys("title", "description", "authScope", "maxResultBytes", "testCredential", "testCredentials", "validateArgs"),
|
|
44
|
+
readOnly: READ_ONLY,
|
|
44
45
|
callAdmission: CALL_ADMISSION,
|
|
45
46
|
usageGuide: USAGE_GUIDE,
|
|
46
47
|
credential: CREDENTIAL,
|
|
@@ -90,6 +91,8 @@ function describedApiAuth(opts) {
|
|
|
90
91
|
*/
|
|
91
92
|
export function apiConnector(id, opts, oauth) {
|
|
92
93
|
opts = assertKnownOptions(opts, `api(${JSON.stringify(id)})`, API_OPTIONS);
|
|
94
|
+
if (opts.readOnly !== undefined)
|
|
95
|
+
READ_ONLY.parse(opts.readOnly, `Connector(${JSON.stringify(id)}).readOnly`);
|
|
93
96
|
if (opts.callAdmission !== undefined)
|
|
94
97
|
CALL_ADMISSION.parse(opts.callAdmission, `api(${JSON.stringify(id)}).callAdmission`);
|
|
95
98
|
assertStaticToolNames(opts.tools, `api(${JSON.stringify(id)}).tools`);
|
|
@@ -124,6 +127,7 @@ export function apiConnector(id, opts, oauth) {
|
|
|
124
127
|
...defined({
|
|
125
128
|
description: opts.description,
|
|
126
129
|
authScope: opts.authScope,
|
|
130
|
+
readOnly: opts.readOnly,
|
|
127
131
|
maxResultBytes: opts.maxResultBytes,
|
|
128
132
|
callAdmission: opts.callAdmission,
|
|
129
133
|
usageGuide: opts.usageGuide,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// path instead of silently doing nothing (INV-11). Values stay the factory's
|
|
6
6
|
// to validate; handlers, schemas, and loggers pass through as `opaque()`.
|
|
7
7
|
import { array, keys, optionsOf, strings, variants } from "../config-schema.js";
|
|
8
|
-
export { CALL_ADMISSION, PROVIDER_COMMON } from "../provider.js";
|
|
8
|
+
export { READ_ONLY, CALL_ADMISSION, PROVIDER_COMMON } from "../provider.js";
|
|
9
9
|
export const CREDENTIAL = optionsOf()({
|
|
10
10
|
...keys("label", "description", "placeholder"),
|
|
11
11
|
fields: array(optionsOf()(keys("name", "label", "description", "placeholder", "inputType"))),
|
|
@@ -82,6 +82,8 @@ export interface RemoteMcpOptions {
|
|
|
82
82
|
* construct.
|
|
83
83
|
*/
|
|
84
84
|
maxResultBytes?: number;
|
|
85
|
+
/** Serve only read-classified tools on every endpoint. Defaults to false. */
|
|
86
|
+
readOnly?: boolean;
|
|
85
87
|
/** Optional per-runtime downstream call-admission policy. */
|
|
86
88
|
callAdmission?: ConnectorCallAdmissionPolicy;
|
|
87
89
|
/**
|
|
@@ -24,7 +24,7 @@ import { retryAfterMs } from "./guarded-fetch.js";
|
|
|
24
24
|
import { resourceUriMatchesTemplates } from "./resource-uri.js";
|
|
25
25
|
import { callerOf } from "../connector-caller.js";
|
|
26
26
|
import { readNegotiation, storeNegotiation } from "./negotiation-cache.js";
|
|
27
|
-
import { CALL_ADMISSION, REMOTE_MCP_AUTH, USAGE_GUIDE } from "./option-shapes.js";
|
|
27
|
+
import { READ_ONLY, CALL_ADMISSION, REMOTE_MCP_AUTH, USAGE_GUIDE } from "./option-shapes.js";
|
|
28
28
|
/**
|
|
29
29
|
* Apply a maintained provider's slot copy and header framing to credential
|
|
30
30
|
* auth the deployment left bare.
|
|
@@ -46,6 +46,7 @@ export function withCredentialDefaults(auth, defaults) {
|
|
|
46
46
|
/** The closed options remoteMcp() accepts; see `assertKnownOptions`. */
|
|
47
47
|
const REMOTE_MCP_OPTIONS = optionsOf()({
|
|
48
48
|
...keys("url", "title", "description", "authScope", "maxResultBytes", "versionNegotiation", "redirects", "requireHttps", "logger", "_transportFactory", "classify", "skills"),
|
|
49
|
+
readOnly: READ_ONLY,
|
|
49
50
|
callAdmission: CALL_ADMISSION,
|
|
50
51
|
usageGuide: USAGE_GUIDE,
|
|
51
52
|
auth: REMOTE_MCP_AUTH,
|
|
@@ -1024,6 +1025,8 @@ function registrationErrorCode(body) {
|
|
|
1024
1025
|
*/
|
|
1025
1026
|
export function remoteMcp(id, opts) {
|
|
1026
1027
|
opts = assertKnownOptions(opts, `remoteMcp(${JSON.stringify(id)})`, REMOTE_MCP_OPTIONS);
|
|
1028
|
+
if (opts.readOnly !== undefined)
|
|
1029
|
+
READ_ONLY.parse(opts.readOnly, `Connector(${JSON.stringify(id)}).readOnly`);
|
|
1027
1030
|
if (opts.callAdmission !== undefined)
|
|
1028
1031
|
CALL_ADMISSION.parse(opts.callAdmission, `remoteMcp(${JSON.stringify(id)}).callAdmission`);
|
|
1029
1032
|
const skillsEnabled = opts.skills;
|
|
@@ -2194,6 +2197,7 @@ export function remoteMcp(id, opts) {
|
|
|
2194
2197
|
},
|
|
2195
2198
|
}),
|
|
2196
2199
|
...(opts.description !== undefined ? { description: opts.description } : {}),
|
|
2200
|
+
...(opts.readOnly !== undefined ? { readOnly: opts.readOnly } : {}),
|
|
2197
2201
|
...(opts.authScope !== undefined ? { authScope: opts.authScope } : {}),
|
|
2198
2202
|
...(opts.maxResultBytes !== undefined ? { maxResultBytes: opts.maxResultBytes } : {}),
|
|
2199
2203
|
...(opts.callAdmission !== undefined ? { callAdmission: opts.callAdmission } : {}),
|
|
@@ -20,6 +20,8 @@ export type DescribedMember = {
|
|
|
20
20
|
};
|
|
21
21
|
/** One connector as `describeConfig()` reports it. */
|
|
22
22
|
export interface DescribedConnector {
|
|
23
|
+
/** Connector policy, independent of endpoint policy; omitted when false. */
|
|
24
|
+
readOnly?: boolean;
|
|
23
25
|
id: string;
|
|
24
26
|
title?: string;
|
|
25
27
|
description?: string;
|
|
@@ -115,7 +117,8 @@ export interface ConnectaConfigDescription {
|
|
|
115
117
|
/**
|
|
116
118
|
* `readOnly` is resolved: the pool's own value, else the deployment's.
|
|
117
119
|
* `members` are resolved references in declaration order. `owner` is the
|
|
118
|
-
* Workspace address agents act as, or null.
|
|
120
|
+
* Workspace address agents act as, or null. `description` is the trimmed
|
|
121
|
+
* text the pool's endpoints add to their MCP instructions, or null.
|
|
119
122
|
*/
|
|
120
123
|
pools: Array<{
|
|
121
124
|
name: string;
|
|
@@ -124,6 +127,7 @@ export interface ConnectaConfigDescription {
|
|
|
124
127
|
members: DescribedMember[];
|
|
125
128
|
readOnly: boolean;
|
|
126
129
|
owner: string | null;
|
|
130
|
+
description: string | null;
|
|
127
131
|
}>;
|
|
128
132
|
modules: {
|
|
129
133
|
ui: {
|
package/dist/describe-config.js
CHANGED
|
@@ -133,6 +133,7 @@ function describeConnector(connector, deploymentCap, registry) {
|
|
|
133
133
|
const guideSummary = connectorGuideSummary(connector);
|
|
134
134
|
const guided = connector.usageGuide !== undefined;
|
|
135
135
|
return {
|
|
136
|
+
...(connector.readOnly === true ? { readOnly: true } : {}),
|
|
136
137
|
id: connector.id,
|
|
137
138
|
...(str(connector.title) !== undefined ? { title: connector.title } : {}),
|
|
138
139
|
...(str(connector.description) !== undefined ? { description: connector.description } : {}),
|
|
@@ -250,6 +251,7 @@ export function describeConfig(input) {
|
|
|
250
251
|
? { kind: "agent", name: member.name }
|
|
251
252
|
: { kind: "human", namespace: member.namespace, id: member.id }),
|
|
252
253
|
owner: input.pools.get(name)?.owner ?? null,
|
|
254
|
+
description: input.pools.get(name)?.description ?? null,
|
|
253
255
|
})),
|
|
254
256
|
modules: {
|
|
255
257
|
ui: { enabled: config.ui !== undefined },
|
package/dist/execute.js
CHANGED
|
@@ -798,18 +798,8 @@ function sandboxProvider(registry, baseUrl, activity, limits, requestScope = {})
|
|
|
798
798
|
...args,
|
|
799
799
|
includeSchemas: args.includeSchemas ?? "json",
|
|
800
800
|
includeSchemaKeys: args.includeSchemaKeys !== false,
|
|
801
|
-
}));
|
|
801
|
+
}, catalogDependency));
|
|
802
802
|
boundedDiscoveryText(result, "Request a smaller limit, omit fullDescriptions, use compact schemas, or pass includeSchemaKeys: false.");
|
|
803
|
-
for (const tool of result.tools)
|
|
804
|
-
catalogAddressDependency(tool.address);
|
|
805
|
-
for (const error of result.catalogErrors)
|
|
806
|
-
catalogDependency(error.connector);
|
|
807
|
-
for (const connector of result.absence?.configuredConnectors ?? [])
|
|
808
|
-
catalogDependency(connector);
|
|
809
|
-
for (const connector of result.queryAnalysis?.configuredConnectors ?? [])
|
|
810
|
-
catalogDependency(connector);
|
|
811
|
-
if (result.queryAnalysis?.connectorScope)
|
|
812
|
-
catalogDependency(result.queryAnalysis.connectorScope);
|
|
813
803
|
return result;
|
|
814
804
|
}),
|
|
815
805
|
describe: (raw) => discovery("describe", async () => {
|
package/dist/fix-prompt.js
CHANGED
|
@@ -29,7 +29,7 @@ export function renderFixPrompt(spec, connectorId) {
|
|
|
29
29
|
"own logs (lines prefixed [connecta]) or by reproducing the failure locally. Never put a secret in source, " +
|
|
30
30
|
"a log line, or your reply: secrets belong in the deployment's environment or its credential vault.",
|
|
31
31
|
"Make the smallest change that fixes it, then verify it by redeploying and refreshing the connection on the " +
|
|
32
|
-
"operator
|
|
33
|
-
"a secret only the operator holds
|
|
32
|
+
"operator Connectors page. If the fix needs a step you cannot do yourself, such as a provider console setting " +
|
|
33
|
+
"or a secret only the operator holds, name that exact step instead.",
|
|
34
34
|
].join("\n\n");
|
|
35
35
|
}
|
package/dist/index.js
CHANGED
|
@@ -67,6 +67,7 @@ function resolvePools(pools, readOnly, registry, members) {
|
|
|
67
67
|
readOnly: pool.readOnly ?? readOnly,
|
|
68
68
|
members: Object.freeze(resolveMembers(pool.members, path, members)),
|
|
69
69
|
...(pool.owner !== undefined ? { owner: pool.owner.trim() } : {}),
|
|
70
|
+
...(pool.description !== undefined ? { description: pool.description.trim() } : {}),
|
|
70
71
|
}));
|
|
71
72
|
}
|
|
72
73
|
return resolved;
|
package/dist/invocation.js
CHANGED
|
@@ -464,9 +464,10 @@ export class InvocationService {
|
|
|
464
464
|
reconciliationRequired: true,
|
|
465
465
|
};
|
|
466
466
|
}
|
|
467
|
-
const
|
|
467
|
+
const connectorReadOnly = target.connector.readOnly === true;
|
|
468
|
+
const refusal = writeRefusal(target.definition.classification, context.source, context.readOnly || connectorReadOnly);
|
|
468
469
|
if (refusal === "read_only_endpoint") {
|
|
469
|
-
return framingError("read_only_endpoint", `Tool "${canonicalAddress}" is a write, and this endpoint is read-only. Nothing here can send it.`);
|
|
470
|
+
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.`);
|
|
470
471
|
}
|
|
471
472
|
if (refusal === "destructive_tool_requires_approval") {
|
|
472
473
|
return framingError("destructive_tool_requires_approval", `Tool "${canonicalAddress}" is a write. Invoke it through call_destructive_tool so the MCP host can request approval.`);
|
package/dist/membership.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export interface ResolvedPool {
|
|
|
25
25
|
members: readonly Member[];
|
|
26
26
|
/** Trimmed `owner`: the Workspace address agents in this pool act as. */
|
|
27
27
|
owner?: string;
|
|
28
|
+
/** Trimmed `description`, appended to this pool's MCP instructions. */
|
|
29
|
+
description?: string;
|
|
28
30
|
}
|
|
29
31
|
/** What resolving a reference needs to know about the deployment. */
|
|
30
32
|
export interface MemberContext {
|
package/dist/meta-tools.js
CHANGED
|
@@ -796,14 +796,15 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
796
796
|
return false;
|
|
797
797
|
continue;
|
|
798
798
|
}
|
|
799
|
-
|
|
799
|
+
const connector = registry.getConnector(dependency.connector);
|
|
800
|
+
if (!connector)
|
|
800
801
|
return false;
|
|
801
802
|
const tools = await registry.getTools(dependency.connector, baseUrl, requestScope, options.signal ? { signal: options.signal } : {});
|
|
802
803
|
const tool = tools.find((tool) => tool.name === dependency.tool);
|
|
803
804
|
if (!tool ||
|
|
804
805
|
!(dependency.access === "catalog" ||
|
|
805
806
|
(dependency.classification === "read" && tool.classification === "read") ||
|
|
806
|
-
opts.readOnly !== true))
|
|
807
|
+
(opts.readOnly !== true && connector.readOnly !== true)))
|
|
807
808
|
return false;
|
|
808
809
|
}
|
|
809
810
|
return true;
|
|
@@ -11,7 +11,7 @@ const OUTCOMES = {
|
|
|
11
11
|
status: 400,
|
|
12
12
|
tone: "declined",
|
|
13
13
|
heading: "Authorization was declined",
|
|
14
|
-
message: "Consent was declined
|
|
14
|
+
message: "Consent was declined, so nothing was stored. Start again when ready.",
|
|
15
15
|
fix: {
|
|
16
16
|
problem: "Consent for a downstream OAuth connector was declined at the provider, so no grant was stored.",
|
|
17
17
|
steps: [
|
|
@@ -72,7 +72,7 @@ const OUTCOMES = {
|
|
|
72
72
|
fix: {
|
|
73
73
|
problem: "A provider returned an authorization code, but exchanging it for tokens failed.",
|
|
74
74
|
steps: [
|
|
75
|
-
"Check the OAuth client
|
|
75
|
+
"Check the OAuth client id and secret the connector reads from the deployment's environment, and that they belong to the OAuth app that showed the consent screen.",
|
|
76
76
|
"Check that the token endpoint and redirect URI the connector uses match the provider's registration exactly.",
|
|
77
77
|
"Find the [connecta] warning for this callback in the deployment logs; it records the exchange error the page withholds.",
|
|
78
78
|
],
|
|
@@ -62,7 +62,7 @@ export declare function slotBadge(slot: Pick<AgentSlot, "state">): {
|
|
|
62
62
|
label: string;
|
|
63
63
|
tone: Tone;
|
|
64
64
|
};
|
|
65
|
-
/** When the slot last changed,
|
|
65
|
+
/** When the slot last changed, issued or revoked (its badge says which); a dash when it never has. */
|
|
66
66
|
export declare function slotSince(slot: AgentSlot, format: (value: string) => string): string;
|
|
67
67
|
export declare function lastSeenLabel(lastSeenAt: string | null, format: (value: string) => string): string;
|
|
68
68
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { failure } from "./view.js";
|
|
2
2
|
/** Why a row's controls are disabled when its slot is not in the live list. */
|
|
3
|
-
const SLOT_UNAVAILABLE = "Token state
|
|
3
|
+
const SLOT_UNAVAILABLE = "Token state unknown. Refresh.";
|
|
4
4
|
/** Why a row's controls are disabled until a fresh list read resolves its slot. */
|
|
5
5
|
const SLOT_UNRESOLVED = "Refresh to continue";
|
|
6
6
|
export function slotActions(slot) {
|
|
@@ -53,12 +53,12 @@ export function slotBadge(slot) {
|
|
|
53
53
|
return { label: "Revoked", tone: "warn" };
|
|
54
54
|
return { label: "No token", tone: "neutral" };
|
|
55
55
|
}
|
|
56
|
-
/** When the slot last changed,
|
|
56
|
+
/** When the slot last changed, issued or revoked (its badge says which); a dash when it never has. */
|
|
57
57
|
export function slotSince(slot, format) {
|
|
58
58
|
if (slot.state === "issued")
|
|
59
|
-
return
|
|
59
|
+
return format(slot.issuedAt);
|
|
60
60
|
if (slot.state === "revoked")
|
|
61
|
-
return
|
|
61
|
+
return format(slot.revokedAt);
|
|
62
62
|
return "—";
|
|
63
63
|
}
|
|
64
64
|
export function lastSeenLabel(lastSeenAt, format) {
|
|
@@ -72,33 +72,33 @@ export function lastSeenLabel(lastSeenAt, format) {
|
|
|
72
72
|
*/
|
|
73
73
|
export function tokenFailureNotice(action, name, facts, productName) {
|
|
74
74
|
if (facts.kind === "session")
|
|
75
|
-
return failure("
|
|
75
|
+
return failure("Session ended. Sign in again.");
|
|
76
76
|
if (facts.kind === "forbidden")
|
|
77
|
-
return failure("You
|
|
77
|
+
return failure("You can't manage agent tokens.");
|
|
78
78
|
if (facts.kind === "stale") {
|
|
79
|
-
return failure(`${name}'s token changed
|
|
79
|
+
return failure(`${name}'s token changed, so nothing was sent. Try again.`);
|
|
80
80
|
}
|
|
81
81
|
if (facts.kind === "network") {
|
|
82
|
-
return failure(`Couldn't reach ${productName}. Refresh to check ${name}'s token
|
|
82
|
+
return failure(`Couldn't reach ${productName}. Refresh to check ${name}'s token.`);
|
|
83
83
|
}
|
|
84
84
|
if (facts.status === 503) {
|
|
85
85
|
return failure(action === "revoke"
|
|
86
|
-
? `
|
|
87
|
-
: `${action === "issue" ? "Issue" : "
|
|
86
|
+
? `Revoke result unknown for ${name}. Revoke again to be sure.`
|
|
87
|
+
: `${action === "issue" ? "Issue" : "Rotate"} result unknown for ${name}. No secret came back, so rotate again.`);
|
|
88
88
|
}
|
|
89
89
|
if (facts.status === 499) {
|
|
90
90
|
return failure(action === "revoke"
|
|
91
|
-
? `The request ended before ${name}'s
|
|
92
|
-
: `The request ended before ${name}'s token
|
|
91
|
+
? `The request ended before ${name}'s revoke finished. Refresh to check its token.`
|
|
92
|
+
: `The request ended before ${name}'s token arrived. Refresh to check its token.`);
|
|
93
93
|
}
|
|
94
94
|
// The route's compare-and-swap refused it: the slot moved after the list was read.
|
|
95
95
|
if (facts.status === 409 && facts.code === "slot_changed")
|
|
96
|
-
return failure("Changed since you looked
|
|
96
|
+
return failure("Changed since you looked. Refresh.");
|
|
97
97
|
if (facts.status === 409)
|
|
98
|
-
return failure(`${name}'s token changed
|
|
98
|
+
return failure(`${name}'s token changed. Reload and try again.`);
|
|
99
99
|
if (facts.status === 404)
|
|
100
100
|
return failure(`${name} is no longer declared in code. Reload the page.`);
|
|
101
101
|
return action === "revoke"
|
|
102
|
-
? failure(
|
|
103
|
-
: failure(`
|
|
102
|
+
? failure(`Couldn't revoke ${name}'s token. Reload and try again.`)
|
|
103
|
+
: failure(`Couldn't issue a token for ${name}. Reload and try again.`);
|
|
104
104
|
}
|