@zackbart/connecta 0.31.0 → 0.32.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.
Files changed (95) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +15 -13
  3. package/bin/connecta.mjs +1 -0
  4. package/bin/version.mjs +1 -1
  5. package/dist/auth-elicitation.js +1 -1
  6. package/dist/catalog-service.js +7 -2
  7. package/dist/config-value-sources.js +6 -3
  8. package/dist/config.d.ts +26 -8
  9. package/dist/config.js +38 -16
  10. package/dist/connector-access.d.ts +2 -1
  11. package/dist/connectors/guarded-fetch.d.ts +9 -0
  12. package/dist/connectors/guarded-fetch.js +7 -0
  13. package/dist/describe-config.d.ts +6 -3
  14. package/dist/describe-config.js +3 -2
  15. package/dist/errors.d.ts +1 -1
  16. package/dist/errors.js +1 -0
  17. package/dist/execute.js +16 -14
  18. package/dist/index.js +4 -3
  19. package/dist/invocation.js +7 -4
  20. package/dist/meta-tools.js +17 -19
  21. package/dist/operator-record.d.ts +2 -2
  22. package/dist/operator-record.js +2 -0
  23. package/dist/operator-ui/contract.d.ts +5 -4
  24. package/dist/operator-ui/generated.js +2 -2
  25. package/dist/operator-ui/model.d.ts +1 -1
  26. package/dist/operator-ui/view.js +1 -0
  27. package/dist/program-writes.js +2 -2
  28. package/dist/provider.d.ts +2 -3
  29. package/dist/providers/_shared/google/workspace.js +1 -1
  30. package/dist/providers/breeze/skill.generated.js +2 -2
  31. package/dist/providers/cloudflare/skill.generated.js +1 -1
  32. package/dist/providers/docs/index.d.ts +1 -1
  33. package/dist/providers/docs/index.js +2 -2
  34. package/dist/providers/drive/index.d.ts +1 -1
  35. package/dist/providers/drive/index.js +1 -1
  36. package/dist/providers/forms/index.d.ts +1 -1
  37. package/dist/providers/forms/index.js +2 -2
  38. package/dist/providers/github/skill.generated.js +2 -2
  39. package/dist/providers/gmail/index.d.ts +1 -1
  40. package/dist/providers/gmail/index.js +274 -23
  41. package/dist/providers/gmail/skill.generated.js +2 -2
  42. package/dist/providers/linear/index.js +1 -1
  43. package/dist/providers/mixpanel/index.d.ts +27 -12
  44. package/dist/providers/mixpanel/index.js +58 -6
  45. package/dist/providers/mixpanel/operations.js +20 -0
  46. package/dist/providers/mixpanel/rest.js +603 -0
  47. package/dist/providers/mixpanel/skill.generated.js +3 -2
  48. package/dist/providers/mixpanel/value-safety.js +16 -0
  49. package/dist/providers/notion/index.js +16 -0
  50. package/dist/providers/notion/rest.js +138 -19
  51. package/dist/providers/notion/skill.generated.js +4 -4
  52. package/dist/providers/planning-center/index.d.ts +27 -0
  53. package/dist/providers/planning-center/index.js +286 -24
  54. package/dist/providers/planning-center/skill.generated.js +24 -4
  55. package/dist/providers/sheets/index.d.ts +1 -1
  56. package/dist/providers/sheets/index.js +3 -3
  57. package/dist/providers/slides/index.d.ts +1 -1
  58. package/dist/providers/slides/index.js +3 -3
  59. package/dist/providers/stripe/skill.generated.js +2 -2
  60. package/dist/providers/vercel/index.js +2 -2
  61. package/dist/providers/vercel/skill.generated.js +2 -2
  62. package/dist/providers/wispr-flow/index.d.ts +14 -0
  63. package/dist/providers/wispr-flow/index.js +117 -0
  64. package/dist/providers/wispr-flow/skill.generated.js +8 -0
  65. package/dist/routes/mcp.js +30 -10
  66. package/dist/routes/operator.js +14 -7
  67. package/dist/routes/ui-config.js +2 -2
  68. package/dist/server.js +3 -0
  69. package/dist/skills.js +5 -4
  70. package/dist/tool-safety.js +13 -5
  71. package/dist/types.d.ts +1 -1
  72. package/dist/ui.d.ts +1 -1
  73. package/dist/ui.js +1 -1
  74. package/dist/usage-guide.js +3 -3
  75. package/dist/version.d.ts +1 -1
  76. package/dist/version.js +1 -1
  77. package/documentation/architecture.md +38 -23
  78. package/documentation/auth.md +86 -27
  79. package/documentation/code-mode.md +81 -72
  80. package/documentation/deploying.md +10 -5
  81. package/documentation/integrating.md +56 -4
  82. package/documentation/meta-tools.md +44 -37
  83. package/documentation/operating.md +11 -8
  84. package/documentation/operator-tests.md +2 -3
  85. package/documentation/operator-ui.md +9 -3
  86. package/documentation/provider-auth-migration.md +5 -4
  87. package/documentation/provider-migration-0.29.md +1 -1
  88. package/examples/worker/AGENTS.md +5 -0
  89. package/examples/worker/src/connecta.config.ts +5 -0
  90. package/package.json +5 -1
  91. package/templates/node/AGENTS.md +6 -4
  92. package/templates/node/README.md +4 -2
  93. package/templates/node/package.json +1 -1
  94. package/templates/node/src/connecta.config.ts +5 -0
  95. package/dist/tool-safety.d.ts +0 -9
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ export { defineConfig } from "./config.js";
16
16
  * catalog lacks it. Remote catalogs load lazily, so their addresses are
17
17
  * checked at catalog load instead and stay unreachable until they match.
18
18
  */
19
- function resolvePools(pools, registry) {
19
+ function resolvePools(pools, readOnly, registry) {
20
20
  const resolved = new Map();
21
21
  if (!pools)
22
22
  return resolved;
@@ -52,7 +52,8 @@ function resolvePools(pools, registry) {
52
52
  }
53
53
  }
54
54
  }
55
- resolved.set(name, { access, trust: pool.trust, grant: pool.grant ?? (() => false) });
55
+ // A pool inherits the deployment value unless it sets its own, either way.
56
+ resolved.set(name, { access, readOnly: pool.readOnly ?? readOnly, grant: pool.grant ?? (() => false) });
56
57
  }
57
58
  return resolved;
58
59
  }
@@ -190,7 +191,7 @@ export function createConnecta(config) {
190
191
  results: resolved.results,
191
192
  classification: resolved.classification,
192
193
  });
193
- const pools = resolvePools(resolved.pools, registry);
194
+ const pools = resolvePools(resolved.pools, resolved.readOnly, registry);
194
195
  warnInsecureConfig(resolved);
195
196
  const requestAdmission = new AdmissionController(resolved.admission.requests);
196
197
  let codeAdmission;
@@ -1,6 +1,6 @@
1
1
  import { withAuthorizationHandoff } from "./authorization-handoff.js";
2
2
  import { hasControlCharacters } from "./tool-name.js";
3
- import { surfaceAllowsTool } from "./tool-safety.js";
3
+ import { writeRefusal } from "./tool-safety.js";
4
4
  import { Cause, Effect, Exit } from "effect";
5
5
  import { isCallAdmissionError } from "./call-admission.js";
6
6
  import { boundedEchoText, classificationCode, classifyCallError, ConnectorCallError, echoedCallArgs, framingError, } from "./errors.js";
@@ -389,7 +389,7 @@ export class InvocationService {
389
389
  // Activity rows and this log line are both payload-free by
390
390
  // construction: the line is the failure's typed record, never its
391
391
  // message, which may be the downstream's own words (INV-6).
392
- if (target && details.code !== "destructive_tool_requires_approval") {
392
+ if (target && details.code !== "destructive_tool_requires_approval" && details.code !== "read_only_endpoint") {
393
393
  logFailure(this.registry.contextFor(target.connector.id, this.catalog.baseUrl, this.catalog.requestScope).logger, "call failed", failureRecord({
394
394
  connector: target.connector.id,
395
395
  // Named only when the catalog listed it (src/operator-record.ts).
@@ -463,8 +463,11 @@ export class InvocationService {
463
463
  reconciliationRequired: true,
464
464
  };
465
465
  }
466
- if (!surfaceAllowsTool(target.definition.classification, context.source, context.trust)) {
467
- const canonicalAddress = `${target.connector.id}.${target.toolName}`;
466
+ const refusal = writeRefusal(target.definition.classification, context.source, context.readOnly);
467
+ if (refusal === "read_only_endpoint") {
468
+ return framingError("read_only_endpoint", `Tool "${canonicalAddress}" is a write, and this endpoint is read-only. Nothing here can send it.`);
469
+ }
470
+ if (refusal === "destructive_tool_requires_approval") {
468
471
  return framingError("destructive_tool_requires_approval", `Tool "${canonicalAddress}" is a write. Invoke it through call_destructive_tool so the MCP host can request approval.`);
469
472
  }
470
473
  // Remote MCP tools advertise their input schema in the catalog. Validate
@@ -210,12 +210,6 @@ async function stashResult(bytes, results, preview, rawText) {
210
210
  totalBytes,
211
211
  hint: `${results.write ? WRITE_ALREADY_RAN + " " : ""}Paging is unavailable: text contains unpaired surrogates and can't be paged as text; request the value as JSON (value mode or a JSON-returning call) to recover it.`,
212
212
  };
213
- if (!results.pageable)
214
- return {
215
- truncated: true,
216
- totalBytes,
217
- hint: `${WRITE_ALREADY_RAN} Paging is unavailable for write results on read-only pools.`,
218
- };
219
213
  const id = crypto.randomUUID();
220
214
  try {
221
215
  // Base64 permits byte-range decoding, and splitting the envelope across
@@ -270,7 +264,7 @@ async function stashResult(bytes, results, preview, rawText) {
270
264
  };
271
265
  }
272
266
  /** Program calls use the direct-call envelope, ledger, expiry and authority. */
273
- export async function pageProgramResult(registry, baseUrl, resolved, value, format, secrets, trust, requestScope, wireBytes = PROGRAM_RESULT_WIRE_BYTES) {
267
+ export async function pageProgramResult(registry, baseUrl, resolved, value, format, secrets, requestScope, wireBytes = PROGRAM_RESULT_WIRE_BYTES) {
274
268
  const data = secrets.redact(value);
275
269
  const wire = JSON.stringify({ data, format });
276
270
  if (enc.encode(wire).length <= wireBytes)
@@ -279,7 +273,6 @@ export async function pageProgramResult(registry, baseUrl, resolved, value, form
279
273
  const bytes = enc.encode(text);
280
274
  const write = resolved.definition.classification !== "read";
281
275
  const notice = await stashResult(bytes, {
282
- pageable: !write || trust === "trusted",
283
276
  binding: {
284
277
  identity: registry.resultIdentity(),
285
278
  baseUrl,
@@ -304,7 +297,7 @@ export async function pageProgramResult(registry, baseUrl, resolved, value, form
304
297
  };
305
298
  }
306
299
  /** Program returns share the direct stash, with all host-admitted call dependencies. */
307
- export async function guardProgramReturn(value, registry, baseUrl, dependencies, secrets, trust, logger, maxChars, completedWrite) {
300
+ export async function guardProgramReturn(value, registry, baseUrl, dependencies, secrets, logger, maxChars, completedWrite) {
308
301
  const serialized = serializeResultText(value);
309
302
  if (serialized.length <= maxChars)
310
303
  return value;
@@ -314,7 +307,6 @@ export async function guardProgramReturn(value, registry, baseUrl, dependencies,
314
307
  const cap = Math.min(registry.maxResultBytes, ...dependencies.map((dependency) => resolveMaxResultBytes(registry.getConnector(dependency.connector)?.maxResultBytes, registry.maxResultBytes)));
315
308
  const results = {
316
309
  secrets,
317
- pageable: !write || trust === "trusted",
318
310
  binding: {
319
311
  identity: registry.resultIdentity(),
320
312
  baseUrl,
@@ -522,6 +514,8 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
522
514
  });
523
515
  const catalog = new CatalogService(registry, baseUrl, {
524
516
  requestScope,
517
+ // search_tools never shows a read-only endpoint's writes.
518
+ readOnly: opts.readOnly,
525
519
  probeTimeoutMs,
526
520
  concurrency: discoveryConcurrency,
527
521
  defer: opts.defer,
@@ -536,6 +530,7 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
536
530
  const outcome = await invocation.invoke(call.address, call.args ?? {}, {
537
531
  source,
538
532
  sentSecrets,
533
+ readOnly: opts.readOnly,
539
534
  ...(timeoutMs !== undefined ? { timeoutMs } : {}),
540
535
  ...(opts.requestSignal !== undefined ? { requestSignal: opts.requestSignal } : {}),
541
536
  unwrapResult: call.resultMode === "value",
@@ -557,7 +552,6 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
557
552
  const cap = resolveMaxResultBytes(resolved.connector.maxResultBytes, globalCap);
558
553
  const results = {
559
554
  secrets,
560
- pageable: resolved.definition.classification === "read" || opts.trust === "trusted",
561
555
  binding: {
562
556
  identity: registry.resultIdentity(),
563
557
  baseUrl,
@@ -788,7 +782,7 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
788
782
  if (binding.program &&
789
783
  !(await registry.recheckResultAccess(undefined, binding.classification, options.signal)))
790
784
  return false;
791
- if (binding.classification === "write" && opts.trust !== "trusted")
785
+ if (binding.classification === "write" && opts.readOnly === true)
792
786
  return false;
793
787
  const dependencies = binding.program ?? [binding];
794
788
  for (const dependency of dependencies) {
@@ -809,7 +803,7 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
809
803
  if (!tool ||
810
804
  !(dependency.access === "catalog" ||
811
805
  (dependency.classification === "read" && tool.classification === "read") ||
812
- opts.trust === "trusted"))
806
+ opts.readOnly !== true))
813
807
  return false;
814
808
  }
815
809
  return true;
@@ -915,9 +909,11 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
915
909
  },
916
910
  };
917
911
  }
918
- const SEARCH_DESC = `Use top-level search for catalog inspection or approval-required work before call_destructive_tool. Unknown-address read-only work belongs in one execute_code program that searches, 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" finds tools that run unasked; "approvalRequired" finds the fail-closed complement. These filters grant no authority. Empty query browses. Returns { catalogErrors, tools, total, offset, limit, hasMore }; read catalogErrors and absence before selecting a tool. Compact schemas have schemaFormat="text".`;
919
- const CALL_DESC = "Call one known-address tool explicitly annotated readOnlyHint: true. Use execute_code for unknown-address, multiple, dependent, or reduced read-only work. Unannotated or write-capable tools fail closed to call_destructive_tool. A truncated result carries a connecta.result action.";
912
+ const SEARCH_DESC = (readOnly) => `${readOnly ? "Use top-level search for catalog inspection; this read-only endpoint omits write tools." : "Use top-level search for catalog inspection or approval-required work before call_destructive_tool."} Unknown-address read-only work belongs in one execute_code program that searches, 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" finds tools that run unasked; "approvalRequired" finds the fail-closed complement. These filters grant no authority. Empty query browses. Returns { catalogErrors, tools, total, offset, limit, hasMore }; read catalogErrors and absence before selecting a tool. Compact schemas have schemaFormat="text".`;
913
+ const CALL_DESC = (readOnly) => `Call one known-address tool explicitly annotated readOnlyHint: true. Use execute_code for unknown-address, multiple, dependent, or reduced read-only work. ${readOnly ? "This endpoint is read-only and refuses every write." : "Unannotated or write-capable tools fail closed to call_destructive_tool."} A truncated result carries a connecta.result action.`;
920
914
  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.";
915
+ /** A read-only endpoint keeps the six-tool surface, so this tool says why it never writes here. */
916
+ 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 call_tool or execute_code for them.";
921
917
  const AUTHORIZE_DESC = "Use after auth_required. Returns an OAuth or operator-credential handoff, or reports required deployment configuration. force=true requests an OAuth restart when the verified user opens the /connect URL; this tool never accepts credentials.";
922
918
  const SKILLS_DESC = "List or fetch on-demand guidance. Fetch usage only when the always-loaded instructions are insufficient or a program needs repair.";
923
919
  /**
@@ -1001,7 +997,7 @@ export function registerMetaTools(server, registry, ctx) {
1001
997
  const mt = createMetaTools(registry, ctx.baseUrl, {
1002
998
  downstreamInput: Boolean(ctx.authElicitation),
1003
999
  requestScope: ctx.requestScope,
1004
- trust: ctx.trust,
1000
+ readOnly: ctx.readOnly,
1005
1001
  defaultToolTimeoutMs: ctx.defaultToolTimeoutMs,
1006
1002
  probeTimeoutMs: ctx.probeTimeoutMs,
1007
1003
  discoveryConcurrency: ctx.discoveryConcurrency,
@@ -1028,7 +1024,7 @@ export function registerMetaTools(server, registry, ctx) {
1028
1024
  return mt.skills(args);
1029
1025
  });
1030
1026
  server.registerTool("search_tools", {
1031
- description: describedFor(registry, SEARCH_DESC, "search"),
1027
+ description: describedFor(registry, SEARCH_DESC(ctx.readOnly === true), "search"),
1032
1028
  inputSchema: SEARCH_INPUT,
1033
1029
  outputSchema: SEARCH_OUTPUT,
1034
1030
  annotations: READ_ONLY_REMOTE,
@@ -1037,7 +1033,7 @@ export function registerMetaTools(server, registry, ctx) {
1037
1033
  return mt.searchTools(args);
1038
1034
  });
1039
1035
  server.registerTool("call_tool", {
1040
- description: CALL_DESC,
1036
+ description: CALL_DESC(ctx.readOnly === true),
1041
1037
  inputSchema: CALL_INPUT,
1042
1038
  // Default mode returns native content, so no structured result is promised.
1043
1039
  // call_tool admits only tools that are themselves explicitly read-only;
@@ -1050,7 +1046,9 @@ export function registerMetaTools(server, registry, ctx) {
1050
1046
  : mt.callTool(args);
1051
1047
  });
1052
1048
  server.registerTool("call_destructive_tool", {
1053
- description: describedFor(registry, CALL_DESTRUCTIVE_DESC, "destructive"),
1049
+ description: ctx.readOnly === true
1050
+ ? CALL_DESTRUCTIVE_READ_ONLY_DESC
1051
+ : describedFor(registry, CALL_DESTRUCTIVE_DESC, "destructive"),
1054
1052
  inputSchema: CALL_DESTRUCTIVE_INPUT,
1055
1053
  annotations: {
1056
1054
  destructiveHint: true,
@@ -8,7 +8,7 @@ type FailureStep = "MCP handshake" | "tools/list" | "tools/call" | "resources/re
8
8
  */
9
9
  export declare const OAUTH_ERROR_CODES: ReadonlySet<string>;
10
10
  /** Inbound denial facts, never identity text or provider diagnostics. */
11
- export type AuthDenialReason = "authentication_failed" | "authorization_header_invalid" | "token_type_mismatch" | "oauth_verification_failed" | "oauth_client_not_allowed" | "oauth_binding_mismatch" | "session_origin_mismatch" | "user_missing" | "email_lookup_failed" | "verified_email_invalid" | "email_domain_denied" | "gate_denied" | "gate_failed" | "pool_not_declared" | "pool_grant_denied" | "pool_grant_threw";
11
+ export type AuthDenialReason = "authentication_failed" | "authorization_header_invalid" | "token_type_mismatch" | "oauth_verification_failed" | "oauth_client_not_allowed" | "oauth_binding_mismatch" | "session_origin_mismatch" | "user_missing" | "email_lookup_failed" | "verified_email_invalid" | "email_domain_denied" | "gate_denied" | "gate_failed" | "default_grant_denied" | "default_grant_threw" | "pool_not_declared" | "pool_grant_denied" | "pool_grant_threw";
12
12
  /**
13
13
  * Name `ctor`'s instances `label` in records and fixed text. Call once, at
14
14
  * module scope, beside the class. An error whose prototype chain reaches no
@@ -104,7 +104,7 @@ export declare function failureRecord(subject: FailureSubject, failure?: unknown
104
104
  * The events an operator log reports failures as. Closed: a new one is a
105
105
  * reviewed line here, never a string assembled at the call site.
106
106
  */
107
- export type FailureEvent = "negotiation cache read failed" | "negotiation cache write failed" | "connectorAccess grant is unreachable" | "call failed" | "input schema unusable; arguments are not validated" | "catalog read failed" | "catalog persistence failed" | "catalog invalidation failed" | "catalog refresh observation failed" | "catalog refresh failed; serving stale catalog" | "deferred catalog refresh failed" | "deferred catalog refresh could not attach to the runtime" | "catalog drift check failed" | "schema digest check failed; serving digested reviews as writes" | "credential shape read failed" | "credential test failed" | "credential test threw" | "session termination refused or failed; the downstream session may remain until its provider timeout" | "activity record failed" | "catalog drift record failed" | "activity read failed" | "OAuth start failed" | "OAuth disconnect failed" | "OAuth callback verifyState threw; no authorization code was exchanged" | "OAuth callback handoff could not be consumed; no authorization code was exchanged" | "MCP pool request denied" | "OAuth error callback consent could not be consumed; no authorization code was exchanged" | "MCP handler error" | "operator status" | "result paging unavailable" | "request failed" | "Clerk request denied" | "Clerk authentication failed";
107
+ export type FailureEvent = "negotiation cache read failed" | "negotiation cache write failed" | "connectorAccess grant is unreachable" | "call failed" | "input schema unusable; arguments are not validated" | "catalog read failed" | "catalog persistence failed" | "catalog invalidation failed" | "catalog refresh observation failed" | "catalog refresh failed; serving stale catalog" | "deferred catalog refresh failed" | "deferred catalog refresh could not attach to the runtime" | "catalog drift check failed" | "schema digest check failed; serving digested reviews as writes" | "credential shape read failed" | "credential test failed" | "credential test threw" | "session termination refused or failed; the downstream session may remain until its provider timeout" | "activity record failed" | "catalog drift record failed" | "activity read failed" | "OAuth start failed" | "OAuth disconnect failed" | "OAuth callback verifyState threw; no authorization code was exchanged" | "OAuth callback handoff could not be consumed; no authorization code was exchanged" | "MCP default endpoint request denied" | "MCP pool request denied" | "OAuth error callback consent could not be consumed; no authorization code was exchanged" | "MCP handler error" | "operator status" | "result paging unavailable" | "request failed" | "Clerk request denied" | "Clerk authentication failed";
108
108
  /**
109
109
  * Log one failure as `[connecta] <event>` and its record. A record
110
110
  * `failureRecord` did not build is replaced, whatever its shape, by a fixed
@@ -112,6 +112,8 @@ const AUTH_DENIAL_REASONS = new Set([
112
112
  "email_domain_denied",
113
113
  "gate_denied",
114
114
  "gate_failed",
115
+ "default_grant_denied",
116
+ "default_grant_threw",
115
117
  "pool_not_declared",
116
118
  "pool_grant_denied",
117
119
  "pool_grant_threw",
@@ -2,7 +2,7 @@ import type { ActivityOutcome } from "../activity.js";
2
2
  import type { ConnectaConfigDescription } from "../describe-config.js";
3
3
  import type { ConnectorStatus, ConnectorToolDescription } from "../types.js";
4
4
  import type { UiProblem } from "./model.js";
5
- /** The registry's final verdict, independent of the viewer's pool trust. */
5
+ /** The registry's final verdict, independent of whether the viewer's endpoint is read-only. */
6
6
  export interface OperatorTool extends ConnectorToolDescription {
7
7
  address: string;
8
8
  classification: "read" | "write";
@@ -36,15 +36,16 @@ export interface OperatorGrant {
36
36
  export interface OperatorPool {
37
37
  name: string;
38
38
  path: string;
39
- trust: "trusted" | "read-only";
39
+ /** Whether this pool's endpoint refuses every write. */
40
+ readOnly: boolean;
40
41
  /** Pool access intersected with the caller's grants. */
41
42
  grants: OperatorGrant[];
42
43
  }
43
44
  export interface OperatorViewer {
44
45
  interactive: boolean;
45
46
  grants: OperatorGrant[];
46
- /** Trust of the root /mcp endpoint. */
47
- trust: "trusted" | "read-only";
47
+ /** Whether the root /mcp endpoint refuses every write. */
48
+ readOnly: boolean;
48
49
  pools: OperatorPool[];
49
50
  permissions: {
50
51
  activity: boolean;