@snagset/server 0.1.2 → 0.1.4

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 (140) hide show
  1. package/dist/app.js +45 -6
  2. package/dist/app.js.map +1 -1
  3. package/dist/bin.js +1 -1
  4. package/dist/db/scope.js +23 -0
  5. package/dist/db/scope.js.map +1 -0
  6. package/dist/instance/mcpHttp.js +42 -0
  7. package/dist/instance/mcpHttp.js.map +1 -0
  8. package/dist/mail/unsubscribe.js +5 -4
  9. package/dist/mail/unsubscribe.js.map +1 -1
  10. package/dist/orgs.js +87 -0
  11. package/dist/orgs.js.map +1 -0
  12. package/dist/plan.js +82 -0
  13. package/dist/plan.js.map +1 -0
  14. package/dist/projectCache.js +3 -2
  15. package/dist/projectCache.js.map +1 -1
  16. package/dist/reviewerCache.js +3 -2
  17. package/dist/reviewerCache.js.map +1 -1
  18. package/dist/routes/account.js +5 -4
  19. package/dist/routes/account.js.map +1 -1
  20. package/dist/routes/anchorReports.js +3 -2
  21. package/dist/routes/anchorReports.js.map +1 -1
  22. package/dist/routes/attachments.js +17 -6
  23. package/dist/routes/attachments.js.map +1 -1
  24. package/dist/routes/commentEdit.js +3 -2
  25. package/dist/routes/commentEdit.js.map +1 -1
  26. package/dist/routes/comments.js +7 -6
  27. package/dist/routes/comments.js.map +1 -1
  28. package/dist/routes/cors.js +5 -4
  29. package/dist/routes/cors.js.map +1 -1
  30. package/dist/routes/instance.js +29 -0
  31. package/dist/routes/instance.js.map +1 -1
  32. package/dist/routes/integrations.js +10 -5
  33. package/dist/routes/integrations.js.map +1 -1
  34. package/dist/routes/mcp.js +45 -0
  35. package/dist/routes/mcp.js.map +1 -0
  36. package/dist/routes/mcpAuth.js +59 -3
  37. package/dist/routes/mcpAuth.js.map +1 -1
  38. package/dist/routes/mcpToken.js +10 -9
  39. package/dist/routes/mcpToken.js.map +1 -1
  40. package/dist/routes/mcpTokens.js +10 -3
  41. package/dist/routes/mcpTokens.js.map +1 -1
  42. package/dist/routes/members.js +11 -0
  43. package/dist/routes/members.js.map +1 -1
  44. package/dist/routes/orgs.js +128 -0
  45. package/dist/routes/orgs.js.map +1 -0
  46. package/dist/routes/owner.js +34 -12
  47. package/dist/routes/owner.js.map +1 -1
  48. package/dist/routes/reviewLinks.js +11 -10
  49. package/dist/routes/reviewLinks.js.map +1 -1
  50. package/dist/routes/rounds.js +7 -6
  51. package/dist/routes/rounds.js.map +1 -1
  52. package/dist/routes/sessions.js +16 -15
  53. package/dist/routes/sessions.js.map +1 -1
  54. package/dist/routes/sites.js +45 -26
  55. package/dist/routes/sites.js.map +1 -1
  56. package/dist/routes/threads.js +7 -11
  57. package/dist/routes/threads.js.map +1 -1
  58. package/dist/serverless.js +86 -120
  59. package/dist/serverless.js.map +1 -1
  60. package/dist/storage/settings.js +8 -7
  61. package/dist/storage/settings.js.map +1 -1
  62. package/dist/vendor/mcp/bin.js +72 -0
  63. package/dist/vendor/mcp/client.js +107 -0
  64. package/dist/vendor/mcp/server.js +242 -0
  65. package/dist/vendor/mcp/source.js +284 -0
  66. package/dist/vendor/mcp/view.js +199 -0
  67. package/dist/vendor/types/names.js +1 -0
  68. package/package.json +4 -2
  69. package/static/dashboard/assets/CompositeItem-DooiJvYh.js +1 -0
  70. package/static/dashboard/assets/{ConfirmDialog-CfUnmf4e.js → ConfirmDialog-B5Kq--VU.js} +1 -1
  71. package/static/dashboard/assets/{CopyButton-0ANrU8mS.js → CopyButton-BX1ldjjz.js} +1 -1
  72. package/static/dashboard/assets/FieldItemContext-DkOlIwHg.js +1 -0
  73. package/static/dashboard/assets/{InstallTag-CFvRuiwX.js → InstallTag-DahspVU0.js} +1 -1
  74. package/static/dashboard/assets/{SaveState-RDbpfaEm.js → SaveState-Q9xJjbqL.js} +1 -1
  75. package/static/dashboard/assets/_app-CRQzX614.js +56 -0
  76. package/static/dashboard/assets/{_app-DpPrYyh8.js → _app-Cd9_WR6L.js} +1 -1
  77. package/static/dashboard/assets/{activity-DwTWlcUi.js → activity-B-59HYua.js} +1 -1
  78. package/static/dashboard/assets/{arrow-left-2PKG3ITQ.js → arrow-left-aeWIKOlS.js} +1 -1
  79. package/static/dashboard/assets/{avatar-AfucjvZ4.js → avatar-KLKmXBKs.js} +1 -1
  80. package/static/dashboard/assets/{badge-CErha5Qd.js → badge-Dly0Vqky.js} +1 -1
  81. package/static/dashboard/assets/{card-DxFqxFQf.js → card-D3S4369l.js} +1 -1
  82. package/static/dashboard/assets/{check-Yc6VFTfm.js → check-CjehM7M4.js} +1 -1
  83. package/static/dashboard/assets/{chevron-right-Go23FN_b.js → chevron-right-CtWPB3ox.js} +1 -1
  84. package/static/dashboard/assets/{copy-BjvQHEe0.js → copy-DbrG17rp.js} +1 -1
  85. package/static/dashboard/assets/{createLucideIcon-DwbZIZsa.js → createLucideIcon-CJO80vPf.js} +1 -1
  86. package/static/dashboard/assets/{dialog-CgkCHO0g.js → dialog-GyPeOagK.js} +1 -1
  87. package/static/dashboard/assets/{dropdown-menu-DRa3ikv4.js → dropdown-menu-BOoXJZ3W.js} +3 -3
  88. package/static/dashboard/assets/{image-off-ByN_ZOK_.js → image-off-BYE-vNh9.js} +1 -1
  89. package/static/dashboard/assets/{index-BRTBtV91.css → index-BR4Mnw-o.css} +1 -1
  90. package/static/dashboard/assets/{index-DG2IWFx-.js → index-D4yHhKPM.js} +30 -30
  91. package/static/dashboard/assets/label-BGuYH_Ll.js +1 -0
  92. package/static/dashboard/assets/{layout-grid-Dj0cm_PO.js → layout-grid-DJ_OjGVc.js} +1 -1
  93. package/static/dashboard/assets/me-B-0XXdLF.js +1 -0
  94. package/static/dashboard/assets/{parts-B3hL1LIW.js → parts-DJaBw8EG.js} +1 -1
  95. package/static/dashboard/assets/{plus-CRLkl9fr.js → plus-x0wkLget.js} +1 -1
  96. package/static/dashboard/assets/s._site-s4IRnE6u.js +1 -0
  97. package/static/dashboard/assets/{s._site.comments-DCpM8f18.js → s._site.comments-Bt0AsQ8j.js} +3 -3
  98. package/static/dashboard/assets/s._site.reviewers-BzJsotII.js +1 -0
  99. package/static/dashboard/assets/s._site.settings-C6ei77kQ.js +1 -0
  100. package/static/dashboard/assets/setup-DTi8FBnt.js +1 -0
  101. package/static/dashboard/assets/sign-in-CGOMDkYg.js +1 -0
  102. package/static/dashboard/assets/sign-up-CfU4RCfo.js +1 -0
  103. package/static/dashboard/assets/sites-zZ5gTDCZ.js +1 -0
  104. package/static/dashboard/assets/studio-DaMq1jOg.js +1 -0
  105. package/static/dashboard/assets/{studio-BIDpGywS.js → studio-r6xIIciH.js} +1 -1
  106. package/static/dashboard/assets/studio.index-CsF3xaoh.js +1 -0
  107. package/static/dashboard/assets/studio.integrations-BnPEkEA3.js +1 -0
  108. package/static/dashboard/assets/studio.members-DegH-I8c.js +1 -0
  109. package/static/dashboard/assets/{sun-CyU37by7.js → sun-BLhaKyhb.js} +1 -1
  110. package/static/dashboard/assets/switch-En3PhTzh.js +1 -0
  111. package/static/dashboard/assets/{table-DEYsEr1D.js → table-DuU_ZP-u.js} +1 -1
  112. package/static/dashboard/assets/useAriaLabelledBy-C3DXCwhM.js +1 -0
  113. package/static/dashboard/assets/{useBaseQuery-Cyp8ADaN.js → useBaseQuery-B_iu1J2b.js} +1 -1
  114. package/static/dashboard/assets/useQuery-yeUPHKKg.js +1 -0
  115. package/static/dashboard/assets/useRender-Bl1M9SFS.js +1 -0
  116. package/static/dashboard/assets/useValueChanged-BwMBSSVy.js +1 -0
  117. package/static/dashboard/assets/{users-CcRaZGiI.js → users-c6wDSgLD.js} +1 -1
  118. package/static/dashboard/assets/{welcome-DFcmcB5m.js → welcome-Ddm5B8Xh.js} +2 -2
  119. package/static/dashboard/assets/you-BmjZYMtz.js +1 -0
  120. package/static/dashboard/assets/{zod-qEjeM9Eg.js → zod-CBoGbUzv.js} +1 -1
  121. package/static/dashboard/index.html +2 -2
  122. package/static/dashboard/assets/CompositeItem-CrOf38Oz.js +0 -1
  123. package/static/dashboard/assets/FieldItemContext-CFDZff0c.js +0 -1
  124. package/static/dashboard/assets/_app-CyFpAeiD.js +0 -56
  125. package/static/dashboard/assets/createBaseUIEventDetails-BWJgXhnd.js +0 -1
  126. package/static/dashboard/assets/label-Bombrpc6.js +0 -1
  127. package/static/dashboard/assets/me-Bno2H07j.js +0 -1
  128. package/static/dashboard/assets/s._site-CP12hsxz.js +0 -1
  129. package/static/dashboard/assets/s._site.reviewers-WT9D4fr3.js +0 -1
  130. package/static/dashboard/assets/s._site.settings-DaxVWDmT.js +0 -1
  131. package/static/dashboard/assets/setup-Ch-Vt7_T.js +0 -1
  132. package/static/dashboard/assets/sign-in-CdGjL0Ha.js +0 -1
  133. package/static/dashboard/assets/sites-8S1PAUfv.js +0 -1
  134. package/static/dashboard/assets/studio-DREMbrco.js +0 -1
  135. package/static/dashboard/assets/studio.index-B4-MAfEP.js +0 -1
  136. package/static/dashboard/assets/studio.integrations-D1bP5fqp.js +0 -1
  137. package/static/dashboard/assets/studio.members-DKl8oRd9.js +0 -1
  138. package/static/dashboard/assets/useQuery-DuAHuwx9.js +0 -1
  139. package/static/dashboard/assets/useRender-BCvsiXRL.js +0 -1
  140. package/static/dashboard/assets/you-ClBoja9X.js +0 -1
@@ -4,129 +4,95 @@ import { loadConfig } from "./config.js";
4
4
  import { applyPlatformDefaults } from "./platform.js";
5
5
  import { createApp } from "./app.js";
6
6
  import { resolveSecretKey } from "./boot.js";
7
- /**
8
- * The serverless entry point — 08 §3.2, LOCK L5, L2.3.
9
- *
10
- * The SECOND entry on the same `createApp(deps)`. The bin owns a process and
11
- * can boot, tick and hold a pool; a function instance owns none of that, and
12
- * §3.2 names the four differences. All four are implemented here and nowhere
13
- * else, which is the point of there being one app factory.
14
- *
15
- * 1. **Jobs are a cron, not an interval.** `/api/jobs/run` invokes
16
- * `runDueJobs`, driven by `vercel.json`'s schedule. Nothing may depend on
17
- * a long-lived process — no LISTEN/NOTIFY, no resident state.
18
- * 2. **Migrations run at deploy**, as a release step. A function instance has
19
- * no boot to hook, and migrating on cold start would take the advisory
20
- * lock on a request's critical path — every instance queueing behind one
21
- * that is applying DDL, on somebody's first page load.
22
- * 3. **`DATABASE_URL` must be POOLED.** The marketplace resources hand one
23
- * out. Interactive transactions work in transaction-pooling mode, so L6's
24
- * same-transaction outbox guarantee holds.
25
- * 4. **`SNAGSET_SECRET_KEY` is required**, because §4.2's generate-a-key-to-
26
- * disk is bin-only: the filesystem here is ephemeral, so a generated key
27
- * would differ per instance and every session and encrypted credential
28
- * would decrypt on one and fail on the next. Checked below, with the
29
- * reason, rather than left to fail as corrupted ciphertext.
30
- */
31
- /**
32
- * The app, built ONCE per instance and reused across invocations.
33
- *
34
- * Module scope is the only warm thing a function has. Rebuilding per request
35
- * would re-read config, re-derive the keyring and open a new pool on every
36
- * call — which on a pooled Postgres is how an instance exhausts the very
37
- * connection limit the pooler exists to protect.
38
- */
39
- let cached = null;
40
- function boot() {
41
- if (cached)
7
+ export function createServerlessHandler(options = {}) {
8
+ let cached = null;
9
+ function boot() {
10
+ if (cached)
11
+ return cached;
12
+ applyPlatformDefaults(process.env); // D105 — Vercel names its own deployment
13
+ const cfg = loadConfig(process.env);
14
+ if (!cfg.get("SNAGSET_SECRET_KEY") && !cfg.get("SNAGSET_SECRET_KEY_FILE")) {
15
+ throw new Error("SNAGSET_SECRET_KEY is required on serverless. The filesystem here is ephemeral, so " +
16
+ "the bin's generate-a-key-on-first-boot path cannot work: each instance would invent " +
17
+ "a different key, and a session or an encrypted credential written by one would fail " +
18
+ "to decrypt on the next. Generate 32 random bytes, base64url them, and set it in the " +
19
+ "deploy's environment (08 §4.2).");
20
+ }
21
+ const sql = connect(cfg.required("DATABASE_URL"), { max: cfg.int("SNAGSET_DATABASE_POOL_MAX") });
22
+ const app = createApp({
23
+ sql,
24
+ cfg,
25
+ secretKey: resolveSecretKey(cfg, null, () => { }),
26
+ // A function instance cannot run the boot state machine — that reads and
27
+ // writes the claim token and would race across instances. The state is
28
+ // derived per request from the database instead, which is what `createApp`
29
+ // does when it is told nothing.
30
+ state: "READY",
31
+ version: options.version ?? process.env["SNAGSET_VERSION"] ?? "0.0.0",
32
+ commit: process.env["VERCEL_GIT_COMMIT_SHA"] ?? "unknown",
33
+ sourceUrl: options.sourceUrl ?? "https://github.com/snagset/snagset",
34
+ modified: false,
35
+ ...(options.planResolver ? { planResolver: options.planResolver } : {}),
36
+ });
37
+ cached = { app, sql };
42
38
  return cached;
43
- applyPlatformDefaults(process.env); // D105 — Vercel names its own deployment
44
- const cfg = loadConfig(process.env);
45
- if (!cfg.get("SNAGSET_SECRET_KEY") && !cfg.get("SNAGSET_SECRET_KEY_FILE")) {
46
- throw new Error("SNAGSET_SECRET_KEY is required on serverless. The filesystem here is ephemeral, so " +
47
- "the bin's generate-a-key-on-first-boot path cannot work: each instance would invent " +
48
- "a different key, and a session or an encrypted credential written by one would fail " +
49
- "to decrypt on the next. Generate 32 random bytes, base64url them, and set it in the " +
50
- "deploy's environment (08 §4.2).");
51
39
  }
52
- const sql = connect(cfg.required("DATABASE_URL"), { max: cfg.int("SNAGSET_DATABASE_POOL_MAX") });
53
- const app = createApp({
54
- sql,
55
- cfg,
56
- secretKey: resolveSecretKey(cfg, null, () => { }),
57
- // A function instance cannot run the boot state machine — that reads and
58
- // writes the claim token and would race across instances. The state is
59
- // derived per request from the database instead, which is what `createApp`
60
- // does when it is told nothing.
61
- state: "READY",
62
- version: process.env["SNAGSET_VERSION"] ?? "0.0.0",
63
- commit: process.env["VERCEL_GIT_COMMIT_SHA"] ?? "unknown",
64
- sourceUrl: "https://github.com/snagset/snagset",
65
- modified: false,
66
- });
67
- cached = { app, sql };
68
- return cached;
69
- }
70
- /**
71
- * The Web-standard handler every serverless target accepts.
72
- *
73
- * A `fetch(Request) => Response` rather than a Vercel-shaped adapter, because
74
- * that is the same signature Cloudflare Workers, Deno and Bun take — D24 makes
75
- * Workers the next hosting milestone on this same app factory, and an entry
76
- * point written to one vendor's helper is one that has to be rewritten for it.
77
- */
78
- /**
79
- * A CONFIGURATION FAILURE SHOULD SAY SO, not crash.
80
- *
81
- * `boot()` throws when a required variable is missing, and until 2026-09-01
82
- * that throw reached the platform — which renders "This Serverless Function
83
- * has crashed. FUNCTION_INVOCATION_FAILED" and puts the real message in
84
- * function logs the operator may not know exist. On the deploy we bless, on
85
- * the very first request, for the most ordinary mistake there is.
86
- *
87
- * The bin has had `explain()` for this since it shipped: an unreachable
88
- * database prints "✗ no Postgres answered at host:port. Start one, or point
89
- * DATABASE_URL at a database that is running." Serverless had nothing, so the
90
- * same operator got a sentence on the bin and a shrug on Vercel.
91
- *
92
- * NAMES ONLY, never values — this response is public, and the whole point of
93
- * the variable being missing is that somebody is about to paste a secret into
94
- * a dashboard. `503` rather than `500`: the instance is not broken, it is not
95
- * configured, and a proxy should feel free to retry once it is.
96
- */
97
- function configFailure(e) {
98
- const message = e instanceof Error ? e.message : String(e);
99
- return new Response(`Snagset is deployed but not configured.\n\n${message}\n\n` +
100
- `Set it in your platform's environment settings and redeploy.\n` +
101
- `\`snagset env\` lists every variable this build accepts.\n`, { status: 503, headers: { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" } });
102
- }
103
- function serve(request) {
104
- let app;
105
- try {
106
- app = boot().app;
40
+ /**
41
+ * The Web-standard handler every serverless target accepts.
42
+ *
43
+ * A `fetch(Request) => Response` rather than a Vercel-shaped adapter, because
44
+ * that is the same signature Cloudflare Workers, Deno and Bun take — D24 makes
45
+ * Workers the next hosting milestone on this same app factory, and an entry
46
+ * point written to one vendor's helper is one that has to be rewritten for it.
47
+ */
48
+ /**
49
+ * A CONFIGURATION FAILURE SHOULD SAY SO, not crash.
50
+ *
51
+ * `boot()` throws when a required variable is missing, and until 2026-09-01
52
+ * that throw reached the platform — which renders "This Serverless Function
53
+ * has crashed. FUNCTION_INVOCATION_FAILED" and puts the real message in
54
+ * function logs the operator may not know exist. On the deploy we bless, on
55
+ * the very first request, for the most ordinary mistake there is.
56
+ *
57
+ * The bin has had `explain()` for this since it shipped: an unreachable
58
+ * database prints "✗ no Postgres answered at host:port. Start one, or point
59
+ * DATABASE_URL at a database that is running." Serverless had nothing, so the
60
+ * same operator got a sentence on the bin and a shrug on Vercel.
61
+ *
62
+ * NAMES ONLY, never values — this response is public, and the whole point of
63
+ * the variable being missing is that somebody is about to paste a secret into
64
+ * a dashboard. `503` rather than `500`: the instance is not broken, it is not
65
+ * configured, and a proxy should feel free to retry once it is.
66
+ */
67
+ function configFailure(e) {
68
+ const message = e instanceof Error ? e.message : String(e);
69
+ return new Response(`Snagset is deployed but not configured.\n\n${message}\n\n` +
70
+ `Set it in your platform's environment settings and redeploy.\n` +
71
+ `\`snagset env\` lists every variable this build accepts.\n`, { status: 503, headers: { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" } });
107
72
  }
108
- catch (e) {
109
- // Nothing is cached on this path, so the NEXT request boots again — set
110
- // the variable and the instance recovers without a redeploy.
111
- return configFailure(e);
73
+ function serve(request) {
74
+ let app;
75
+ try {
76
+ app = boot().app;
77
+ }
78
+ catch (e) {
79
+ // Nothing is cached on this path, so the NEXT request boots again — set
80
+ // the variable and the instance recovers without a redeploy.
81
+ return configFailure(e);
82
+ }
83
+ return app.fetch(request);
112
84
  }
113
- return app.fetch(request);
114
- }
115
- export default { fetch: serve };
116
- /** Named, for platforms whose convention is a bare exported function. */
117
- export const handler = serve;
118
- /**
119
- * Release the instance's pool. **Tests only** — a function instance is torn
120
- * down by the platform and never calls this.
121
- *
122
- * It exists because the cache above is module scope, which is exactly what
123
- * makes a warm instance cheap and what makes a test process hang: without it
124
- * node's runner waits forever on a pool nothing will close.
125
- */
126
- export async function closeForTests() {
127
- const held = cached;
128
- cached = null;
129
- if (held)
130
- await held.sql.end({ timeout: 5 });
85
+ async function closeForTests() {
86
+ const held = cached;
87
+ cached = null;
88
+ if (held)
89
+ await held.sql.end({ timeout: 5 });
90
+ }
91
+ return { fetch: serve, closeForTests };
131
92
  }
93
+ /** The blessed deploy: the engine as it is, no resolver, unlimited — every self-host's serverless entry. */
94
+ const standard = createServerlessHandler();
95
+ export default { fetch: standard.fetch };
96
+ export const handler = standard.fetch;
97
+ export const closeForTests = standard.closeForTests;
132
98
  //# sourceMappingURL=serverless.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"serverless.js","sourceRoot":"","sources":["../src/serverless.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"serverless.js","sourceRoot":"","sources":["../src/serverless.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { open, seal } from "../crypto.js";
2
2
  import { createBucket } from "./bucket.js";
3
+ import { maintenance } from "../db/scope.js";
3
4
  /**
4
5
  * The bucket, from the environment — D60, and the PRIMARY path.
5
6
  *
@@ -47,10 +48,10 @@ export function envBucketConfig(env) {
47
48
  };
48
49
  }
49
50
  const orgRow = async (sql) => {
50
- const rows = await sql `
51
+ const rows = await maintenance(sql, (tx) => tx `
51
52
  SELECT id, storage_config, storage_credentials, storage_credentials_iv, storage_connected_at
52
53
  FROM orgs WHERE deleted_at IS NULL ORDER BY created_at LIMIT 1
53
- `;
54
+ `);
54
55
  return rows[0] ?? null;
55
56
  };
56
57
  export async function storageStatus(sql, keys, env = process.env) {
@@ -102,15 +103,15 @@ export async function connectBucket(sql, keys, config, creds) {
102
103
  if (!org)
103
104
  return { ok: false, step: "instance", detail: "this instance is not claimed yet" };
104
105
  const sealed = seal(keys, JSON.stringify(creds));
105
- await sql `
106
+ await maintenance(sql, (tx) => tx `
106
107
  UPDATE orgs
107
- SET storage_config = ${sql.json({ ...config })},
108
+ SET storage_config = ${tx.json({ ...config })},
108
109
  storage_credentials = ${sealed.ciphertext},
109
110
  storage_credentials_iv = ${sealed.iv},
110
111
  storage_connected_at = now(),
111
112
  updated_at = now()
112
113
  WHERE id = ${org.id}
113
- `;
114
+ `);
114
115
  return { ok: true };
115
116
  }
116
117
  /**
@@ -122,11 +123,11 @@ export async function connectBucket(sql, keys, config, creds) {
122
123
  * costing money and recoverable by nobody.
123
124
  */
124
125
  export async function disconnectBucket(sql) {
125
- await sql `
126
+ await maintenance(sql, (tx) => tx `
126
127
  UPDATE orgs
127
128
  SET storage_connected_at = NULL, storage_credentials = NULL,
128
129
  storage_credentials_iv = NULL, updated_at = now()
129
130
  WHERE deleted_at IS NULL
130
- `;
131
+ `);
131
132
  }
132
133
  //# sourceMappingURL=settings.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/storage/settings.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/storage/settings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { createServer } from "./server.js";
4
+ /**
5
+ * The stdio entry point — 07 §8.1's supported transport.
6
+ *
7
+ * | Transport | Where it runs | Default |
8
+ * |---|---|---|
9
+ * | **stdio** | the developer's own machine, spawned by their MCP client | **the supported path** |
10
+ * | streamable HTTP | our server, at `POST /mcp` (D107) | **off** until the switch on Studio › Integrations › MCP, or `SNAGSET_MCP_HTTP_ENABLED` (D108) |
11
+ *
12
+ * This file is the first. stdio is the default and §8.5 is the reason, not
13
+ * a scheduling accident: source mapping needs the checkout, the checkout is
14
+ * on the developer's machine, and a remote server cannot offer `source:map`
15
+ * at all — the instance serves the second without it.
16
+ *
17
+ * ── NOTHING MAY BE WRITTEN TO STDOUT ──
18
+ *
19
+ * stdout IS the protocol channel. A `console.log` anywhere in this package
20
+ * corrupts the JSON-RPC stream and the client reports a parse error with no
21
+ * indication of where it came from. Diagnostics go to stderr, which the client
22
+ * shows in its logs. This is the single most common way an MCP server that
23
+ * works in a test fails in a real client.
24
+ */
25
+ const env = process.env;
26
+ /**
27
+ * Config comes from the MCP CLIENT's process env (§8.2), not ours — it is
28
+ * written into the client's config file:
29
+ *
30
+ * ```jsonc
31
+ * { "mcpServers": { "snagset": { "command": "npx", "args": ["-y", "./server.js"],
32
+ * "env": { "SNAGSET_MCP_TOKEN": "…", "SNAGSET_MCP_HOST": "https://review.example.com" } } } }
33
+ * ```
34
+ *
35
+ * `CLICKREVIEW_TOKEN` and `CLICKREVIEW_HOST` are DELETED — the last
36
+ * un-namespaced spelling in 07, kept there only as a deletion record. They are
37
+ * not read here, not even as a fallback: a fallback is how a deleted name stays
38
+ * alive for years.
39
+ */
40
+ const token = env["SNAGSET_MCP_TOKEN"];
41
+ const host = env["SNAGSET_MCP_HOST"];
42
+ function die(message) {
43
+ // stderr, and a non-zero exit. A server that starts and then answers every
44
+ // tool call with "no token" looks to the agent like a product that does not
45
+ // work; failing at startup puts the message where the developer configuring
46
+ // it will actually see it.
47
+ process.stderr.write(`snagset-mcp: ${message}\n`);
48
+ process.exit(2);
49
+ }
50
+ if (!token) {
51
+ die("SNAGSET_MCP_TOKEN is not set. Mint one on the instance with `snagset mcp-token create <name>` " +
52
+ "and put it in your MCP client's config under env.");
53
+ }
54
+ if (!host) {
55
+ die("SNAGSET_MCP_HOST is not set — the base URL of your Snagset instance, e.g. https://review.example.com");
56
+ }
57
+ if (!token.startsWith("snagmcp_")) {
58
+ // Named early because the two credentials look nothing alike and the failure
59
+ // otherwise arrives as a 401 on the first tool call, three steps later.
60
+ die("SNAGSET_MCP_TOKEN does not look like an MCP token — they begin `snagmcp_`. An API key will not work here.");
61
+ }
62
+ const server = createServer({
63
+ host,
64
+ token,
65
+ // The client spawns us in the developer's project directory, which is what
66
+ // makes `map_thread_to_source` work with no configuration at all.
67
+ repoRoot: env["SNAGSET_MCP_REPO_ROOT"] ?? process.cwd(),
68
+ project: env["SNAGSET_MCP_PROJECT"],
69
+ version: env["SNAGSET_MCP_VERSION"] ?? "0.0.0",
70
+ });
71
+ await server.connect(new StdioServerTransport());
72
+ process.stderr.write(`snagset-mcp: connected to ${host}\n`);
@@ -0,0 +1,107 @@
1
+ /**
2
+ * The HTTP client — 07 §8.1's load-bearing sentence made literal.
3
+ *
4
+ * > *"It is not a second API. Every read the MCP server performs goes through
5
+ * > the same `/api/v1` routes with the same authorisation as any other caller;
6
+ * > it holds no database connection, no credential store and no cache of its
7
+ * > own. A bug in this package cannot read a thread its token could not already
8
+ * > read."*
9
+ *
10
+ * That last clause is a claim about THIS FILE, and it is only true while this
11
+ * is the only way out of the package. There is no `postgres` dependency here
12
+ * and there must never be one: the day this package can reach the database, its
13
+ * scopes become advisory and every mitigation in §8.6 rests on the agent
14
+ * behaving.
15
+ *
16
+ * The whole surface is one method. `fetch` is the platform's, so the package
17
+ * carries no HTTP dependency either.
18
+ */
19
+ export class Client {
20
+ #host;
21
+ #token;
22
+ #fetch;
23
+ constructor(cfg) {
24
+ // Trailing slash removed once, here, rather than at eleven call sites —
25
+ // `https://x/` + `/api/v1` is a double slash, which some proxies 404 and
26
+ // some silently normalise, so it fails on somebody else's deployment and
27
+ // not on ours.
28
+ this.#host = cfg.host.replace(/\/+$/, "");
29
+ this.#token = cfg.token;
30
+ this.#fetch = cfg.fetch ?? globalThis.fetch;
31
+ }
32
+ get host() {
33
+ return this.#host;
34
+ }
35
+ async call(path, init = {}) {
36
+ const url = new URL(this.#host + path);
37
+ for (const [k, v] of Object.entries(init.query ?? {})) {
38
+ if (v !== undefined && v !== "")
39
+ url.searchParams.set(k, v);
40
+ }
41
+ let res;
42
+ try {
43
+ res = await this.#fetch(url, {
44
+ method: init.method ?? "GET",
45
+ headers: {
46
+ Authorization: `Bearer ${this.#token}`,
47
+ ...(init.body === undefined ? {} : { "content-type": "application/json" }),
48
+ },
49
+ ...(init.body === undefined ? {} : { body: JSON.stringify(init.body) }),
50
+ });
51
+ }
52
+ catch (e) {
53
+ // A network failure is the single most likely thing to go wrong here —
54
+ // the agent is on a laptop, the instance is somewhere else — and the
55
+ // message names the host, because "fetch failed" sends a developer to
56
+ // look at their code rather than at their VPN.
57
+ return {
58
+ ok: false,
59
+ status: 0,
60
+ message: `Could not reach ${this.#host}: ${e instanceof Error ? e.message : String(e)}. Check SNAGSET_MCP_HOST and that the instance is running.`,
61
+ };
62
+ }
63
+ const text = await res.text();
64
+ if (!res.ok) {
65
+ const parsed = safeJson(text);
66
+ const code = parsed?.error?.code;
67
+ const message = parsed?.error?.message ?? text.slice(0, 400);
68
+ // 07 §8.4: on 429 the tool layer must tell the agent to STOP and ask its
69
+ // user — *"never a silent retry, never a sleep-and-retry loop. An agent
70
+ // that has been talked into mass-resolving threads must hit a wall it has
71
+ // to report, because the wall is the only part of the mitigation a human
72
+ // sees."* This client therefore has no retry logic at all, on any status.
73
+ if (res.status === 429) {
74
+ // Appended only when the server did not already say it. The server's
75
+ // own budget message ends "do not retry", and adding ours produced
76
+ // "…do not retry. Do not retry — tell your user." — a stop signal that
77
+ // reads like a stutter, which is the opposite of what it is for.
78
+ const stop = /do not retry/i.test(message) ? "" : " Do not retry — tell your user.";
79
+ return { ok: false, status: 429, message: message + stop };
80
+ }
81
+ if (code === "mcp_scope_denied") {
82
+ return { ok: false, status: 403, message };
83
+ }
84
+ return { ok: false, status: res.status, message: `${res.status}: ${message}` };
85
+ }
86
+ const value = safeJson(text);
87
+ if (value === null && text.trim() !== "null") {
88
+ return {
89
+ ok: false,
90
+ status: res.status,
91
+ // The exact failure the server's own widget route had: a 200 with an
92
+ // HTML body, because an SPA fallback answered a path nobody had
93
+ // registered. Named, because "unexpected token <" is not a diagnosis.
94
+ message: `${this.#host} answered ${res.status} with something that is not JSON. Is SNAGSET_MCP_HOST pointing at a Snagset instance?`,
95
+ };
96
+ }
97
+ return { ok: true, value: value };
98
+ }
99
+ }
100
+ function safeJson(text) {
101
+ try {
102
+ return JSON.parse(text);
103
+ }
104
+ catch {
105
+ return null;
106
+ }
107
+ }