@subseam/mcp 0.4.2 → 0.4.12
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 +14 -10
- package/dist/main.js +47 -11
- 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
|
|
|
@@ -37,12 +36,13 @@ S='<the cookie value>'; C=$(openssl rand -hex 16)
|
|
|
37
36
|
mkdir -p ~/.subseam && curl -s -X POST https://subseam.theconstructory.com/tokens \
|
|
38
37
|
-H "cookie: __Host-subseam_session=$S; subseam_csrf=$C" -H "x-csrf-token: $C" \
|
|
39
38
|
-H 'content-type: application/json' -d '{"label":"my-machine"}' \
|
|
40
|
-
| node -e 'process.stdin.on("data",d=>process.stdout.write(
|
|
39
|
+
| node -e 'let b="";process.stdin.on("data",d=>b+=d).on("end",()=>{let t;try{t=JSON.parse(b).token}catch{}if(typeof t==="string")process.stdout.write(t);else{console.error("subseam refused the token request: "+b);process.exit(1)}})' > ~/.subseam/token
|
|
41
40
|
chmod 600 ~/.subseam/token
|
|
42
41
|
```
|
|
43
42
|
|
|
44
|
-
`~/.subseam/token` now holds one line starting `cpt_`. If
|
|
45
|
-
|
|
43
|
+
`~/.subseam/token` now holds one line starting `cpt_`. If the command prints "subseam refused the
|
|
44
|
+
token request" with the server's answer, the session cookie was wrong or expired: sign in again and
|
|
45
|
+
repeat. The server also accepts the token in the
|
|
46
46
|
`SUBSEAM_TOKEN` environment variable instead of the file.
|
|
47
47
|
|
|
48
48
|
## 3. Install
|
|
@@ -54,15 +54,19 @@ npm install @subseam/mcp
|
|
|
54
54
|
|
|
55
55
|
## 4. Register it with your agent
|
|
56
56
|
|
|
57
|
-
For Claude Code
|
|
57
|
+
For Claude Code:
|
|
58
58
|
|
|
59
59
|
```
|
|
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/
|
|
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/subseam"}}'
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
`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
|
|
64
|
+
`node ~/.subseam/mcp/node_modules/@subseam/mcp/dist/main.js` over stdio with the same
|
|
65
|
+
environment.
|
|
66
|
+
|
|
67
|
+
The server acts for your organization. If you belong to several, it mounts only
|
|
68
|
+
`subseam_unavailable`, which lists each one's name and id: add `"SUBSEAM_TENANT":"<id>"` to the
|
|
69
|
+
`env` above for the one this agent works in.
|
|
66
70
|
|
|
67
71
|
## 5. Check it works
|
|
68
72
|
|
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
|
});
|
|
@@ -48311,7 +48336,7 @@ var workReadTools = createWorkReadToolProviders({ capability: "work" });
|
|
|
48311
48336
|
var bundledProviders = [src_default2, src_default, tension_default, ...defineTools, ...decisionTools, ...captureTools, ...buildTools, secureTool, ...workTools, ...workReadTools];
|
|
48312
48337
|
|
|
48313
48338
|
// package.json
|
|
48314
|
-
var version2 = "0.4.
|
|
48339
|
+
var version2 = "0.4.12";
|
|
48315
48340
|
|
|
48316
48341
|
// src/server.ts
|
|
48317
48342
|
var SERVER_VERSION = version2;
|
|
@@ -48320,9 +48345,20 @@ function selectEntitled(providers, enabled) {
|
|
|
48320
48345
|
}
|
|
48321
48346
|
async function createServer(config3, cwd, initialWorkspaceId) {
|
|
48322
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
|
+
}
|
|
48323
48359
|
let enabled = [];
|
|
48324
48360
|
try {
|
|
48325
|
-
enabled = await fetchEnabledCapabilities(
|
|
48361
|
+
enabled = await fetchEnabledCapabilities(session);
|
|
48326
48362
|
} catch (err2) {
|
|
48327
48363
|
const reason = err2 instanceof Error ? err2.message : String(err2);
|
|
48328
48364
|
process.stderr.write(
|
|
@@ -48333,7 +48369,7 @@ async function createServer(config3, cwd, initialWorkspaceId) {
|
|
|
48333
48369
|
return server;
|
|
48334
48370
|
}
|
|
48335
48371
|
const providers = bundledProviders.map((p, i) => assertToolProvider(p, `bundled provider [${i}]`));
|
|
48336
|
-
const ctx = { cwd, tenantId:
|
|
48372
|
+
const ctx = { cwd, tenantId: session.tenantId ?? null, gateway: createGatewayClient(session), enabledCapabilities: enabled, workspace: createWorkspaceBinding(initialWorkspaceId) };
|
|
48337
48373
|
mountProviders(server, selectEntitled(providers, enabled), ctx);
|
|
48338
48374
|
return server;
|
|
48339
48375
|
}
|