@zackbart/connecta 0.34.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 +30 -0
- package/README.md +3 -2
- package/bin/version.mjs +1 -1
- package/dist/catalog-drift.d.ts +30 -26
- package/dist/catalog-drift.js +169 -84
- package/dist/catalog-service.js +1 -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 +2 -0
- package/dist/describe-config.js +1 -0
- package/dist/invocation.js +3 -2
- package/dist/meta-tools.js +3 -2
- package/dist/operator-ui/config-summary.js +3 -0
- package/dist/operator-ui/connection-map.js +2 -1
- package/dist/operator-ui/endpoints.js +1 -1
- package/dist/operator-ui/generated.js +3 -3
- package/dist/operator-ui/health.js +52 -0
- package/dist/operator-ui/overview.js +6 -17
- package/dist/operator-ui/view.d.ts +3 -4
- package/dist/operator-ui/view.js +29 -11
- package/dist/provider.d.ts +6 -1
- package/dist/provider.js +18 -1
- package/dist/providers/_shared/google/workspace.d.ts +3 -0
- package/dist/providers/_shared/google/workspace.js +1 -1
- 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 +1 -1
- 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/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 +1 -1
- package/dist/providers/notion/index.d.ts +2 -0
- package/dist/providers/notion/index.js +1 -1
- package/dist/providers/overflow/index.d.ts +2 -0
- package/dist/providers/planning-center/index.d.ts +2 -0
- 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/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/vercel/index.d.ts +2 -0
- package/dist/providers/vercel/index.js +1 -1
- package/dist/providers/vercel/openapi.generated.js +391 -374
- package/dist/providers/vercel/skill.generated.js +1 -1
- package/dist/providers/vercel/value-safety.js +14 -4
- package/dist/registry.d.ts +10 -14
- package/dist/registry.js +62 -62
- package/dist/routes/ui-data.js +8 -2
- package/dist/skills.js +8 -2
- package/dist/types.d.ts +18 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +48 -5
- package/documentation/code-mode.md +7 -5
- package/documentation/integrating.md +55 -1
- package/documentation/meta-tools.md +2 -2
- package/documentation/operating.md +1 -1
- package/documentation/operator-tests.md +8 -2
- package/documentation/operator-ui.md +58 -11
- package/documentation/provider-auth-migration.md +22 -0
- package/package.json +5 -1
- package/templates/node/package.json +1 -1
|
@@ -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;
|
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 } : {}),
|
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/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;
|
|
@@ -401,6 +401,7 @@ const AUTH_MODE = {
|
|
|
401
401
|
* catalog. Anything else becomes a tile, so a new field is never hidden.
|
|
402
402
|
*/
|
|
403
403
|
const CONNECTOR_KNOWN = [
|
|
404
|
+
"readOnly",
|
|
404
405
|
"id",
|
|
405
406
|
"title",
|
|
406
407
|
"description",
|
|
@@ -488,6 +489,8 @@ export function connectorSummary(connector, sources = {}) {
|
|
|
488
489
|
const base = `config.connectors.${connector.id}`;
|
|
489
490
|
const kind = SOURCE_KIND[connector.source.kind] ?? SOURCE_KIND.custom;
|
|
490
491
|
const badges = [{ id: "kind", label: kind.label, icon: kind.icon }];
|
|
492
|
+
if (connector.readOnly)
|
|
493
|
+
badges.push({ id: "readOnly", label: "Read-only" });
|
|
491
494
|
if (connector.source.provider)
|
|
492
495
|
badges.push({ id: "provider", label: connector.source.provider, icon: "box" });
|
|
493
496
|
badges.push(connector.authScope === "personal"
|
|
@@ -210,7 +210,8 @@ export function connectionMap(contract, options = {}) {
|
|
|
210
210
|
const maintainers = new Set((contract.root?.maintainers ?? []).flatMap((member) => member.kind === "human" ? [`person:${member.namespace}:${member.id}`] : []));
|
|
211
211
|
const connectors = (contract.role === "maintainer" ? (contract.config?.connectors ?? []) : []).flatMap((config) => {
|
|
212
212
|
const live = contract.live.connectors.find((c) => c.id === config.id);
|
|
213
|
-
|
|
213
|
+
const signals = options.signals && Object.hasOwn(options.signals, config.id) ? options.signals[config.id] : undefined;
|
|
214
|
+
return live ? [{ config, live, health: connectorVerdict(live, signals) }] : [];
|
|
214
215
|
});
|
|
215
216
|
const health = new Map(connectors.map(({ config, health }) => [config.id, health]));
|
|
216
217
|
const nodes = [];
|
|
@@ -63,7 +63,7 @@ function endpointGroups(contract, grants, readOnly, pool) {
|
|
|
63
63
|
if (!live || !grant)
|
|
64
64
|
return [];
|
|
65
65
|
const tools = live.tools.filter((tool) => {
|
|
66
|
-
if (readOnly && tool.classification !== "read")
|
|
66
|
+
if ((readOnly || config.readOnly) && tool.classification !== "read")
|
|
67
67
|
return false;
|
|
68
68
|
return (grant.tools === "all" ||
|
|
69
69
|
grant.tools.some((g) => g.name === tool.name && (!g.requireReadOnly || tool.classification === "read")));
|