@zackbart/connecta 0.32.0 → 0.34.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 +98 -0
- package/README.md +95 -116
- package/SECURITY.md +1 -1
- package/bin/connecta.mjs +24 -6
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.d.ts +167 -23
- package/dist/access-tokens.js +676 -140
- package/dist/activity.d.ts +3 -0
- package/dist/agent-identity.js +47 -0
- package/dist/auth/cloudflare-access.js +13 -5
- package/dist/branding.d.ts +7 -0
- package/dist/branding.js +76 -9
- package/dist/call-admission-context.js +15 -0
- package/dist/call-admission.js +30 -43
- package/dist/call-recovery.js +6 -2
- package/dist/catalog-service.js +15 -1
- package/dist/config-value-sources.js +16 -9
- package/dist/config.d.ts +48 -75
- package/dist/config.js +144 -36
- package/dist/connector-access.d.ts +4 -34
- package/dist/connector-access.js +15 -99
- package/dist/connector-caller.d.ts +7 -1
- package/dist/connector-caller.js +4 -2
- package/dist/connectors/api-connector.d.ts +27 -4
- package/dist/connectors/api-connector.js +2 -0
- package/dist/connectors/remote-mcp.js +2 -0
- package/dist/describe-config.d.ts +31 -7
- package/dist/describe-config.js +8 -17
- package/dist/execute.js +1 -11
- package/dist/fix-prompt.js +2 -2
- package/dist/index.d.ts +4 -3
- package/dist/index.js +30 -10
- package/dist/invocation.js +5 -3
- package/dist/membership.d.ts +53 -0
- package/dist/membership.js +121 -0
- package/dist/module-contracts.d.ts +23 -6
- package/dist/oauth-callback-outcome.js +2 -2
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +2 -4
- package/dist/operator-ui/agents.d.ts +74 -0
- package/dist/operator-ui/agents.js +104 -0
- package/dist/operator-ui/config-summary.js +562 -0
- package/dist/operator-ui/connection-map.js +375 -0
- package/dist/operator-ui/contract.d.ts +149 -28
- package/dist/operator-ui/endpoints.js +221 -0
- package/dist/operator-ui/fix-prompts.js +5 -5
- package/dist/operator-ui/generated.js +5 -4
- package/dist/operator-ui/member-page.js +97 -0
- package/dist/operator-ui/model.d.ts +14 -6
- package/dist/operator-ui/overview.js +134 -0
- package/dist/operator-ui/view.d.ts +107 -37
- package/dist/operator-ui/view.js +163 -86
- package/dist/page-styles.js +5 -3
- package/dist/provider.d.ts +61 -8
- package/dist/provider.js +46 -7
- package/dist/providers/_shared/google/workspace.d.ts +56 -17
- package/dist/providers/_shared/google/workspace.js +34 -8
- package/dist/providers/_shared/rest/tools.js +2 -2
- package/dist/providers/_shared/rest/value-safety.d.ts +50 -0
- package/dist/providers/_shared/rest/value-safety.js +699 -7
- package/dist/providers/ccb/index.js +2 -2
- package/dist/providers/cloudflare/index.js +3 -3
- package/dist/providers/cloudflare/rest.js +25 -3
- package/dist/providers/cloudflare/skill.generated.js +1 -1
- package/dist/providers/cloudflare/value-safety.js +32 -0
- package/dist/providers/docs/index.js +4 -4
- package/dist/providers/docs/skill.generated.js +2 -2
- package/dist/providers/drive/index.js +4 -4
- package/dist/providers/drive/skill.generated.js +2 -2
- package/dist/providers/forms/index.js +4 -4
- package/dist/providers/forms/skill.generated.js +2 -2
- package/dist/providers/gmail/index.js +11 -9
- package/dist/providers/gmail/skill.generated.js +2 -2
- package/dist/providers/mixpanel/index.js +1 -1
- package/dist/providers/mixpanel/skill.generated.js +3 -3
- package/dist/providers/notion/index.d.ts +7 -0
- package/dist/providers/notion/index.js +100 -11
- package/dist/providers/notion/rest.js +45 -5
- package/dist/providers/notion/skill.generated.js +2 -2
- package/dist/providers/notion/value-safety.js +30 -5
- package/dist/providers/overflow/index.js +2 -2
- package/dist/providers/overflow/skill.generated.js +2 -2
- package/dist/providers/planning-center/index.js +2 -2
- package/dist/providers/planning-center/skill.generated.js +4 -4
- package/dist/providers/revenuecat/skill.generated.js +2 -2
- package/dist/providers/sheets/index.d.ts +3 -0
- package/dist/providers/sheets/index.js +7 -4
- package/dist/providers/sheets/skill.generated.js +2 -2
- package/dist/providers/slides/index.js +4 -4
- package/dist/providers/slides/skill.generated.js +2 -2
- package/dist/providers/stripe/rest.js +9 -2
- package/dist/providers/stripe/skill.generated.js +1 -1
- package/dist/providers/stripe/value-safety.js +22 -0
- package/dist/providers/tithely/index.js +1 -1
- package/dist/providers/vercel/index.js +1 -1
- package/dist/providers/vercel/rest.js +20 -2
- package/dist/providers/vercel/skill.generated.js +2 -2
- package/dist/providers/vercel/value-safety.js +22 -0
- package/dist/registry.d.ts +2 -3
- package/dist/registry.js +23 -9
- package/dist/routes/access-tokens.js +82 -75
- package/dist/routes/activity-disclosure.js +17 -18
- package/dist/routes/activity.js +34 -54
- package/dist/routes/actor-labels.js +33 -4
- package/dist/routes/agent-path.js +43 -0
- package/dist/routes/connect.js +16 -12
- package/dist/routes/credentials.js +16 -40
- package/dist/routes/mcp.js +161 -104
- package/dist/routes/oauth-management.js +11 -5
- package/dist/routes/oauth.js +5 -11
- package/dist/routes/operator.js +24 -23
- package/dist/routes/shared.d.ts +60 -20
- package/dist/routes/shared.js +187 -96
- package/dist/routes/ui-config.js +437 -97
- package/dist/routes/ui-data.js +2 -2
- package/dist/routes/ui.js +9 -16
- package/dist/runtime/call-admission.js +9 -0
- package/dist/server.js +5 -0
- package/dist/skills.js +10 -0
- package/dist/storage/keys.js +9 -1
- package/dist/storage/sql.js +5 -1
- package/dist/types.d.ts +29 -7
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +23 -16
- package/dist/usage-guide.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/workspace-address.js +9 -0
- package/dist/workspace-subject.js +23 -0
- package/documentation/README.md +47 -23
- package/documentation/architecture.md +371 -61
- package/documentation/auth.md +897 -249
- package/documentation/code-mode.md +37 -16
- package/documentation/deploying.md +126 -25
- package/documentation/integrating.md +97 -23
- package/documentation/meta-tools.md +50 -31
- package/documentation/migration-0.33.md +352 -0
- package/documentation/migration-0.34.md +67 -0
- package/documentation/operating.md +3 -1
- package/documentation/operator-tests.md +147 -34
- package/documentation/operator-ui.md +519 -64
- package/documentation/provider-auth-migration.md +37 -23
- package/documentation/provider-migration-0.29.md +2 -2
- package/examples/worker/AGENTS.md +37 -9
- package/examples/worker/README.md +127 -61
- package/examples/worker/src/connecta.config.ts +40 -23
- package/examples/worker/wrangler.jsonc +4 -1
- package/package.json +19 -18
- package/templates/node/.env.example +3 -0
- package/templates/node/AGENTS.md +33 -6
- package/templates/node/Dockerfile +2 -2
- package/templates/node/README.md +80 -52
- package/templates/node/docker-compose.yml +3 -2
- package/templates/node/package.json +4 -3
- package/templates/node/src/adopt-token.ts +23 -0
- package/templates/node/src/connecta.config.ts +28 -12
- package/templates/node/src/rotate-token.ts +17 -0
- package/templates/node/src/provision-token.ts +0 -17
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/** How many chips an endpoint row shows before "+N more". */
|
|
2
|
+
const ACCESS_CHIP_LIMIT = 4;
|
|
3
|
+
/** Up to two letters for a person's avatar, from their label or raw id. */
|
|
4
|
+
export function initialsOf(text) {
|
|
5
|
+
const words = text
|
|
6
|
+
.replace(/^user_/, "")
|
|
7
|
+
.split(/[\s._@-]+/)
|
|
8
|
+
.filter(Boolean);
|
|
9
|
+
const letters = words.length > 1 ? words[0][0] + words[1][0] : (words[0] ?? text).slice(0, 2);
|
|
10
|
+
return letters.toUpperCase();
|
|
11
|
+
}
|
|
12
|
+
/** A stable tint bucket for a member key, shared by chips and the Overview map. */
|
|
13
|
+
export function hueOf(key) {
|
|
14
|
+
let hash = 0;
|
|
15
|
+
for (const char of key)
|
|
16
|
+
hash = (hash * 31 + char.charCodeAt(0)) >>> 0;
|
|
17
|
+
return hash % 6;
|
|
18
|
+
}
|
|
19
|
+
/** A member as a chip. Labels are null on Access deployments, where the raw id shows instead. */
|
|
20
|
+
function accessChip(member) {
|
|
21
|
+
if (member.kind === "agent") {
|
|
22
|
+
const key = `agent:${member.name}`;
|
|
23
|
+
return { key, kind: "agent", text: member.name, initials: "", hue: hueOf(key) };
|
|
24
|
+
}
|
|
25
|
+
const key = `${member.namespace}:${member.id}`;
|
|
26
|
+
const text = member.label ?? member.id;
|
|
27
|
+
return { key, kind: "human", text, initials: initialsOf(text), hue: hueOf(key) };
|
|
28
|
+
}
|
|
29
|
+
/** Chips for one endpoint in contract order, people first, without repeats. */
|
|
30
|
+
export function accessChips(members) {
|
|
31
|
+
const unique = new Map();
|
|
32
|
+
for (const chip of members.map(accessChip))
|
|
33
|
+
if (!unique.has(chip.key))
|
|
34
|
+
unique.set(chip.key, chip);
|
|
35
|
+
const chips = [...unique.values()];
|
|
36
|
+
return [...chips.filter((chip) => chip.kind === "human"), ...chips.filter((chip) => chip.kind === "agent")];
|
|
37
|
+
}
|
|
38
|
+
/** The chips a collapsed row shows, and how many the "+N more" button holds back. */
|
|
39
|
+
export function visibleChips(chips, expanded) {
|
|
40
|
+
if (expanded || chips.length <= ACCESS_CHIP_LIMIT)
|
|
41
|
+
return { shown: chips, hidden: 0 };
|
|
42
|
+
return { shown: chips.slice(0, ACCESS_CHIP_LIMIT), hidden: chips.length - ACCESS_CHIP_LIMIT };
|
|
43
|
+
}
|
|
44
|
+
/** Matrix cells for one endpoint, keyed by connector id. */
|
|
45
|
+
function cellsFor(contract, pool) {
|
|
46
|
+
return new Map(contract.matrix.filter((cell) => cell.pool === pool).map((cell) => [cell.connectorId, cell]));
|
|
47
|
+
}
|
|
48
|
+
/** Plain /mcp declares no tool list; its matrix cells are its grants. */
|
|
49
|
+
function rootGrants(contract) {
|
|
50
|
+
return contract.matrix
|
|
51
|
+
.filter((cell) => cell.pool === null)
|
|
52
|
+
.map((cell) => ({
|
|
53
|
+
connectorId: cell.connectorId,
|
|
54
|
+
tools: cell.tools === "all" ? "all" : cell.tools.map((name) => ({ name, requireReadOnly: false })),
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
/** Intersect declared grants with final live verdicts, in config order. */
|
|
58
|
+
function endpointGroups(contract, grants, readOnly, pool) {
|
|
59
|
+
const cells = cellsFor(contract, pool);
|
|
60
|
+
return (contract.config?.connectors ?? []).flatMap((config) => {
|
|
61
|
+
const live = contract.live.connectors.find((c) => c.id === config.id);
|
|
62
|
+
const grant = grants.find((g) => g.connectorId === config.id);
|
|
63
|
+
if (!live || !grant)
|
|
64
|
+
return [];
|
|
65
|
+
const tools = live.tools.filter((tool) => {
|
|
66
|
+
if (readOnly && tool.classification !== "read")
|
|
67
|
+
return false;
|
|
68
|
+
return (grant.tools === "all" ||
|
|
69
|
+
grant.tools.some((g) => g.name === tool.name && (!g.requireReadOnly || tool.classification === "read")));
|
|
70
|
+
});
|
|
71
|
+
// A granted connector with no complete catalog still needs a visible fix.
|
|
72
|
+
return [{ config, live, tools, runsAs: cells.get(config.id)?.runsAs ?? null }];
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const toolCount = (groups) => groups.reduce((n, group) => n + group.tools.length, 0);
|
|
76
|
+
/** How many of these tools read and how many write; an unclassified tool counts as neither. */
|
|
77
|
+
export function toolSplit(tools) {
|
|
78
|
+
let read = 0;
|
|
79
|
+
let write = 0;
|
|
80
|
+
for (const tool of tools) {
|
|
81
|
+
if (tool.classification === "read")
|
|
82
|
+
read++;
|
|
83
|
+
else if (tool.classification === "write")
|
|
84
|
+
write++;
|
|
85
|
+
}
|
|
86
|
+
return { read, write };
|
|
87
|
+
}
|
|
88
|
+
/** One row for plain /mcp, then one per pool, in contract order. Maintainer view only. */
|
|
89
|
+
export function endpointRows(contract) {
|
|
90
|
+
if (contract.role !== "maintainer")
|
|
91
|
+
return [];
|
|
92
|
+
const rows = [];
|
|
93
|
+
if (contract.root) {
|
|
94
|
+
const groups = endpointGroups(contract, rootGrants(contract), contract.root.readOnly, null);
|
|
95
|
+
rows.push({
|
|
96
|
+
pool: null,
|
|
97
|
+
label: "Default",
|
|
98
|
+
path: contract.root.path,
|
|
99
|
+
agentPath: null,
|
|
100
|
+
readOnly: contract.root.readOnly,
|
|
101
|
+
toolCount: toolCount(groups),
|
|
102
|
+
groups,
|
|
103
|
+
access: accessChips(contract.root.maintainers),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
for (const pool of contract.pools) {
|
|
107
|
+
const groups = endpointGroups(contract, pool.tools, pool.readOnly, pool.name);
|
|
108
|
+
rows.push({
|
|
109
|
+
pool: pool.name,
|
|
110
|
+
label: pool.name,
|
|
111
|
+
path: pool.humanPath,
|
|
112
|
+
agentPath: pool.members.some((member) => member.kind === "agent") ? pool.agentPath : null,
|
|
113
|
+
readOnly: pool.readOnly,
|
|
114
|
+
toolCount: toolCount(groups),
|
|
115
|
+
groups,
|
|
116
|
+
access: accessChips(pool.members),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return rows;
|
|
120
|
+
}
|
|
121
|
+
/** A calendar day, without the time: when a shared credential was saved. */
|
|
122
|
+
function formatDay(value) {
|
|
123
|
+
const date = new Date(value);
|
|
124
|
+
return Number.isNaN(date.valueOf()) ? "" : date.toLocaleDateString(undefined, { dateStyle: "medium" });
|
|
125
|
+
}
|
|
126
|
+
/** Where a shared credential lives; a vault or OAuth date when the contract has one. */
|
|
127
|
+
function sharedSourceLabel(source, date) {
|
|
128
|
+
switch (source) {
|
|
129
|
+
case "vault":
|
|
130
|
+
return date ? `saved ${date}` : "saved";
|
|
131
|
+
case "oauth":
|
|
132
|
+
return date ? `connected ${date}` : "connected";
|
|
133
|
+
case "missing":
|
|
134
|
+
return "not configured";
|
|
135
|
+
case "config":
|
|
136
|
+
return "set in deployment";
|
|
137
|
+
case "unauthenticated":
|
|
138
|
+
return "no credential needed";
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** Whose account a connector acts with on one endpoint, in one line. */
|
|
142
|
+
export function runsAsLabel(runsAs, day = formatDay) {
|
|
143
|
+
switch (runsAs.kind) {
|
|
144
|
+
case "shared":
|
|
145
|
+
return `Shared · ${sharedSourceLabel(runsAs.source, runsAs.savedAt ? day(runsAs.savedAt) : "")}`;
|
|
146
|
+
case "personal":
|
|
147
|
+
return `Personal · ${runsAs.connectedMembers} of ${runsAs.members} connected`;
|
|
148
|
+
case "workspace":
|
|
149
|
+
return `Workspace · people: self · agents: ${runsAs.agents ?? "none"}`;
|
|
150
|
+
case "workspaceFixed":
|
|
151
|
+
return `Everyone acts as ${runsAs.subject}`;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
export function matrixColumns(contract) {
|
|
155
|
+
if (contract.role !== "maintainer")
|
|
156
|
+
return [];
|
|
157
|
+
return [
|
|
158
|
+
...(contract.root ? [{ pool: null, path: contract.root.path, readOnly: contract.root.readOnly }] : []),
|
|
159
|
+
...contract.pools.map((pool) => ({ pool: pool.name, path: pool.humanPath, readOnly: pool.readOnly })),
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
/** What one connector × endpoint cell holds, straight from the contract's matrix. */
|
|
163
|
+
export function matrixCell(contract, connectorId, column) {
|
|
164
|
+
const cell = contract.matrix.find((c) => c.connectorId === connectorId && c.pool === column.pool);
|
|
165
|
+
if (!cell)
|
|
166
|
+
return { kind: "none" };
|
|
167
|
+
const overlay = contract.live.connectors.find((c) => c.id === connectorId);
|
|
168
|
+
const live = overlay?.tools ?? [];
|
|
169
|
+
const tools = cell.tools === "all"
|
|
170
|
+
? live.map(({ name, classification }) => ({ name, classification }))
|
|
171
|
+
: cell.tools.map((name) => ({ name, classification: live.find((t) => t.name === name)?.classification ?? null }));
|
|
172
|
+
const known = overlay && (live.length > 0 || (overlay.status === "ok" && !overlay.problem));
|
|
173
|
+
const granted = cell.tools === "all" ? live : live.filter((tool) => cell.tools.includes(tool.name));
|
|
174
|
+
return {
|
|
175
|
+
kind: cell.tools === "all" ? "all" : "some",
|
|
176
|
+
tools,
|
|
177
|
+
runsAs: cell.runsAs,
|
|
178
|
+
coverage: known ? { granted: granted.length, total: live.length } : null,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/** The live grant fraction, or null when no complete catalog is available. */
|
|
182
|
+
export function matrixCellShare(cell) {
|
|
183
|
+
if (cell.kind === "none")
|
|
184
|
+
return 0;
|
|
185
|
+
if (!cell.coverage)
|
|
186
|
+
return null;
|
|
187
|
+
return cell.coverage.total ? cell.coverage.granted / cell.coverage.total : 0;
|
|
188
|
+
}
|
|
189
|
+
/** The cell's visible text: "all", a count, or a dash. */
|
|
190
|
+
export function matrixCellText(cell) {
|
|
191
|
+
if (cell.kind === "none")
|
|
192
|
+
return "—";
|
|
193
|
+
if (!cell.coverage)
|
|
194
|
+
return "?";
|
|
195
|
+
return cell.kind === "all" ? "all" : String(cell.coverage.granted);
|
|
196
|
+
}
|
|
197
|
+
/** The cell's accessible name, e.g. "GitHub on /mcp/support: 1 of 2 live tools". */
|
|
198
|
+
export function matrixCellLabel(connector, column, cell) {
|
|
199
|
+
const where = `${connector} on ${column.path}`;
|
|
200
|
+
if (cell.kind === "none")
|
|
201
|
+
return `${where}: no tools`;
|
|
202
|
+
const what = !cell.coverage
|
|
203
|
+
? "unknown live tool coverage"
|
|
204
|
+
: cell.kind === "all"
|
|
205
|
+
? `all ${cell.coverage.total} tools`
|
|
206
|
+
: `${cell.coverage.granted} of ${cell.coverage.total} live tools`;
|
|
207
|
+
return `${where}: ${what}${column.readOnly ? ", read-only" : ""}`;
|
|
208
|
+
}
|
|
209
|
+
/** The connector's subject for the matrix's Runs as column: the /mcp cell, else its first pool's. */
|
|
210
|
+
export function connectorRunsAs(contract, connectorId) {
|
|
211
|
+
const cells = contract.matrix.filter((cell) => cell.connectorId === connectorId);
|
|
212
|
+
const root = cells.find((cell) => cell.pool === null);
|
|
213
|
+
if (root)
|
|
214
|
+
return root.runsAs;
|
|
215
|
+
for (const pool of contract.pools) {
|
|
216
|
+
const cell = cells.find((c) => c.pool === pool.name);
|
|
217
|
+
if (cell)
|
|
218
|
+
return cell.runsAs;
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
@@ -11,7 +11,7 @@ const CATALOGUE = {
|
|
|
11
11
|
oauth_required: {
|
|
12
12
|
problem: "A downstream OAuth connector needs authorization: no grant is stored, or the stored grant expired or was revoked and could not be refreshed.",
|
|
13
13
|
steps: [
|
|
14
|
-
"Reauthorize from the
|
|
14
|
+
"Reauthorize from the connector's Connection tab (Connect or Reconnect) or with authorize_connector. A lapsed grant needs no code change.",
|
|
15
15
|
"If it needs reauthorizing again soon after, check that the OAuth client requests offline access or a refresh token, and that the storage holding OAuth tokens persists across restarts and is shared by every instance.",
|
|
16
16
|
"If authorization cannot start at all, check the connector's OAuth client configuration and the deployment's publicUrl, which forms the /oauth/callback/<connector id> redirect URI.",
|
|
17
17
|
],
|
|
@@ -19,8 +19,8 @@ const CATALOGUE = {
|
|
|
19
19
|
credential_required: {
|
|
20
20
|
problem: "A connector with an operator-managed credential slot has no usable credential stored.",
|
|
21
21
|
steps: [
|
|
22
|
-
"
|
|
23
|
-
"If nobody can,
|
|
22
|
+
"With a credential vault configured, a maintainer adds a shared credential on the operator page, and a pool member saves their own personal one from their view of it; neither needs a code change. Without a vault, configure one first.",
|
|
23
|
+
"If nobody can, list the person in the deployment's maintainers for shared auth, or in the members of a pool containing the connector for personal auth.",
|
|
24
24
|
"Check that the connector's credential declaration (label and fields) matches what the downstream actually needs.",
|
|
25
25
|
],
|
|
26
26
|
},
|
|
@@ -57,9 +57,9 @@ const CATALOGUE = {
|
|
|
57
57
|
oauth_action_failed: {
|
|
58
58
|
problem: "Restarting or disconnecting a connector's downstream OAuth from the operator page failed.",
|
|
59
59
|
steps: [
|
|
60
|
-
"Check that the connector implements startAuth and disconnectAuth, and that the storage holding its OAuth state is writable.",
|
|
60
|
+
"Check that the connector implements the hooks the action needs (startAuth, finishAuth, and verifyState to connect; disconnectAuth to disconnect), and that the storage holding its OAuth state is writable.",
|
|
61
61
|
"Check the connector's OAuth client configuration, including the authorization server it discovers or is given.",
|
|
62
|
-
"Confirm the
|
|
62
|
+
"Confirm the person is a maintainer for shared auth, or a member of a pool containing the connector for personal auth.",
|
|
63
63
|
],
|
|
64
64
|
},
|
|
65
65
|
catalog_failed: {
|