@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.
- package/README.md +10 -7
- package/dist/main.js +58 -17
- 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.
|
|
16
|
-
|
|
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
|
|
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/
|
|
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
|
|
65
|
-
environment
|
|
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://
|
|
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
|
-
|
|
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(/\/$/, "")}
|
|
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
|
-
|
|
28213
|
-
|
|
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}`,
|
|
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
|
-
|
|
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`
|
|
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).
|
|
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}"
|
|
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.
|
|
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(
|
|
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:
|
|
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
|
}
|