@subseam/mcp 0.4.1 → 0.4.11

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 (3) hide show
  1. package/README.md +10 -7
  2. package/dist/main.js +58 -17
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -12,9 +12,8 @@ and the rest. It runs on your machine and talks to the subseam service over HTTP
12
12
  ## What you need
13
13
 
14
14
  - Node.js 22 or later, and npm.
15
- - A GitHub account, and a subseam organization that someone has added you to. The org's owner
16
- gives you its id (a UUID); that is your **tenant id**.
17
- - The subseam API URL: `https://subseam.theconstructory.com/api`.
15
+ - A GitHub account, and a subseam organization that someone has added you to.
16
+ - The subseam API URL: `https://subseam.theconstructory.com/subseam`.
18
17
 
19
18
  ## 1. Sign in
20
19
 
@@ -54,15 +53,19 @@ npm install @subseam/mcp
54
53
 
55
54
  ## 4. Register it with your agent
56
55
 
57
- For Claude Code, with your tenant id in place of `<tenant id>`:
56
+ For Claude Code:
58
57
 
59
58
  ```
60
- claude mcp add-json --scope user subseam '{"command":"node","args":["'"$HOME"'/.subseam/mcp/node_modules/@subseam/mcp/dist/main.js"],"env":{"SUBSEAM_URL":"https://subseam.theconstructory.com/api","SUBSEAM_TENANT":"<tenant id>"}}'
59
+ claude mcp add-json --scope user subseam '{"command":"node","args":["'"$HOME"'/.subseam/mcp/node_modules/@subseam/mcp/dist/main.js"],"env":{"SUBSEAM_URL":"https://subseam.theconstructory.com/subseam"}}'
61
60
  ```
62
61
 
63
62
  `claude mcp list` should show `subseam ... Connected`. Any other MCP host: run
64
- `node ~/.subseam/mcp/node_modules/@subseam/mcp/dist/main.js` over stdio with the same two
65
- environment variables.
63
+ `node ~/.subseam/mcp/node_modules/@subseam/mcp/dist/main.js` over stdio with the same
64
+ environment.
65
+
66
+ The server acts for your organization. If you belong to several, it mounts only
67
+ `subseam_unavailable`, which lists each one's name and id: add `"SUBSEAM_TENANT":"<id>"` to the
68
+ `env` above for the one this agent works in.
66
69
 
67
70
  ## 5. Check it works
68
71
 
package/dist/main.js CHANGED
@@ -24036,10 +24036,11 @@ function requireEnv(env, name, purpose) {
24036
24036
  function loadConfig2(env = process.env, sources = realTokenSources()) {
24037
24037
  return Object.freeze({
24038
24038
  get url() {
24039
- return requireEnv(env, "SUBSEAM_URL", "to reach the hosted service, e.g. https://app.subseam.dev");
24039
+ return requireEnv(env, "SUBSEAM_URL", "to reach the hosted service, e.g. https://subseam.theconstructory.com/subseam");
24040
24040
  },
24041
24041
  get tenantId() {
24042
- return requireEnv(env, "SUBSEAM_TENANT", "as the organization id the channel acts for (a UUID)");
24042
+ const value = env["SUBSEAM_TENANT"];
24043
+ return value === void 0 || value === "" ? void 0 : value;
24043
24044
  },
24044
24045
  get token() {
24045
24046
  return resolveTenantToken(sources);
@@ -28203,14 +28204,35 @@ async function requireRepo(env, readOrigin2) {
28203
28204
  throw new Error(`SUBSEAM_REPO unset; git remote origin = ${seen} did not parse as owner/name`);
28204
28205
  }
28205
28206
 
28207
+ // src/account.ts
28208
+ init_esm_shims();
28209
+ var AccountChoiceError = class extends Error {
28210
+ };
28211
+ async function resolveAccount(config3) {
28212
+ if (config3.tenantId !== void 0) return config3.tenantId;
28213
+ const url2 = `${config3.url.replace(/\/$/, "")}/me`;
28214
+ const response = await fetch(url2, { headers: { authorization: `Bearer ${config3.token}` } });
28215
+ if (!response.ok) {
28216
+ throw new Error(
28217
+ `account lookup (GET ${url2}) failed: ${response.status} ${response.statusText}. ` + (response.status === 401 ? "Check the stored token (SUBSEAM_TOKEN or ~/.subseam/token) is valid." : await response.text().catch(() => ""))
28218
+ );
28219
+ }
28220
+ const body2 = await response.json();
28221
+ const orgs = (body2.accounts ?? []).filter((a) => a.kind === "org");
28222
+ if (orgs.length <= 1) return orgs[0]?.accountId ?? null;
28223
+ const listed = orgs.map((a) => `${a.handle ?? "(no handle)"} ${a.accountId}`).join("; ");
28224
+ throw new AccountChoiceError(`this token's account belongs to ${orgs.length} organizations (${listed}). Set SUBSEAM_TENANT to the id of the one this session acts for.`);
28225
+ }
28226
+
28206
28227
  // src/discovery-failure.ts
28207
28228
  init_esm_shims();
28208
28229
  var DISCOVERY_FAILURE_TOOL = "subseam_unavailable";
28209
- function discoveryFailureText(url2, reason) {
28210
- return `subseam is unavailable this session: capability discovery failed, so no subseam tools are mounted. Tried GET ${url2.replace(/\/$/, "")}/capabilities \u2014 ${reason}. Check SUBSEAM_URL is your subseam API URL and that it is reachable, then restart the MCP server.`;
28230
+ function discoveryFailureText(url2, reason, path7 = "/capabilities", advice = REACHABILITY_ADVICE) {
28231
+ return `subseam is unavailable this session: capability discovery failed, so no subseam tools are mounted. Tried GET ${url2.replace(/\/$/, "")}${path7} \u2014 ${reason}. ${advice}`;
28211
28232
  }
28212
- function mountDiscoveryFailure(server, url2, reason) {
28213
- const text = discoveryFailureText(url2, reason);
28233
+ var REACHABILITY_ADVICE = "Check SUBSEAM_URL is your subseam API URL and that it is reachable, then restart the MCP server.";
28234
+ function mountDiscoveryFailure(server, url2, reason, path7, advice) {
28235
+ const text = discoveryFailureText(url2, reason, path7, advice);
28214
28236
  server.registerTool(DISCOVERY_FAILURE_TOOL, { description: text, inputSchema: {} }, () => ({
28215
28237
  content: [{ type: "text", text }],
28216
28238
  isError: true
@@ -29683,10 +29705,13 @@ init_esm_shims();
29683
29705
  init_esm_shims();
29684
29706
 
29685
29707
  // src/gateway.ts
29708
+ function accountHeader(config3) {
29709
+ return config3.tenantId === void 0 ? {} : { [TENANT_HEADER]: config3.tenantId };
29710
+ }
29686
29711
  async function fetchEnabledCapabilities(config3) {
29687
29712
  const base = config3.url.replace(/\/$/, "");
29688
29713
  const response = await fetch(`${base}/capabilities`, {
29689
- headers: { authorization: `Bearer ${config3.token}`, [TENANT_HEADER]: config3.tenantId }
29714
+ headers: { authorization: `Bearer ${config3.token}`, ...accountHeader(config3) }
29690
29715
  });
29691
29716
  if (!response.ok) {
29692
29717
  const detail = await response.text().catch(() => "");
@@ -29706,7 +29731,7 @@ function createGatewayClient(config3) {
29706
29731
  headers: {
29707
29732
  "content-type": "application/json",
29708
29733
  authorization: `Bearer ${config3.token}`,
29709
- [TENANT_HEADER]: config3.tenantId
29734
+ ...accountHeader(config3)
29710
29735
  },
29711
29736
  body: JSON.stringify(input12)
29712
29737
  });
@@ -30699,10 +30724,11 @@ var input2 = external_exports.object({
30699
30724
  source: external_exports.record(external_exports.unknown()).optional(),
30700
30725
  nlIntent: external_exports.string().min(1).optional(),
30701
30726
  createdBy: external_exports.string().min(1).optional(),
30702
- // OPTIONAL IN THE SCHEMA, MANDATORY IN EFFECT (the shape `invoke-define-admit-route.ts` takes):
30703
- // the `source` arm's admission is `authored`, and Core refuses a blank basis with a typed
30727
+ // OPTIONAL IN THE SCHEMA, MANDATORY IN EFFECT ON `source` (the shape `invoke-define-admit-route.ts`
30728
+ // takes): the `source` arm's admission is `authored`, and Core refuses a blank basis with a typed
30704
30729
  // MISSING_BASIS carrying its repair hint. Declaring it required here would trade that named
30705
- // refusal for a Zod 400 with nothing to act on, and the `dry_run`/`mine` arms need none.
30730
+ // refusal for a Zod 400 with nothing to act on, and the `dry_run`/`mine` arms need none. The
30731
+ // `nlIntent` arm forwards it when given and otherwise cites its own sentence.
30706
30732
  basis: external_exports.string().min(1).optional(),
30707
30733
  dry_run: external_exports.boolean().optional(),
30708
30734
  mine: external_exports.object({
@@ -30714,7 +30740,7 @@ var input2 = external_exports.object({
30714
30740
  });
30715
30741
  var defineTool = {
30716
30742
  name: "define",
30717
- description: "Write a definition into this tenant's governing corpus. Pass exactly one of `source` (the SDL definition object \u2014 deterministic, no LLM) or `nlIntent` (a natural-language intent, authored through the CEGIS loop), plus `repo` (org-qualified) and optional `commit` (defaults to resolved tip). With `source`, pass `basis` too: free text citing what the definition stood on \u2014 the source doc plus its section (there is no fixed vocabulary). It is required in effect on this arm; an admission that cites nothing is refused. `nlIntent` supplies its own. `dry_run: true` with `source` validates against the live graph and admits NOTHING, returning per-gate errors with repair hints. `dry_run: true` with `source` and no `repo` answers only whether the language can say it (bind, capability and strength are deferred until a repo is named). `mine: {repo}` submits a whole-repo candidate mining pass instead (async \u2014 poll with `mine: {jobId}`). New versions land defaulted until approved. `gap: { construct, witness, issue }` records a construct the language cannot say yet (witness is the file:line that needed it) and admits nothing.",
30743
+ description: "Write a definition into this tenant's governing corpus. Pass exactly one of `source` (the SDL definition object \u2014 deterministic, no LLM) or `nlIntent` (a natural-language intent, authored through the CEGIS loop), plus `repo` (org-qualified) and optional `commit` (defaults to resolved tip). Pass `basis` on either arm: free text citing what the definition stood on \u2014 the source doc plus its section (there is no fixed vocabulary), or `capture-note:<id>` for a captured note, which also marks that note formalized. It is required in effect with `source`; an admission that cites nothing is refused. With `nlIntent` it defaults to the intent sentence. `dry_run: true` with `source` validates against the live graph and admits NOTHING, returning per-gate errors with repair hints. `dry_run: true` with `source` and no `repo` answers only whether the language can say it (bind, capability and strength are deferred until a repo is named). `mine: {repo}` submits a whole-repo candidate mining pass instead (async \u2014 poll with `mine: {jobId}`). New versions land defaulted until approved. `gap: { construct, witness, issue }` records a construct the language cannot say yet (witness is the file:line that needed it) and admits nothing.",
30718
30744
  capability: "define",
30719
30745
  inputSchema: input2.shape,
30720
30746
  async handler(raw, ctx) {
@@ -30763,7 +30789,8 @@ var defineTool = {
30763
30789
  repo: args2.repo,
30764
30790
  nlIntent: args2.nlIntent,
30765
30791
  ...args2.commit !== void 0 ? { commit: args2.commit } : {},
30766
- ...args2.createdBy !== void 0 ? { createdBy: args2.createdBy } : {}
30792
+ ...args2.createdBy !== void 0 ? { createdBy: args2.createdBy } : {},
30793
+ ...args2.basis !== void 0 ? { basis: args2.basis } : {}
30767
30794
  };
30768
30795
  const wire = await ctx.gateway.invoke("define-author", payload2);
30769
30796
  const body2 = readBody(authorBody, wire, "define", "define-author");
@@ -31591,6 +31618,7 @@ var defineInboxTool = {
31591
31618
  };
31592
31619
  var LABELS = {
31593
31620
  drift: "drift",
31621
+ draft: "draft",
31594
31622
  "refinement-pending": "refinement",
31595
31623
  "snooze-expired": "snooze expired",
31596
31624
  "snooze-expiring": "snooze due",
@@ -31606,6 +31634,8 @@ var HOW = {
31606
31634
  // parsing a number back out of a sentence to put it in a tool call is precisely the guessed
31607
31635
  // content the ruling forbids. So the call is named with the id it does hold and the remaining
31608
31636
  // arguments are stated as arguments the caller supplies.
31637
+ // A first draft is version 1, so the call is complete.
31638
+ draft: (item) => `define_ratify {kind:'ratify-version', definitionId:'${item.id}', version:1, status:'ratified'|'corrected'}`,
31609
31639
  "refinement-pending": (item) => `define_ratify {kind:'ratify-version', definitionId:'${item.id}', version:\u2026, status:'ratified'|'corrected'} \u2014 supply the version shown in this line`,
31610
31640
  // The read shows the alteration, verdict and definition; the adjudication follows it.
31611
31641
  proposal: (item) => `define_proposal_read {taskId:'${item.id}'}, then define_adjudicate {taskId:'${item.id}', decision:'accept'|'reject', reason:\u2026}`
@@ -31981,7 +32011,7 @@ function renderCapture(n) {
31981
32011
  const anchor = onPackage ? `attached to the package holding ${n.path}` : n.anchorKind === "concept" ? `concept ${n.conceptId}` : n.anchorKind === "path" ? `path ${n.path}` : n.anchorKind === "repo" ? `repo ${n.repoId}` : "unanchored";
31982
32012
  const chan = n.channel !== null ? ` [${n.channel}]` : "";
31983
32013
  return `capture: note ${n.id} stored (${anchor})${chan}.
31984
- To draft it as a definition, call \`define\` with this repo as \`repo\`, \`basis: "capture-note:${n.id}"\` and the definition as an SDL \`source\`.`;
32014
+ To draft it as a definition, call \`define\` with this repo as \`repo\`, \`basis: "capture-note:${n.id}"\`, and either \`nlIntent\` (the note in your words) or an SDL \`source\`.`;
31985
32015
  }
31986
32016
 
31987
32017
  // ../capture/src/tool/capture-list.ts
@@ -48306,7 +48336,7 @@ var workReadTools = createWorkReadToolProviders({ capability: "work" });
48306
48336
  var bundledProviders = [src_default2, src_default, tension_default, ...defineTools, ...decisionTools, ...captureTools, ...buildTools, secureTool, ...workTools, ...workReadTools];
48307
48337
 
48308
48338
  // package.json
48309
- var version2 = "0.4.1";
48339
+ var version2 = "0.4.11";
48310
48340
 
48311
48341
  // src/server.ts
48312
48342
  var SERVER_VERSION = version2;
@@ -48315,9 +48345,20 @@ function selectEntitled(providers, enabled) {
48315
48345
  }
48316
48346
  async function createServer(config3, cwd, initialWorkspaceId) {
48317
48347
  const server = new McpServer({ name: "subseam", version: SERVER_VERSION });
48348
+ let session;
48349
+ try {
48350
+ const account = await resolveAccount(config3);
48351
+ session = { url: config3.url, token: config3.token, tenantId: account ?? void 0 };
48352
+ } catch (err2) {
48353
+ const reason = err2 instanceof Error ? err2.message : String(err2);
48354
+ process.stderr.write(`subseam-mcp: account lookup failed; mounting only ${DISCOVERY_FAILURE_TOOL}. ${reason}
48355
+ `);
48356
+ mountDiscoveryFailure(server, config3.url, reason, "/me", err2 instanceof AccountChoiceError ? "Then restart the MCP server." : void 0);
48357
+ return server;
48358
+ }
48318
48359
  let enabled = [];
48319
48360
  try {
48320
- enabled = await fetchEnabledCapabilities(config3);
48361
+ enabled = await fetchEnabledCapabilities(session);
48321
48362
  } catch (err2) {
48322
48363
  const reason = err2 instanceof Error ? err2.message : String(err2);
48323
48364
  process.stderr.write(
@@ -48328,7 +48369,7 @@ async function createServer(config3, cwd, initialWorkspaceId) {
48328
48369
  return server;
48329
48370
  }
48330
48371
  const providers = bundledProviders.map((p, i) => assertToolProvider(p, `bundled provider [${i}]`));
48331
- const ctx = { cwd, tenantId: config3.tenantId, gateway: createGatewayClient(config3), enabledCapabilities: enabled, workspace: createWorkspaceBinding(initialWorkspaceId) };
48372
+ const ctx = { cwd, tenantId: session.tenantId ?? null, gateway: createGatewayClient(session), enabledCapabilities: enabled, workspace: createWorkspaceBinding(initialWorkspaceId) };
48332
48373
  mountProviders(server, selectEntitled(providers, enabled), ctx);
48333
48374
  return server;
48334
48375
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@subseam/mcp",
3
- "version": "0.4.1",
3
+ "version": "0.4.11",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {