@zackbart/connecta 0.7.6 → 0.7.8
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 +99 -0
- package/dist/activity.d.ts +17 -0
- package/dist/activity.d.ts.map +1 -1
- package/dist/activity.js.map +1 -1
- package/dist/auth/clerk.d.ts.map +1 -1
- package/dist/auth/clerk.js +101 -0
- package/dist/auth/clerk.js.map +1 -1
- package/dist/auth/downstream-oauth.d.ts +57 -20
- package/dist/auth/downstream-oauth.d.ts.map +1 -1
- package/dist/auth/downstream-oauth.js +275 -67
- package/dist/auth/downstream-oauth.js.map +1 -1
- package/dist/catalog.d.ts +5 -3
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +13 -4
- package/dist/catalog.js.map +1 -1
- package/dist/connectors/remote-mcp.d.ts.map +1 -1
- package/dist/connectors/remote-mcp.js +165 -103
- package/dist/connectors/remote-mcp.js.map +1 -1
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +27 -15
- package/dist/execute.js.map +1 -1
- package/dist/index.d.ts +9 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/meta-tools.d.ts +4 -4
- package/dist/meta-tools.d.ts.map +1 -1
- package/dist/meta-tools.js +85 -25
- package/dist/meta-tools.js.map +1 -1
- package/dist/registry.d.ts +24 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +87 -17
- package/dist/registry.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +238 -19
- package/dist/server.js.map +1 -1
- package/dist/storage/file.d.ts.map +1 -1
- package/dist/storage/file.js +8 -0
- package/dist/storage/file.js.map +1 -1
- package/dist/types.d.ts +21 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui.d.ts +7 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +118 -4
- package/dist/ui.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/activity.ts +20 -0
- package/src/auth/clerk.ts +124 -0
- package/src/auth/downstream-oauth.ts +359 -68
- package/src/catalog.ts +24 -4
- package/src/connectors/remote-mcp.ts +172 -104
- package/src/execute.ts +29 -13
- package/src/index.ts +12 -1
- package/src/meta-tools.ts +89 -25
- package/src/registry.ts +115 -20
- package/src/server.ts +306 -16
- package/src/storage/file.ts +7 -0
- package/src/types.ts +23 -0
- package/src/ui.ts +124 -3
- package/src/version.ts +1 -1
package/src/ui.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import type { Registry } from "./registry.js";
|
|
12
12
|
import type { Toolkit } from "./toolkits.js";
|
|
13
13
|
import type { ConnectaBranding, UiAuthConfig } from "./types.js";
|
|
14
|
+
import { CONNECTA_VERSION } from "./version.js";
|
|
14
15
|
|
|
15
16
|
/** Connecta's default monochrome "C" mark. */
|
|
16
17
|
export const CONNECTA_FAVICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
@@ -256,6 +257,8 @@ export interface UiConnector {
|
|
|
256
257
|
authorizationUrl?: string;
|
|
257
258
|
toolCount: number;
|
|
258
259
|
tools: UiTool[];
|
|
260
|
+
/** This connector exposes operator-managed downstream OAuth lifecycle hooks. */
|
|
261
|
+
oauth?: boolean;
|
|
259
262
|
/**
|
|
260
263
|
* Verdict of the last proactive credential liveness check (issue #24), for the
|
|
261
264
|
* connectors that hold a credential connecta stores. Shown beside the live
|
|
@@ -299,11 +302,15 @@ export interface UiConnector {
|
|
|
299
302
|
|
|
300
303
|
export interface UiData {
|
|
301
304
|
serverInfo: { name: string; version: string };
|
|
305
|
+
/** Version of the installed @zackbart/connecta package. */
|
|
306
|
+
connectaVersion: string;
|
|
302
307
|
connectors: UiConnector[];
|
|
303
308
|
/** Read-only projection of the validated deployment config. */
|
|
304
309
|
toolkits: UiToolkit[];
|
|
305
310
|
activityEnabled: boolean;
|
|
306
311
|
credentialManagement: CredentialManagementCapability;
|
|
312
|
+
/** True only for an eligible, unrestricted Clerk operator. */
|
|
313
|
+
oauthManagement: boolean;
|
|
307
314
|
}
|
|
308
315
|
|
|
309
316
|
export interface UiToolkit {
|
|
@@ -408,6 +415,7 @@ export async function buildUiData(
|
|
|
408
415
|
: "requires_clerk",
|
|
409
416
|
toolkits?: ReadonlyMap<string, Toolkit>,
|
|
410
417
|
defer?: DeferredWork,
|
|
418
|
+
oauthManagement = false,
|
|
411
419
|
): Promise<UiData> {
|
|
412
420
|
const requestScope = {};
|
|
413
421
|
const connectorSet = registry.listConnectors();
|
|
@@ -533,6 +541,7 @@ export async function buildUiData(
|
|
|
533
541
|
: {}),
|
|
534
542
|
toolCount: tools.length,
|
|
535
543
|
tools,
|
|
544
|
+
...(c.disconnectAuth && c.startAuth ? { oauth: true } : {}),
|
|
536
545
|
...(credentialCheck
|
|
537
546
|
? {
|
|
538
547
|
credentialCheck: {
|
|
@@ -581,10 +590,12 @@ export async function buildUiData(
|
|
|
581
590
|
);
|
|
582
591
|
return {
|
|
583
592
|
serverInfo,
|
|
593
|
+
connectaVersion: CONNECTA_VERSION,
|
|
584
594
|
connectors,
|
|
585
595
|
toolkits: toolkitData,
|
|
586
596
|
activityEnabled,
|
|
587
597
|
credentialManagement,
|
|
598
|
+
oauthManagement,
|
|
588
599
|
};
|
|
589
600
|
}
|
|
590
601
|
|
|
@@ -860,6 +871,8 @@ ${clerkScript}
|
|
|
860
871
|
#notice { margin-bottom: .75rem; }
|
|
861
872
|
#notice:empty { display: none; }
|
|
862
873
|
#notice:not(:empty) { text-decoration: underline; }
|
|
874
|
+
#oauthNotice { margin-bottom: .75rem; }
|
|
875
|
+
#oauthNotice:empty { display: none; }
|
|
863
876
|
.error-notice, .msg { text-decoration: underline; }
|
|
864
877
|
.card,
|
|
865
878
|
.credential-card,
|
|
@@ -1047,6 +1060,7 @@ ${clerkScript}
|
|
|
1047
1060
|
.activity-time,
|
|
1048
1061
|
.activity-actor,
|
|
1049
1062
|
.activity-detail { color: var(--muted); font-size: .82rem; }
|
|
1063
|
+
.activity-actor-id { color: var(--muted); margin-top: .1rem; }
|
|
1050
1064
|
.activity-address { font-family: var(--mono); font-size: .78rem; overflow-wrap: anywhere; }
|
|
1051
1065
|
.activity-outcome { font-size: .9em; }
|
|
1052
1066
|
.activity-item.error .activity-outcome,
|
|
@@ -1148,6 +1162,7 @@ ${clerkScript}
|
|
|
1148
1162
|
</div>
|
|
1149
1163
|
</div>
|
|
1150
1164
|
<p class="cap" id="serverInfo">${escapeHtmlAttr(brand.productName)} operator</p>
|
|
1165
|
+
<p id="oauthNotice" class="meta" role="status" aria-live="polite" tabindex="-1"></p>
|
|
1151
1166
|
</div>
|
|
1152
1167
|
</div>
|
|
1153
1168
|
<section class="section pgrid" aria-labelledby="connectorLedgerHeading">
|
|
@@ -1260,6 +1275,12 @@ function setNotice(message, isError) {
|
|
|
1260
1275
|
$("credentialNotice").setAttribute("role", isError ? "alert" : "status");
|
|
1261
1276
|
}
|
|
1262
1277
|
|
|
1278
|
+
function setOauthNotice(message, isError) {
|
|
1279
|
+
$("oauthNotice").textContent = message || "";
|
|
1280
|
+
$("oauthNotice").classList.toggle("error-notice", Boolean(isError));
|
|
1281
|
+
$("oauthNotice").setAttribute("role", isError ? "alert" : "status");
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1263
1284
|
async function sessionToken() {
|
|
1264
1285
|
return AUTH.kind === "clerk"
|
|
1265
1286
|
? await Clerk.session?.getToken()
|
|
@@ -1287,6 +1308,7 @@ function clearIdentityState() {
|
|
|
1287
1308
|
DATA = null;
|
|
1288
1309
|
clearActivityState();
|
|
1289
1310
|
$("list").innerHTML = "";
|
|
1311
|
+
setOauthNotice("");
|
|
1290
1312
|
$("toolkitList").innerHTML = "";
|
|
1291
1313
|
$("filter").value = "";
|
|
1292
1314
|
$("credentialList").innerHTML = "";
|
|
@@ -1431,14 +1453,16 @@ async function load() {
|
|
|
1431
1453
|
$("app").classList.remove("hidden");
|
|
1432
1454
|
$("appNav").classList.remove("hidden");
|
|
1433
1455
|
const si = DATA.serverInfo || {};
|
|
1434
|
-
$("serverInfo").textContent = (si.name || ${escapeScriptString(brand.productName)}) + " v" + (
|
|
1456
|
+
$("serverInfo").textContent = (si.name || ${escapeScriptString(brand.productName)}) + " v" + (DATA.connectaVersion || "?");
|
|
1435
1457
|
updateCapabilities();
|
|
1436
1458
|
activatePage(CURRENT_PAGE);
|
|
1437
1459
|
}
|
|
1438
1460
|
|
|
1439
1461
|
function actorLabel(actor) {
|
|
1440
1462
|
if (!actor || !actor.kind) return "unknown";
|
|
1441
|
-
return actor.
|
|
1463
|
+
return actor.label
|
|
1464
|
+
? actor.kind + " · " + actor.label
|
|
1465
|
+
: actor.id ? actor.kind + " · " + actor.id : actor.kind;
|
|
1442
1466
|
}
|
|
1443
1467
|
|
|
1444
1468
|
function renderActivity() {
|
|
@@ -1457,6 +1481,8 @@ function renderActivity() {
|
|
|
1457
1481
|
event.errorCode,
|
|
1458
1482
|
actor.kind,
|
|
1459
1483
|
actor.id,
|
|
1484
|
+
actor.namespace,
|
|
1485
|
+
actor.label,
|
|
1460
1486
|
].some((value) => String(value || "").toLowerCase().includes(query));
|
|
1461
1487
|
});
|
|
1462
1488
|
const uniqueTools = new Set(ACTIVITY.map((event) => event.address)).size;
|
|
@@ -1480,13 +1506,23 @@ function renderActivity() {
|
|
|
1480
1506
|
? " · " + esc(event.attempts) + " attempts"
|
|
1481
1507
|
: "";
|
|
1482
1508
|
const errorCopy = event.errorCode ? " · " + esc(event.errorCode) : "";
|
|
1509
|
+
const actorId = event.actor?.id
|
|
1510
|
+
? (event.actor.namespace
|
|
1511
|
+
? event.actor.namespace + " · " + event.actor.id
|
|
1512
|
+
: event.actor.id)
|
|
1513
|
+
: "";
|
|
1514
|
+
const stableActorId = actorId &&
|
|
1515
|
+
(event.actor?.label || event.actor?.namespace)
|
|
1516
|
+
? '<div class="activity-actor-id mono">' + esc(actorId) + "</div>"
|
|
1517
|
+
: "";
|
|
1483
1518
|
item.innerHTML =
|
|
1484
1519
|
'<div class="activity-stamp"><span class="dot ' +
|
|
1485
1520
|
(outcomeClass === "success" ? "ok" : "") +
|
|
1486
1521
|
'" aria-hidden="true"></span><div><time class="activity-time" datetime="' +
|
|
1487
1522
|
esc(event.occurredAt) + '">' +
|
|
1488
1523
|
esc(formatDate(event.occurredAt)) +
|
|
1489
|
-
'</time><div class="activity-actor">' + esc(actorLabel(event.actor)) +
|
|
1524
|
+
'</time><div class="activity-actor">' + esc(actorLabel(event.actor)) +
|
|
1525
|
+
"</div>" + stableActorId + "</div></div>" +
|
|
1490
1526
|
'<div><div class="activity-address">' + esc(event.address) +
|
|
1491
1527
|
'</div><div class="activity-detail">' + esc(event.source) + retryCopy +
|
|
1492
1528
|
errorCopy + '</div></div>' +
|
|
@@ -1680,6 +1716,20 @@ function renderConnections() {
|
|
|
1680
1716
|
: '<p class="connector-auth meta">Authorization URL: ' +
|
|
1681
1717
|
esc(c.authorizationUrl) + "</p>";
|
|
1682
1718
|
}
|
|
1719
|
+
if (c.oauth && DATA.oauthManagement) {
|
|
1720
|
+
const oauthName = c.title || c.id;
|
|
1721
|
+
head += '<div class="credential-actions">';
|
|
1722
|
+
head += '<button type="button" class="linklike danger" ' +
|
|
1723
|
+
'aria-label="Disconnect OAuth for ' + esc(oauthName) + '" ' +
|
|
1724
|
+
'data-oauth-action="disconnect" data-connector="' + esc(c.id) +
|
|
1725
|
+
'">Disconnect OAuth</button>';
|
|
1726
|
+
head += '<button type="button" class="linklike" ' +
|
|
1727
|
+
'aria-label="' + (c.status === "ok" ? "Reconnect OAuth for " :
|
|
1728
|
+
"Restart authorization for ") + esc(oauthName) + '" ' +
|
|
1729
|
+
'data-oauth-action="reconnect" data-connector="' + esc(c.id) +
|
|
1730
|
+
'">' + (c.status === "ok" ? "Reconnect OAuth" : "Restart authorization") +
|
|
1731
|
+
"</button></div>";
|
|
1732
|
+
}
|
|
1683
1733
|
if (c.credential) {
|
|
1684
1734
|
head += '<p class="connector-auth"><a class="linklike" href="/credentials" ' +
|
|
1685
1735
|
'data-operator-page="credentials">Manage credential →</a></p>';
|
|
@@ -1709,6 +1759,77 @@ function renderConnections() {
|
|
|
1709
1759
|
renderToolkits();
|
|
1710
1760
|
}
|
|
1711
1761
|
|
|
1762
|
+
async function oauthRequest(connector, method, generation) {
|
|
1763
|
+
const token = await sessionToken();
|
|
1764
|
+
if (generation !== SESSION_GENERATION) {
|
|
1765
|
+
throw new Error("The operator session changed.");
|
|
1766
|
+
}
|
|
1767
|
+
if (!token) throw new Error("Your Clerk session has expired.");
|
|
1768
|
+
const res = await fetch("/ui/oauth/" + encodeURIComponent(connector), {
|
|
1769
|
+
method,
|
|
1770
|
+
headers: { Authorization: "Bearer " + token },
|
|
1771
|
+
});
|
|
1772
|
+
if (res.status === 204) return null;
|
|
1773
|
+
let payload = {};
|
|
1774
|
+
try { payload = await res.json(); } catch (e) {}
|
|
1775
|
+
if (!res.ok) throw new Error(payload.error || "Request failed (" + res.status + ").");
|
|
1776
|
+
return payload;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
$("list").onclick = async (event) => {
|
|
1780
|
+
const button = event.target.closest("[data-oauth-action]");
|
|
1781
|
+
if (!button) return;
|
|
1782
|
+
const connector = button.dataset.connector;
|
|
1783
|
+
const action = button.dataset.oauthAction;
|
|
1784
|
+
const generation = SESSION_GENERATION;
|
|
1785
|
+
const question = action === "disconnect"
|
|
1786
|
+
? "Disconnect OAuth for " + connector +
|
|
1787
|
+
"? Stored credentials and any pending authorization will be removed."
|
|
1788
|
+
: "Restart OAuth for " + connector +
|
|
1789
|
+
"? Stored credentials and any pending authorization will be replaced.";
|
|
1790
|
+
if (!window.confirm(question)) return;
|
|
1791
|
+
|
|
1792
|
+
setOauthNotice("");
|
|
1793
|
+
const buttons = [...document.querySelectorAll(
|
|
1794
|
+
'[data-connector="' + CSS.escape(connector) + '"]'
|
|
1795
|
+
)];
|
|
1796
|
+
buttons.forEach((item) => { item.disabled = true; });
|
|
1797
|
+
try {
|
|
1798
|
+
const result = await oauthRequest(
|
|
1799
|
+
connector,
|
|
1800
|
+
action === "disconnect" ? "DELETE" : "POST",
|
|
1801
|
+
generation,
|
|
1802
|
+
);
|
|
1803
|
+
if (generation !== SESSION_GENERATION) return;
|
|
1804
|
+
await load();
|
|
1805
|
+
if (generation !== SESSION_GENERATION) return;
|
|
1806
|
+
setOauthNotice(
|
|
1807
|
+
action === "disconnect"
|
|
1808
|
+
? "OAuth disconnected. Restart authorization when you are ready to reconnect."
|
|
1809
|
+
: result?.message ||
|
|
1810
|
+
"Authorization restarted. Open the authorization link to reconnect.",
|
|
1811
|
+
);
|
|
1812
|
+
$("oauthNotice").focus();
|
|
1813
|
+
} catch (e) {
|
|
1814
|
+
if (generation !== SESSION_GENERATION) return;
|
|
1815
|
+
// A reset can publish its durable epoch before best-effort physical cleanup
|
|
1816
|
+
// reports failure. Refresh so stale tools/health/actions never survive that
|
|
1817
|
+
// partial success, while preserving the original error for the operator.
|
|
1818
|
+
try {
|
|
1819
|
+
await load();
|
|
1820
|
+
} catch (refreshError) {
|
|
1821
|
+
// The mutation error is the actionable one; load() already owns its gate.
|
|
1822
|
+
}
|
|
1823
|
+
if (generation !== SESSION_GENERATION) return;
|
|
1824
|
+
setOauthNotice(e.message || "OAuth action failed.", true);
|
|
1825
|
+
$("oauthNotice").focus();
|
|
1826
|
+
} finally {
|
|
1827
|
+
if (generation === SESSION_GENERATION) {
|
|
1828
|
+
buttons.forEach((item) => { item.disabled = false; });
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
};
|
|
1832
|
+
|
|
1712
1833
|
function renderCredentials() {
|
|
1713
1834
|
const list = $("credentialList");
|
|
1714
1835
|
list.innerHTML = "";
|
package/src/version.ts
CHANGED