@zackbart/connecta 0.7.1 → 0.7.3

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 (54) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +2 -1
  3. package/dist/connectors/remote-mcp.d.ts.map +1 -1
  4. package/dist/connectors/remote-mcp.js +117 -79
  5. package/dist/connectors/remote-mcp.js.map +1 -1
  6. package/dist/credential-health.d.ts +8 -5
  7. package/dist/credential-health.d.ts.map +1 -1
  8. package/dist/credential-health.js +20 -13
  9. package/dist/credential-health.js.map +1 -1
  10. package/dist/executors/quickjs.d.ts.map +1 -1
  11. package/dist/executors/quickjs.js +57 -5
  12. package/dist/executors/quickjs.js.map +1 -1
  13. package/dist/index.d.ts +2 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/meta-tools.d.ts.map +1 -1
  17. package/dist/meta-tools.js +47 -11
  18. package/dist/meta-tools.js.map +1 -1
  19. package/dist/registry.d.ts +18 -22
  20. package/dist/registry.d.ts.map +1 -1
  21. package/dist/registry.js +33 -21
  22. package/dist/registry.js.map +1 -1
  23. package/dist/server.d.ts.map +1 -1
  24. package/dist/server.js +9 -6
  25. package/dist/server.js.map +1 -1
  26. package/dist/timeout.d.ts +9 -4
  27. package/dist/timeout.d.ts.map +1 -1
  28. package/dist/timeout.js +34 -4
  29. package/dist/timeout.js.map +1 -1
  30. package/dist/toolkits.d.ts +8 -0
  31. package/dist/toolkits.d.ts.map +1 -1
  32. package/dist/toolkits.js +3 -0
  33. package/dist/toolkits.js.map +1 -1
  34. package/dist/types.d.ts +2 -2
  35. package/dist/types.d.ts.map +1 -1
  36. package/dist/ui.d.ts +12 -1
  37. package/dist/ui.d.ts.map +1 -1
  38. package/dist/ui.js +187 -6
  39. package/dist/ui.js.map +1 -1
  40. package/dist/version.d.ts +1 -1
  41. package/dist/version.js +1 -1
  42. package/package.json +1 -1
  43. package/src/connectors/remote-mcp.ts +128 -83
  44. package/src/credential-health.ts +20 -18
  45. package/src/executors/quickjs.ts +65 -5
  46. package/src/index.ts +2 -1
  47. package/src/meta-tools.ts +75 -26
  48. package/src/registry.ts +48 -20
  49. package/src/server.ts +11 -8
  50. package/src/timeout.ts +41 -4
  51. package/src/toolkits.ts +11 -0
  52. package/src/types.ts +2 -2
  53. package/src/ui.ts +212 -11
  54. package/src/version.ts +1 -1
package/src/ui.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  import type { CredentialVault } from "./credentials.js";
7
7
  import { closeConnectorScope } from "./connector-scope.js";
8
8
  import type { Registry } from "./registry.js";
9
+ import type { Toolkit } from "./toolkits.js";
9
10
  import type { ConnectaBranding, UiAuthConfig } from "./types.js";
10
11
 
11
12
  /** Connecta's default monochrome "C" mark. */
@@ -296,10 +297,21 @@ export interface UiConnector {
296
297
  export interface UiData {
297
298
  serverInfo: { name: string; version: string };
298
299
  connectors: UiConnector[];
300
+ /** Read-only projection of the validated deployment config. */
301
+ toolkits: UiToolkit[];
299
302
  activityEnabled: boolean;
300
303
  credentialManagement: CredentialManagementCapability;
301
304
  }
302
305
 
306
+ export interface UiToolkit {
307
+ name: string;
308
+ connectors: string[];
309
+ includeTools: string[];
310
+ excludeTools: string[];
311
+ /** Tools currently loaded through healthy connectors and visible in this view. */
312
+ toolCount: number;
313
+ }
314
+
303
315
  export type CredentialManagementCapability =
304
316
  | "available"
305
317
  | "requires_clerk"
@@ -391,10 +403,11 @@ export async function buildUiData(
391
403
  credentialManagement: CredentialManagementCapability = credentialVault
392
404
  ? "available"
393
405
  : "requires_clerk",
406
+ toolkits?: ReadonlyMap<string, Toolkit>,
394
407
  ): Promise<UiData> {
395
408
  const requestScope = {};
396
409
  const connectorSet = registry.listConnectors();
397
- const connectors = await Promise.all(
410
+ const settled = await Promise.allSettled(
398
411
  connectorSet.map(async (c): Promise<UiConnector> => {
399
412
  const status = await registry.statusFor(c.id, baseUrl, requestScope);
400
413
  const credentialCheck = await registry.credentialHealthFor(c.id);
@@ -530,19 +543,41 @@ export async function buildUiData(
530
543
  ...(credential ? { credential } : {}),
531
544
  };
532
545
  }),
533
- ).finally(async () => {
534
- await Promise.all(
535
- connectorSet.map((connector) =>
536
- closeConnectorScope(
537
- connector,
538
- registry.contextFor(connector.id, baseUrl, requestScope),
539
- ),
546
+ );
547
+ await Promise.all(
548
+ connectorSet.map((connector) =>
549
+ closeConnectorScope(
550
+ connector,
551
+ registry.contextFor(connector.id, baseUrl, requestScope),
540
552
  ),
541
- );
553
+ ),
554
+ );
555
+ const connectors = settled.map((result) => {
556
+ if (result.status === "rejected") throw result.reason;
557
+ return result.value;
542
558
  });
559
+ const toolkitData: UiToolkit[] = [...(toolkits?.values() ?? [])].map(
560
+ (toolkit) => ({
561
+ name: toolkit.name,
562
+ connectors: [...toolkit.connectors],
563
+ includeTools: [...toolkit.includeTools],
564
+ excludeTools: [...toolkit.excludeTools],
565
+ toolCount: connectors.reduce(
566
+ (count, connector) =>
567
+ count +
568
+ (toolkit.hasConnector(connector.id)
569
+ ? connector.tools.filter((tool) =>
570
+ toolkit.hasTool(connector.id, tool.name),
571
+ ).length
572
+ : 0),
573
+ 0,
574
+ ),
575
+ }),
576
+ );
543
577
  return {
544
578
  serverInfo,
545
579
  connectors,
580
+ toolkits: toolkitData,
546
581
  activityEnabled,
547
582
  credentialManagement,
548
583
  };
@@ -876,6 +911,56 @@ ${clerkScript}
876
911
  .connector-description { margin-top: .25rem; max-width: 40rem; }
877
912
  .connector-message,
878
913
  .connector-auth { margin-top: .75rem; }
914
+ .connector-toolkits {
915
+ align-items: baseline;
916
+ display: flex;
917
+ flex-wrap: wrap;
918
+ gap: .25rem .5rem;
919
+ margin-top: .5rem;
920
+ }
921
+ .scope-label {
922
+ border: 1px solid var(--rule);
923
+ display: inline-block;
924
+ font-family: var(--mono);
925
+ font-size: .72rem;
926
+ padding: .05rem .35rem;
927
+ }
928
+
929
+ .toolkit-copy { margin-bottom: 1rem; max-width: 42rem; }
930
+ .toolkit-ledger { border-bottom: 1px solid var(--rule); }
931
+ .toolkit-card {
932
+ border-top: 1px solid var(--rule);
933
+ padding: .9rem 0 1rem;
934
+ }
935
+ .toolkit-head {
936
+ display: grid;
937
+ gap: var(--gap);
938
+ grid-template-columns: minmax(0, 2fr) minmax(10rem, 1fr);
939
+ }
940
+ .toolkit-name {
941
+ align-items: baseline;
942
+ display: flex;
943
+ flex-wrap: wrap;
944
+ gap: .5rem;
945
+ }
946
+ .toolkit-name h3 { overflow-wrap: anywhere; }
947
+ .toolkit-state { text-align: right; }
948
+ .toolkit-connectors {
949
+ display: flex;
950
+ flex-wrap: wrap;
951
+ gap: .5rem;
952
+ margin-top: .75rem;
953
+ }
954
+ .toolkit-connector {
955
+ border-left: 1px solid var(--ink);
956
+ display: inline-flex;
957
+ flex-wrap: wrap;
958
+ gap: .4rem;
959
+ padding-left: .5rem;
960
+ }
961
+ .toolkit-rules { margin-top: .75rem; }
962
+ .toolkit-rules > * + * { margin-top: .25rem; }
963
+ .toolkit-endpoint { margin-top: .75rem; }
879
964
 
880
965
  .credential-ledger { border-bottom: 1px solid var(--rule); }
881
966
  .credential-card { border-top: 1px solid var(--rule); padding-bottom: .75rem; padding-top: .75rem; }
@@ -991,9 +1076,11 @@ ${clerkScript}
991
1076
  .section,
992
1077
  .section + .section { margin-top: 2.5rem; }
993
1078
  .connector-head,
1079
+ .toolkit-head,
994
1080
  .tool,
995
1081
  .activity-item { grid-template-columns: 1fr; }
996
- .connector-state { text-align: left; }
1082
+ .connector-state,
1083
+ .toolkit-state { text-align: left; }
997
1084
  .credential-field { align-items: start; grid-template-columns: 1fr; gap: .25rem; }
998
1085
  input { min-height: 2.75rem; }
999
1086
  }
@@ -1068,6 +1155,16 @@ ${clerkScript}
1068
1155
  <div id="list" class="connector-tools" aria-busy="false"></div>
1069
1156
  </div>
1070
1157
  </section>
1158
+ <section class="section pgrid" aria-labelledby="toolkitLedgerHeading">
1159
+ <h2 class="pcap" id="toolkitLedgerHeading">Toolkits</h2>
1160
+ <div class="pbody">
1161
+ <p class="toolkit-copy meta">
1162
+ Read-only views from deployment config. Change the config and redeploy
1163
+ to update them.
1164
+ </p>
1165
+ <div id="toolkitList" class="toolkit-ledger"></div>
1166
+ </div>
1167
+ </section>
1071
1168
  </section>
1072
1169
 
1073
1170
  <section id="credentialsView"${page === "credentials" ? "" : ' class="hidden"'}>
@@ -1129,6 +1226,7 @@ let ACTIVITY_LOADED = false;
1129
1226
  let CURRENT_PAGE = INITIAL_PAGE;
1130
1227
  let SESSION_GENERATION = 0;
1131
1228
  let ACTIVITY_GENERATION = 0;
1229
+ let CLERK_SESSION_ID = null;
1132
1230
  $("mcpUrl").textContent = MCP_URL;
1133
1231
 
1134
1232
  function esc(s) {
@@ -1184,6 +1282,7 @@ function clearIdentityState() {
1184
1282
  DATA = null;
1185
1283
  clearActivityState();
1186
1284
  $("list").innerHTML = "";
1285
+ $("toolkitList").innerHTML = "";
1187
1286
  $("filter").value = "";
1188
1287
  $("credentialList").innerHTML = "";
1189
1288
  $("credentialList").setAttribute("aria-busy", "false");
@@ -1267,7 +1366,10 @@ function activatePage(page, options) {
1267
1366
  loadActivity(true);
1268
1367
  }
1269
1368
  }
1270
- if (next !== "credentials") $("credentialList").innerHTML = "";
1369
+ if (next !== "credentials") {
1370
+ $("credentialList").innerHTML = "";
1371
+ setNotice("");
1372
+ }
1271
1373
  // Focus what is actually on screen. While gated the page views are hidden, so
1272
1374
  // focusing their heading is a silent no-op that drops focus to <body> and
1273
1375
  // restarts the next Tab from the top of the document — the gate's own h1 is
@@ -1452,6 +1554,71 @@ async function loadActivity(reset) {
1452
1554
  }
1453
1555
  }
1454
1556
 
1557
+ function toolkitMcpUrl(name) {
1558
+ const url = new URL(MCP_URL, window.location.href);
1559
+ url.searchParams.set("toolkit", name);
1560
+ return url.toString();
1561
+ }
1562
+
1563
+ function renderToolkits() {
1564
+ const list = $("toolkitList");
1565
+ const toolkits = DATA.toolkits || [];
1566
+ list.innerHTML = "";
1567
+ if (!toolkits.length) {
1568
+ list.innerHTML =
1569
+ '<p class="empty">No toolkits are configured. Unscoped clients see the full connector registry.</p>';
1570
+ return;
1571
+ }
1572
+ const connectors = new Map(
1573
+ DATA.connectors.map((connector) => [connector.id, connector]),
1574
+ );
1575
+ for (const toolkit of toolkits) {
1576
+ const el = document.createElement("article");
1577
+ el.className = "toolkit-card";
1578
+ const connectorCount = toolkit.connectors.length;
1579
+ const toolCount = toolkit.toolCount || 0;
1580
+ let body =
1581
+ '<div class="toolkit-head"><div><div class="toolkit-name">' +
1582
+ '<h3 class="mono">' + esc(toolkit.name) + "</h3>" +
1583
+ '<span class="cap">configured view</span></div>';
1584
+ body += '</div><div class="toolkit-state cap">' +
1585
+ connectorCount + (connectorCount === 1 ? " connector" : " connectors") +
1586
+ " · " + toolCount + (toolCount === 1 ? " loaded tool" : " loaded tools") +
1587
+ "</div></div>";
1588
+ body += '<ul class="toolkit-connectors" aria-label="Included connectors">';
1589
+ for (const id of toolkit.connectors) {
1590
+ const connector = connectors.get(id);
1591
+ body += '<li class="toolkit-connector"><span>' +
1592
+ esc(connector?.title || id) + '</span><code class="mono">' +
1593
+ esc(id) + "</code></li>";
1594
+ }
1595
+ body += "</ul>";
1596
+ body += '<div class="toolkit-rules meta">';
1597
+ if (toolkit.includeTools.length) {
1598
+ body += '<p>Per-connector allowlist: <span class="mono">' +
1599
+ toolkit.includeTools.map(esc).join('</span>, <span class="mono">') +
1600
+ "</span>. Connectors without an allowlist keep all tools.</p>";
1601
+ }
1602
+ if (toolkit.excludeTools.length) {
1603
+ body += '<p>Hidden: <span class="mono">' +
1604
+ toolkit.excludeTools.map(esc).join('</span>, <span class="mono">') +
1605
+ "</span></p>";
1606
+ }
1607
+ if (!toolkit.includeTools.length && !toolkit.excludeTools.length) {
1608
+ body += "<p>All tools on the included connectors are visible.</p>";
1609
+ }
1610
+ body += "</div>";
1611
+ const endpoint = toolkitMcpUrl(toolkit.name);
1612
+ body += '<div class="endpoint toolkit-endpoint"><div class="endpoint-row">' +
1613
+ '<code class="mono">' + esc(endpoint) + "</code>" +
1614
+ '<button class="linklike" type="button" data-toolkit-copy="' +
1615
+ esc(toolkit.name) + '" aria-label="Copy endpoint for ' +
1616
+ esc(toolkit.name) + '">Copy URL</button></div></div>';
1617
+ el.innerHTML = body;
1618
+ list.appendChild(el);
1619
+ }
1620
+ }
1621
+
1455
1622
  function renderConnections() {
1456
1623
  const q = $("filter").value.trim().toLowerCase();
1457
1624
  const list = $("list");
@@ -1472,6 +1639,15 @@ function renderConnections() {
1472
1639
  if (c.description) {
1473
1640
  head += '<p class="connector-description meta">' + esc(c.description) + "</p>";
1474
1641
  }
1642
+ const memberships = (DATA.toolkits || [])
1643
+ .filter((toolkit) => toolkit.connectors.includes(c.id))
1644
+ .map((toolkit) => toolkit.name);
1645
+ if (memberships.length) {
1646
+ head += '<div class="connector-toolkits meta"><span>Toolkits</span>' +
1647
+ memberships.map((name) =>
1648
+ '<span class="scope-label">' + esc(name) + "</span>"
1649
+ ).join("") + "</div>";
1650
+ }
1475
1651
  head += '</div><div class="connector-state cap">' + esc(status) + " · " +
1476
1652
  c.toolCount + (c.toolCount === 1 ? " tool" : " tools") +
1477
1653
  '<br><span class="mono">' + esc(c.id) + "</span></div></div>";
@@ -1525,6 +1701,7 @@ function renderConnections() {
1525
1701
  : "No connectors are declared in this deployment.") +
1526
1702
  "</p>";
1527
1703
  }
1704
+ renderToolkits();
1528
1705
  }
1529
1706
 
1530
1707
  function renderCredentials() {
@@ -1771,6 +1948,19 @@ $("copyMcpUrl").onclick = async () => {
1771
1948
  }
1772
1949
  window.setTimeout(() => { button.textContent = "Copy URL"; }, 1600);
1773
1950
  };
1951
+ $("toolkitList").onclick = async (event) => {
1952
+ const button = event.target.closest("[data-toolkit-copy]");
1953
+ if (!button) return;
1954
+ try {
1955
+ await navigator.clipboard.writeText(
1956
+ toolkitMcpUrl(button.dataset.toolkitCopy),
1957
+ );
1958
+ button.textContent = "Copied";
1959
+ } catch (e) {
1960
+ button.textContent = "Copy failed";
1961
+ }
1962
+ window.setTimeout(() => { button.textContent = "Copy URL"; }, 1600);
1963
+ };
1774
1964
  $("signin").onclick = () => Clerk.redirectToSignIn({
1775
1965
  signInFallbackRedirectUrl: window.location.href,
1776
1966
  signUpFallbackRedirectUrl: window.location.href,
@@ -1820,6 +2010,17 @@ async function init() {
1820
2010
  signUpFallbackRedirectUrl: window.location.href,
1821
2011
  afterSignOutUrl: window.location.href,
1822
2012
  });
2013
+ CLERK_SESSION_ID = Clerk.session?.id ?? null;
2014
+ Clerk.addListener((resources) => {
2015
+ const nextSessionId = resources.session?.id ?? null;
2016
+ if (nextSessionId === CLERK_SESSION_ID) return;
2017
+ CLERK_SESSION_ID = nextSessionId;
2018
+ // Clerk has already updated its public session before notifying
2019
+ // listeners. Clear synchronously so stale identity-scoped data cannot
2020
+ // be repainted while the replacement identity is being fetched.
2021
+ showGate("");
2022
+ void load();
2023
+ });
1823
2024
  } catch (e) {
1824
2025
  return showGate("Clerk could not initialize: " + e.message);
1825
2026
  }
package/src/version.ts CHANGED
@@ -4,4 +4,4 @@
4
4
  * a bump that forgets this file fails the build rather than shipping a stale
5
5
  * version to `/health` and to downstream MCP handshakes.
6
6
  */
7
- export const CONNECTA_VERSION = "0.7.1";
7
+ export const CONNECTA_VERSION = "0.7.3";