@zackbart/connecta 0.10.0 → 0.10.2

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 (123) hide show
  1. package/AGENTS.md +113 -0
  2. package/CHANGELOG.md +83 -0
  3. package/README.md +62 -12
  4. package/bin/connecta.mjs +272 -0
  5. package/dist/access-tokens.d.ts +32 -0
  6. package/dist/access-tokens.d.ts.map +1 -0
  7. package/dist/access-tokens.js +225 -0
  8. package/dist/access-tokens.js.map +1 -0
  9. package/dist/catalog-service.d.ts +39 -1
  10. package/dist/catalog-service.d.ts.map +1 -1
  11. package/dist/catalog-service.js +133 -11
  12. package/dist/catalog-service.js.map +1 -1
  13. package/dist/catalog.d.ts +17 -0
  14. package/dist/catalog.d.ts.map +1 -1
  15. package/dist/catalog.js +113 -13
  16. package/dist/catalog.js.map +1 -1
  17. package/dist/execute.d.ts +45 -1
  18. package/dist/execute.d.ts.map +1 -1
  19. package/dist/execute.js +265 -68
  20. package/dist/execute.js.map +1 -1
  21. package/dist/index.d.ts +11 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +12 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/invocation.d.ts.map +1 -1
  26. package/dist/invocation.js +1 -5
  27. package/dist/invocation.js.map +1 -1
  28. package/dist/meta-tools.d.ts +1 -0
  29. package/dist/meta-tools.d.ts.map +1 -1
  30. package/dist/meta-tools.js +410 -12
  31. package/dist/meta-tools.js.map +1 -1
  32. package/dist/operator-ui/generated.d.ts +2 -2
  33. package/dist/operator-ui/generated.d.ts.map +1 -1
  34. package/dist/operator-ui/generated.js +2 -2
  35. package/dist/operator-ui/generated.js.map +1 -1
  36. package/dist/operator-ui/model.d.ts +2 -0
  37. package/dist/operator-ui/model.d.ts.map +1 -1
  38. package/dist/operator-ui/model.js.map +1 -1
  39. package/dist/routes/access-tokens.d.ts +7 -0
  40. package/dist/routes/access-tokens.d.ts.map +1 -0
  41. package/dist/routes/access-tokens.js +84 -0
  42. package/dist/routes/access-tokens.js.map +1 -0
  43. package/dist/routes/shared.d.ts +3 -0
  44. package/dist/routes/shared.d.ts.map +1 -1
  45. package/dist/routes/shared.js.map +1 -1
  46. package/dist/routes/ui.d.ts.map +1 -1
  47. package/dist/routes/ui.js +9 -1
  48. package/dist/routes/ui.js.map +1 -1
  49. package/dist/server.d.ts.map +1 -1
  50. package/dist/server.js +5 -0
  51. package/dist/server.js.map +1 -1
  52. package/dist/skills.d.ts +1 -1
  53. package/dist/skills.d.ts.map +1 -1
  54. package/dist/skills.js +1 -1
  55. package/dist/storage/file.d.ts.map +1 -1
  56. package/dist/storage/file.js +5 -0
  57. package/dist/storage/file.js.map +1 -1
  58. package/dist/storage/memory.d.ts.map +1 -1
  59. package/dist/storage/memory.js +8 -0
  60. package/dist/storage/memory.js.map +1 -1
  61. package/dist/tool-safety.d.ts +10 -0
  62. package/dist/tool-safety.d.ts.map +1 -0
  63. package/dist/tool-safety.js +12 -0
  64. package/dist/tool-safety.js.map +1 -0
  65. package/dist/types.d.ts +5 -0
  66. package/dist/types.d.ts.map +1 -1
  67. package/dist/ui.d.ts +4 -4
  68. package/dist/ui.d.ts.map +1 -1
  69. package/dist/ui.js +44 -1
  70. package/dist/ui.js.map +1 -1
  71. package/dist/version.d.ts +1 -1
  72. package/dist/version.js +1 -1
  73. package/documentation/architecture.md +7 -0
  74. package/documentation/auth.md +58 -0
  75. package/documentation/call-admission.md +7 -0
  76. package/documentation/code-first-exploration.md +292 -0
  77. package/documentation/code-mode.md +697 -0
  78. package/documentation/connector-guides.md +7 -0
  79. package/documentation/connectors.md +63 -0
  80. package/documentation/mcp-2026-07-28.md +46 -0
  81. package/documentation/meta-tools.md +167 -0
  82. package/documentation/operations.md +7 -0
  83. package/documentation/operator-ui.md +7 -0
  84. package/documentation/request-admission.md +7 -0
  85. package/documentation/storage-and-credentials.md +54 -0
  86. package/ethos.md +132 -0
  87. package/examples/node/README.md +53 -0
  88. package/examples/node/src/index.ts +73 -0
  89. package/examples/worker/README.md +160 -0
  90. package/examples/worker/src/cloudflare-kv.ts +43 -0
  91. package/examples/worker/src/d1-activity-row.ts +100 -0
  92. package/examples/worker/src/d1-activity.ts +144 -0
  93. package/examples/worker/src/index.ts +136 -0
  94. package/examples/worker/wrangler.jsonc +26 -0
  95. package/package.json +11 -1
  96. package/src/access-tokens.ts +289 -0
  97. package/src/catalog-service.ts +177 -15
  98. package/src/catalog.ts +143 -12
  99. package/src/execute.ts +372 -96
  100. package/src/index.ts +33 -1
  101. package/src/invocation.ts +1 -8
  102. package/src/meta-tools.ts +504 -11
  103. package/src/operator-ui/browser.css +63 -0
  104. package/src/operator-ui/browser.ts +288 -2
  105. package/src/operator-ui/generated.ts +2 -2
  106. package/src/operator-ui/model.ts +6 -0
  107. package/src/routes/access-tokens.ts +115 -0
  108. package/src/routes/shared.ts +3 -0
  109. package/src/routes/ui.ts +9 -0
  110. package/src/server.ts +5 -0
  111. package/src/skills.ts +1 -1
  112. package/src/storage/file.ts +5 -0
  113. package/src/storage/memory.ts +8 -0
  114. package/src/tool-safety.ts +15 -0
  115. package/src/types.ts +5 -0
  116. package/src/ui.ts +50 -1
  117. package/src/version.ts +1 -1
  118. package/templates/node/.env.example +5 -0
  119. package/templates/node/AGENTS.md +19 -0
  120. package/templates/node/README.md +33 -0
  121. package/templates/node/package.json +23 -0
  122. package/templates/node/src/index.ts +43 -0
  123. package/templates/node/tsconfig.json +12 -0
@@ -1,10 +1,11 @@
1
1
  import {
2
+ type AccessTokenManagementCapability,
2
3
  filterUiConnectors,
3
4
  type CredentialManagementCapability,
4
5
  type UiData,
5
6
  } from "./model.js";
6
7
 
7
- type OperatorPage = "connections" | "credentials" | "activity";
8
+ type OperatorPage = "connections" | "credentials" | "tokens" | "activity";
8
9
 
9
10
  interface BrowserAuth {
10
11
  kind: string;
@@ -69,6 +70,17 @@ interface UiActionResponse {
69
70
  ok?: boolean;
70
71
  message?: string;
71
72
  error?: string;
73
+ token?: string;
74
+ accessToken?: UiAccessToken;
75
+ accessTokens?: UiAccessToken[];
76
+ }
77
+
78
+ interface UiAccessToken {
79
+ id: string;
80
+ name: string;
81
+ tokenPrefix: string;
82
+ createdAt: string;
83
+ revokedAt?: string;
72
84
  }
73
85
 
74
86
  interface OperatorElement extends HTMLElement {
@@ -99,12 +111,15 @@ const $ = (id: string): OperatorElement => {
99
111
  const PAGE_META = {
100
112
  connections: { path: "/", label: "Connections" },
101
113
  credentials: { path: "/credentials", label: "Credentials" },
114
+ tokens: { path: "/tokens", label: "Access tokens" },
102
115
  activity: { path: "/activity", label: "Activity" },
103
116
  } satisfies Record<OperatorPage, { path: string; label: string }>;
104
117
  let DATA: UiData | null = null;
105
118
  let ACTIVITY: UiActivityEvent[] = [];
106
119
  let ACTIVITY_CURSOR: string | null = null;
107
120
  let ACTIVITY_LOADED = false;
121
+ let ACCESS_TOKENS: UiAccessToken[] = [];
122
+ let ACCESS_TOKENS_LOADED = false;
108
123
  let CURRENT_PAGE: OperatorPage = INITIAL_PAGE;
109
124
  let SESSION_GENERATION = 0;
110
125
  let ACTIVITY_GENERATION = 0;
@@ -194,9 +209,20 @@ function clearIdentityState(): void {
194
209
  $("credentialUnavailable").textContent = "";
195
210
  $("credentialUnavailable").classList.add("hidden");
196
211
  $("credentialList").classList.add("hidden");
212
+ ACCESS_TOKENS = [];
213
+ ACCESS_TOKENS_LOADED = false;
214
+ $("tokenList").innerHTML = "";
215
+ $("tokenList").setAttribute("aria-busy", "false");
216
+ $("tokenNotice").textContent = "";
217
+ clearCreatedAccessToken();
218
+ $("tokenName").value = "";
219
+ $("tokenUnavailable").textContent = "";
220
+ $("tokenUnavailable").classList.add("hidden");
221
+ $("tokenAvailable").classList.add("hidden");
197
222
  $("activityUnavailable").classList.add("hidden");
198
223
  $("activityAvailable").classList.add("hidden");
199
224
  $("credentialsNav").classList.add("hidden");
225
+ $("tokensNav").classList.add("hidden");
200
226
  $("activityNav").classList.add("hidden");
201
227
  $("serverInfo").textContent = PRODUCT_OPERATOR_LABEL;
202
228
  }
@@ -221,6 +247,7 @@ function showGate(msg: string): void {
221
247
 
222
248
  function pageForPath(path: string): OperatorPage {
223
249
  if (path === "/credentials") return "credentials";
250
+ if (path === "/tokens") return "tokens";
224
251
  if (path === "/activity") return "activity";
225
252
  return "connections";
226
253
  }
@@ -237,16 +264,43 @@ function credentialUnavailableCopy(
237
264
  return "Credential management requires an eligible Clerk operator. Bearer-authenticated sessions can inspect connections but cannot manage stored credentials.";
238
265
  }
239
266
 
267
+ function accessTokenUnavailableCopy(
268
+ capability?: AccessTokenManagementCapability,
269
+ ): string {
270
+ if (capability === "not_configured") {
271
+ return "Access tokens are not configured for this deployment. Add accessTokens to the deployment configuration to enable them.";
272
+ }
273
+ return "Access token management requires an eligible Clerk operator. A Bearer token can connect to MCP, but it cannot create or revoke other tokens.";
274
+ }
275
+
276
+ function setTokenNotice(message: string, error = false): void {
277
+ $("tokenNotice").textContent = message;
278
+ $("tokenNotice").setAttribute("role", error ? "alert" : "status");
279
+ }
280
+
281
+ function clearCreatedAccessToken(): void {
282
+ $("createdToken").textContent = "";
283
+ $("tokenReveal").classList.add("hidden");
284
+ $("tokenCreateForm").classList.remove("hidden");
285
+ }
286
+
240
287
  function updateCapabilities(): void {
241
288
  const credentialsAvailable =
242
289
  DATA?.credentialManagement === "available";
243
290
  $("credentialsNav").classList.toggle("hidden", !credentialsAvailable);
291
+ const tokensAvailable = DATA?.accessTokenManagement === "available";
292
+ $("tokensNav").classList.toggle("hidden", !tokensAvailable);
244
293
  $("activityNav").classList.toggle("hidden", !DATA?.activityEnabled);
245
294
  $("credentialUnavailable").classList.toggle("hidden", credentialsAvailable);
246
295
  $("credentialList").classList.toggle("hidden", !credentialsAvailable);
247
296
  $("credentialUnavailable").textContent = credentialsAvailable
248
297
  ? ""
249
298
  : credentialUnavailableCopy(DATA?.credentialManagement);
299
+ $("tokenUnavailable").classList.toggle("hidden", Boolean(tokensAvailable));
300
+ $("tokenAvailable").classList.toggle("hidden", !tokensAvailable);
301
+ $("tokenUnavailable").textContent = tokensAvailable
302
+ ? ""
303
+ : accessTokenUnavailableCopy(DATA?.accessTokenManagement);
250
304
  $("activityUnavailable").classList.toggle("hidden", Boolean(DATA?.activityEnabled));
251
305
  $("activityAvailable").classList.toggle("hidden", !DATA?.activityEnabled);
252
306
  }
@@ -269,6 +323,13 @@ function activatePage(
269
323
  updateCapabilities();
270
324
  if (next === "connections") renderConnections();
271
325
  if (next === "credentials") renderCredentials();
326
+ if (
327
+ next === "tokens" &&
328
+ DATA.accessTokenManagement === "available" &&
329
+ !ACCESS_TOKENS_LOADED
330
+ ) {
331
+ void loadAccessTokens();
332
+ }
272
333
  if (next === "activity" && DATA.activityEnabled && !ACTIVITY_LOADED) {
273
334
  loadActivity(true);
274
335
  }
@@ -277,6 +338,7 @@ function activatePage(
277
338
  $("credentialList").innerHTML = "";
278
339
  setNotice("");
279
340
  }
341
+ if (next !== "tokens") clearCreatedAccessToken();
280
342
  // Focus what is actually on screen. While gated the page views are hidden, so
281
343
  // focusing their heading is a silent no-op that drops focus to <body> and
282
344
  // restarts the next Tab from the top of the document — the gate's own h1 is
@@ -569,7 +631,7 @@ function renderConnections(): void {
569
631
 
570
632
  async function operatorRequest(
571
633
  path: string,
572
- method: "DELETE" | "POST" | "PUT",
634
+ method: "DELETE" | "GET" | "POST" | "PUT",
573
635
  generation: number,
574
636
  body?: object,
575
637
  ): Promise<UiActionResponse | null> {
@@ -597,6 +659,226 @@ async function operatorRequest(
597
659
  return payload;
598
660
  }
599
661
 
662
+ function renderAccessTokens(): void {
663
+ const list = $("tokenList");
664
+ list.innerHTML = "";
665
+ if (!ACCESS_TOKENS.length) {
666
+ list.innerHTML =
667
+ '<p class="empty">No access tokens yet. Name the first MCP client above.</p>';
668
+ return;
669
+ }
670
+ for (const token of ACCESS_TOKENS) {
671
+ const revoked = Boolean(token.revokedAt);
672
+ const item = document.createElement("section");
673
+ item.className = "token-card" + (revoked ? " revoked" : "");
674
+ item.setAttribute("aria-labelledby", "access-token-" + token.id);
675
+ item.innerHTML =
676
+ '<div class="token-card-head"><div><h2 id="access-token-' +
677
+ esc(token.id) + '">' + esc(token.name) +
678
+ '</h2><p class="mono">' + esc(token.tokenPrefix) +
679
+ '…</p></div><div class="cap">' +
680
+ (revoked
681
+ ? "Revoked " + esc(formatDate(token.revokedAt!))
682
+ : "Created " + esc(formatDate(token.createdAt))) +
683
+ "</div></div>" +
684
+ '<div class="credential-actions">' +
685
+ '<button class="linklike" type="button" data-token-action="rename" data-token-id="' +
686
+ esc(token.id) + '">Rename</button>' +
687
+ (revoked
688
+ ? ""
689
+ : '<button class="linklike danger" type="button" data-token-action="revoke" data-token-id="' +
690
+ esc(token.id) + '">Revoke</button>') +
691
+ "</div>" +
692
+ '<form class="credential-form hidden" data-token-form="' +
693
+ esc(token.id) + '"><label class="visually-hidden" for="token-name-' +
694
+ esc(token.id) + '">Token name</label><input id="token-name-' +
695
+ esc(token.id) + '" type="text" maxlength="80" value="' +
696
+ esc(token.name) + '" autocomplete="off">' +
697
+ '<button class="linklike" type="submit" data-token-action="save-name" data-token-id="' +
698
+ esc(token.id) + '">Save name</button>' +
699
+ '<button class="linklike" type="button" data-token-action="cancel-name" data-token-id="' +
700
+ esc(token.id) + '">Cancel</button></form>';
701
+ list.appendChild(item);
702
+ }
703
+ }
704
+
705
+ async function loadAccessTokens(): Promise<void> {
706
+ const generation = SESSION_GENERATION;
707
+ $("tokenList").setAttribute("aria-busy", "true");
708
+ setTokenNotice("Loading access tokens…");
709
+ try {
710
+ const payload = await operatorRequest(
711
+ "/ui/access-tokens",
712
+ "GET",
713
+ generation,
714
+ );
715
+ if (generation !== SESSION_GENERATION) return;
716
+ ACCESS_TOKENS = payload?.accessTokens || [];
717
+ ACCESS_TOKENS_LOADED = true;
718
+ setTokenNotice("");
719
+ renderAccessTokens();
720
+ } catch (error) {
721
+ if (generation !== SESSION_GENERATION) return;
722
+ setTokenNotice(errorMessage(
723
+ error,
724
+ "Access tokens could not be loaded.",
725
+ ), true);
726
+ } finally {
727
+ if (generation === SESSION_GENERATION) {
728
+ $("tokenList").setAttribute("aria-busy", "false");
729
+ }
730
+ }
731
+ }
732
+
733
+ $("tokenCreateForm").onsubmit = async (event) => {
734
+ event.preventDefault();
735
+ const name = $("tokenName").value.trim();
736
+ if (!name) {
737
+ setTokenNotice("Name the MCP client before creating a token.", true);
738
+ return;
739
+ }
740
+ const generation = SESSION_GENERATION;
741
+ $("createToken").disabled = true;
742
+ setTokenNotice("");
743
+ try {
744
+ const payload = await operatorRequest(
745
+ "/ui/access-tokens",
746
+ "POST",
747
+ generation,
748
+ { name },
749
+ );
750
+ if (generation !== SESSION_GENERATION) return;
751
+ if (!payload?.token || !payload.accessToken) {
752
+ throw new Error("The created token was not returned.");
753
+ }
754
+ ACCESS_TOKENS = [
755
+ payload.accessToken,
756
+ ...ACCESS_TOKENS.filter((token) => token.id !== payload.accessToken!.id),
757
+ ];
758
+ ACCESS_TOKENS_LOADED = true;
759
+ $("tokenName").value = "";
760
+ $("createdToken").textContent = payload.token;
761
+ $("tokenCreateForm").classList.add("hidden");
762
+ $("tokenReveal").classList.remove("hidden");
763
+ setTokenNotice("Access token created.");
764
+ renderAccessTokens();
765
+ $("tokenRevealHeading").focus();
766
+ } catch (error) {
767
+ if (generation !== SESSION_GENERATION) return;
768
+ setTokenNotice(errorMessage(
769
+ error,
770
+ "Access token could not be created.",
771
+ ), true);
772
+ $("tokenNotice").focus();
773
+ } finally {
774
+ if (generation === SESSION_GENERATION) $("createToken").disabled = false;
775
+ }
776
+ };
777
+
778
+ $("copyCreatedToken").onclick = async () => {
779
+ const button = $("copyCreatedToken");
780
+ const token = $("createdToken").textContent || "";
781
+ try {
782
+ await navigator.clipboard.writeText(token);
783
+ button.textContent = "Copied";
784
+ } catch {
785
+ button.textContent = "Copy failed";
786
+ }
787
+ window.setTimeout(() => { button.textContent = "Copy token"; }, 1600);
788
+ };
789
+
790
+ $("dismissCreatedToken").onclick = () => {
791
+ clearCreatedAccessToken();
792
+ $("tokenName").focus();
793
+ };
794
+
795
+ $("tokenList").onclick = async (event) => {
796
+ const button = closestElement<HTMLButtonElement>(
797
+ event.target,
798
+ "[data-token-action]",
799
+ );
800
+ if (!button) return;
801
+ const id = button.dataset.tokenId;
802
+ const action = button.dataset.tokenAction;
803
+ if (!id || !action) return;
804
+ const form = document.querySelector<HTMLFormElement>(
805
+ '[data-token-form="' + CSS.escape(id) + '"]',
806
+ );
807
+ if (!form) return;
808
+ if (action === "rename") {
809
+ form.classList.remove("hidden");
810
+ form.querySelector("input")?.focus();
811
+ return;
812
+ }
813
+ if (action === "cancel-name") {
814
+ const current = ACCESS_TOKENS.find((token) => token.id === id);
815
+ const input = form.querySelector<HTMLInputElement>("input");
816
+ if (input && current) input.value = current.name;
817
+ form.classList.add("hidden");
818
+ return;
819
+ }
820
+ if (action !== "revoke") return;
821
+ const named = ACCESS_TOKENS.find((token) => token.id === id);
822
+ if (!window.confirm(
823
+ "Revoke " + (named?.name || "this access token") +
824
+ "? Its MCP client will immediately lose access."
825
+ )) return;
826
+ const generation = SESSION_GENERATION;
827
+ try {
828
+ const payload = await operatorRequest(
829
+ "/ui/access-tokens/" + encodeURIComponent(id),
830
+ "DELETE",
831
+ generation,
832
+ );
833
+ if (generation !== SESSION_GENERATION) return;
834
+ if (payload?.accessToken) {
835
+ ACCESS_TOKENS = ACCESS_TOKENS.map((token) =>
836
+ token.id === id ? payload.accessToken! : token);
837
+ }
838
+ setTokenNotice("Access token revoked.");
839
+ renderAccessTokens();
840
+ $("tokenNotice").focus();
841
+ } catch (error) {
842
+ if (generation !== SESSION_GENERATION) return;
843
+ setTokenNotice(errorMessage(
844
+ error,
845
+ "Access token could not be revoked.",
846
+ ), true);
847
+ }
848
+ };
849
+
850
+ $("tokenList").onsubmit = async (event) => {
851
+ event.preventDefault();
852
+ const form = closestElement<HTMLFormElement>(event.target, "[data-token-form]");
853
+ if (!form) return;
854
+ const id = form.dataset.tokenForm;
855
+ const name = form.querySelector<HTMLInputElement>("input")?.value.trim();
856
+ if (!id || !name) return;
857
+ const generation = SESSION_GENERATION;
858
+ try {
859
+ const payload = await operatorRequest(
860
+ "/ui/access-tokens/" + encodeURIComponent(id),
861
+ "PUT",
862
+ generation,
863
+ { name },
864
+ );
865
+ if (generation !== SESSION_GENERATION) return;
866
+ if (payload?.accessToken) {
867
+ ACCESS_TOKENS = ACCESS_TOKENS.map((token) =>
868
+ token.id === id ? payload.accessToken! : token);
869
+ }
870
+ setTokenNotice("Access token renamed.");
871
+ renderAccessTokens();
872
+ $("tokenNotice").focus();
873
+ } catch (error) {
874
+ if (generation !== SESSION_GENERATION) return;
875
+ setTokenNotice(errorMessage(
876
+ error,
877
+ "Access token could not be renamed.",
878
+ ), true);
879
+ }
880
+ };
881
+
600
882
  $("list").onclick = async (event) => {
601
883
  const button = closestElement<HTMLButtonElement>(
602
884
  event.target,
@@ -931,6 +1213,7 @@ document.addEventListener("click", (event) => {
931
1213
  if (
932
1214
  page !== "connections" &&
933
1215
  page !== "credentials" &&
1216
+ page !== "tokens" &&
934
1217
  page !== "activity"
935
1218
  ) return;
936
1219
  event.preventDefault();
@@ -939,6 +1222,9 @@ document.addEventListener("click", (event) => {
939
1222
  window.addEventListener("popstate", () => {
940
1223
  activatePage(pageForPath(window.location.pathname), { focus: true });
941
1224
  });
1225
+ // A document restored from the back-forward cache must not restore a
1226
+ // one-time secret that was visible when the operator left the page.
1227
+ window.addEventListener("pagehide", clearCreatedAccessToken);
942
1228
 
943
1229
  async function init(): Promise<void> {
944
1230
  if (AUTH.kind === "clerk") {