@systemdox/mcp-server 0.1.55 → 0.1.57
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 +25 -5
- package/dist/cli.js +80 -13
- package/dist/http.js +1825 -1752
- package/dist/index.js +79 -12
- package/dist/install-skill.js +28 -3
- package/dist/lib/api-client.d.ts +4 -0
- package/dist/lib/api-client.js +28 -3
- package/dist/lib/config.d.ts +6 -0
- package/dist/lib/mcp-answer.d.ts +18 -0
- package/dist/plugin-path.js +28 -3
- package/dist/server.d.ts +5 -1
- package/dist/server.js +51 -9
- package/dist/setup-mcp.js +28 -3
- package/dist/setup-team.js +28 -3
- package/dist/sync-memory.js +28 -3
- package/package.json +10 -1
package/README.md
CHANGED
|
@@ -25,8 +25,10 @@ MCP-compatible tool read that context — and write decisions back.
|
|
|
25
25
|
|
|
26
26
|
1. SystemDox is in private preview. [Request early access](https://www.systemdox.com)
|
|
27
27
|
to get a workspace.
|
|
28
|
-
2. Connect a GitHub repository
|
|
29
|
-
3.
|
|
28
|
+
2. Connect a GitHub repository.
|
|
29
|
+
3. Paste `https://<your-workspace>.mcp.systemdox.com/connect` into Claude and
|
|
30
|
+
sign in, or generate an API key (workspace settings) and add the server to
|
|
31
|
+
your tool — hosted (Option A) or local (Option B) below.
|
|
30
32
|
|
|
31
33
|
Docs: [MCP setup guide](https://www.systemdox.com/guides/mcp-setup) ·
|
|
32
34
|
[Documentation standard](https://www.systemdox.com/guides/documentation-standard) ·
|
|
@@ -34,9 +36,27 @@ Docs: [MCP setup guide](https://www.systemdox.com/guides/mcp-setup) ·
|
|
|
34
36
|
|
|
35
37
|
---
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
## Connect by signing in
|
|
40
|
+
|
|
41
|
+
Your workspace has its own address. Paste it into Claude's Settings → Connectors,
|
|
42
|
+
or run:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
claude mcp add --transport http systemdox https://<your-workspace>.mcp.systemdox.com/connect
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Your browser opens SystemDox. Sign in with your second factor and approve. It only
|
|
49
|
+
reads unless you choose to allow writes. Claude holds a token for an hour, renewed
|
|
50
|
+
for you, and the API key behind the connection stays with SystemDox. Only an owner
|
|
51
|
+
or admin of the workspace can connect this way for now, and an account that signs
|
|
52
|
+
in only with Google or Apple cannot.
|
|
53
|
+
|
|
54
|
+
## With an API key
|
|
55
|
+
|
|
56
|
+
There are two more ways to run it, for scripts, CI and other tools. Both
|
|
57
|
+
authenticate with a SystemDox API key (generate one on your SystemDox settings
|
|
58
|
+
page) and act strictly as the caller — every tool call is scoped to that key's
|
|
59
|
+
tenant.
|
|
40
60
|
|
|
41
61
|
## Option A — Hosted endpoint (no install)
|
|
42
62
|
|
package/dist/cli.js
CHANGED
|
@@ -267,6 +267,17 @@ var init_cache = __esm({
|
|
|
267
267
|
});
|
|
268
268
|
|
|
269
269
|
// src/lib/api-client.ts
|
|
270
|
+
function unescapedPath(path) {
|
|
271
|
+
let current = path.split(/[?#]/, 1)[0];
|
|
272
|
+
for (; ; ) {
|
|
273
|
+
const next = current.replace(/[\t\n\r]/g, "").replace(/%(?:2e|2f|5c|25|09|0a|0d)/gi, (escape) => decodeURIComponent(escape));
|
|
274
|
+
if (next === current) return current;
|
|
275
|
+
current = next;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function walksThePath(path) {
|
|
279
|
+
return unescapedPath(path).split(/[/\\]/).some((segment) => segment === "." || segment === "..");
|
|
280
|
+
}
|
|
270
281
|
var SystemDoxApiError, TEAM_SETUP, seg, SystemDoxApiClient;
|
|
271
282
|
var init_api_client = __esm({
|
|
272
283
|
"src/lib/api-client.ts"() {
|
|
@@ -327,7 +338,7 @@ var init_api_client = __esm({
|
|
|
327
338
|
}
|
|
328
339
|
async mutate(method, path, body) {
|
|
329
340
|
const url = this.buildUrl(path);
|
|
330
|
-
const res = await
|
|
341
|
+
const res = await this.call(url, {
|
|
331
342
|
method,
|
|
332
343
|
headers: {
|
|
333
344
|
// The API Gateway authorizer only reads the Authorization header;
|
|
@@ -377,7 +388,7 @@ var init_api_client = __esm({
|
|
|
377
388
|
/** getPluginZip: the zip's bytes as served, and its X-Content-SHA256
|
|
378
389
|
* header. The caller checks the BYTES against the hash it was given. */
|
|
379
390
|
async getPluginZip(workspace, version) {
|
|
380
|
-
const res = await
|
|
391
|
+
const res = await this.call(
|
|
381
392
|
this.buildUrl(`${TEAM_SETUP}/${seg(workspace)}/plugin/versions/${seg(version)}/zip`),
|
|
382
393
|
{
|
|
383
394
|
headers: {
|
|
@@ -443,6 +454,10 @@ var init_api_client = __esm({
|
|
|
443
454
|
buildUrl(path, params) {
|
|
444
455
|
const base = this.config.apiUrl.replace(/\/+$/, "");
|
|
445
456
|
const url = new URL(`${base}${path.startsWith("/") ? "" : "/"}${path}`);
|
|
457
|
+
const { origin, pathname } = new URL(base);
|
|
458
|
+
if (walksThePath(path) || url.origin !== origin || !url.pathname.startsWith(`${pathname.replace(/\/+$/, "")}/`)) {
|
|
459
|
+
throw new Error("Refused: this id leaves the API's own path.");
|
|
460
|
+
}
|
|
446
461
|
if (params) {
|
|
447
462
|
for (const [key, value] of Object.entries(params)) {
|
|
448
463
|
url.searchParams.set(key, value);
|
|
@@ -452,7 +467,7 @@ var init_api_client = __esm({
|
|
|
452
467
|
}
|
|
453
468
|
async fetchRaw(path, params, signal) {
|
|
454
469
|
const url = this.buildUrl(path, params);
|
|
455
|
-
return
|
|
470
|
+
return this.call(url, {
|
|
456
471
|
headers: {
|
|
457
472
|
Authorization: `Bearer ${this.config.apiKey}`,
|
|
458
473
|
"X-API-Key": this.config.apiKey,
|
|
@@ -461,6 +476,16 @@ var init_api_client = __esm({
|
|
|
461
476
|
signal
|
|
462
477
|
});
|
|
463
478
|
}
|
|
479
|
+
/** Every call of this client, so `onAnswer` hears each answer. */
|
|
480
|
+
async call(url, init) {
|
|
481
|
+
const res = await fetch(url, init);
|
|
482
|
+
const { onAnswer } = this.config;
|
|
483
|
+
if (onAnswer) {
|
|
484
|
+
const refused2 = res.ok ? void 0 : new SystemDoxApiError(res.status, await res.clone().text());
|
|
485
|
+
onAnswer({ status: res.status, code: refused2?.code });
|
|
486
|
+
}
|
|
487
|
+
return res;
|
|
488
|
+
}
|
|
464
489
|
};
|
|
465
490
|
}
|
|
466
491
|
});
|
|
@@ -2889,7 +2914,7 @@ function registerGetSpecs(server, apiClient2) {
|
|
|
2889
2914
|
async ({ draft_id, status, search }) => {
|
|
2890
2915
|
if (draft_id) {
|
|
2891
2916
|
const spec = await apiClient2.get(
|
|
2892
|
-
`/v1/specs/${draft_id}`
|
|
2917
|
+
`/v1/specs/${encodeURIComponent(draft_id)}`
|
|
2893
2918
|
);
|
|
2894
2919
|
const sections = [`# ${spec.name}
|
|
2895
2920
|
|
|
@@ -4617,7 +4642,8 @@ function registerUpdateSpec(server, apiClient2) {
|
|
|
4617
4642
|
}
|
|
4618
4643
|
},
|
|
4619
4644
|
async ({ draft_id, section, content, requirement_name }) => {
|
|
4620
|
-
const
|
|
4645
|
+
const specPath = `/v1/specs/${encodeURIComponent(draft_id)}`;
|
|
4646
|
+
const spec = await apiClient2.get(specPath);
|
|
4621
4647
|
if (spec.status !== "draft") {
|
|
4622
4648
|
return {
|
|
4623
4649
|
content: [
|
|
@@ -4642,8 +4668,8 @@ function registerUpdateSpec(server, apiClient2) {
|
|
|
4642
4668
|
};
|
|
4643
4669
|
body.requirements = [...existing, newReq];
|
|
4644
4670
|
}
|
|
4645
|
-
await apiClient2.put(
|
|
4646
|
-
apiClient2.invalidateCache(
|
|
4671
|
+
await apiClient2.put(specPath, body);
|
|
4672
|
+
apiClient2.invalidateCache(specPath);
|
|
4647
4673
|
apiClient2.invalidateCache("/v1/specs");
|
|
4648
4674
|
return {
|
|
4649
4675
|
content: [
|
|
@@ -4707,7 +4733,7 @@ ${decision}
|
|
|
4707
4733
|
## Consequences
|
|
4708
4734
|
|
|
4709
4735
|
${consequences}` + relatedSection;
|
|
4710
|
-
await apiClient2.put(`/v1/specs/${created.draftId}`, {
|
|
4736
|
+
await apiClient2.put(`/v1/specs/${encodeURIComponent(created.draftId)}`, {
|
|
4711
4737
|
proposalContent: adrContent
|
|
4712
4738
|
});
|
|
4713
4739
|
apiClient2.invalidateCache("/v1/specs");
|
|
@@ -5294,14 +5320,25 @@ var init_team_setup = __esm({
|
|
|
5294
5320
|
|
|
5295
5321
|
// src/server.ts
|
|
5296
5322
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5297
|
-
function
|
|
5298
|
-
const
|
|
5323
|
+
function offering(server, access) {
|
|
5324
|
+
const offered = new Set(access === "write" ? [...READ_TOOLS, ...WRITE_TOOLS] : READ_TOOLS);
|
|
5325
|
+
return new Proxy(server, {
|
|
5326
|
+
get(target, property) {
|
|
5327
|
+
const value = Reflect.get(target, property, target);
|
|
5328
|
+
if (property !== "registerTool") return typeof value === "function" ? value.bind(target) : value;
|
|
5329
|
+
return (name, ...rest) => offered.has(name) ? target.registerTool(name, ...rest) : void 0;
|
|
5330
|
+
}
|
|
5331
|
+
});
|
|
5332
|
+
}
|
|
5333
|
+
function createServer(apiClient2, { localFiles = false, instructions, keyProblem: keyProblem2, access } = {}) {
|
|
5334
|
+
const mcpServer = new McpServer(
|
|
5299
5335
|
{
|
|
5300
5336
|
name: "systemdox-mcp-server",
|
|
5301
5337
|
version: SERVER_VERSION
|
|
5302
5338
|
},
|
|
5303
5339
|
instructions ? { instructions } : void 0
|
|
5304
5340
|
);
|
|
5341
|
+
const server = access ? offering(mcpServer, access) : mcpServer;
|
|
5305
5342
|
registerGetGuardrails(server, apiClient2);
|
|
5306
5343
|
registerGetSpecs(server, apiClient2);
|
|
5307
5344
|
registerGetDocs(server, apiClient2);
|
|
@@ -5321,9 +5358,9 @@ function createServer(apiClient2, { localFiles = false, instructions, keyProblem
|
|
|
5321
5358
|
registerCreatePromptTemplate(server, apiClient2);
|
|
5322
5359
|
registerSkillTools(server, apiClient2, { localFiles });
|
|
5323
5360
|
registerTeamSetupTools(server, apiClient2, { localFiles });
|
|
5324
|
-
return
|
|
5361
|
+
return mcpServer;
|
|
5325
5362
|
}
|
|
5326
|
-
var SERVER_VERSION;
|
|
5363
|
+
var SERVER_VERSION, READ_TOOLS, WRITE_TOOLS;
|
|
5327
5364
|
var init_server = __esm({
|
|
5328
5365
|
"src/server.ts"() {
|
|
5329
5366
|
"use strict";
|
|
@@ -5346,7 +5383,37 @@ var init_server = __esm({
|
|
|
5346
5383
|
init_create_prompt_template();
|
|
5347
5384
|
init_skills2();
|
|
5348
5385
|
init_team_setup();
|
|
5349
|
-
SERVER_VERSION = true ? "0.1.
|
|
5386
|
+
SERVER_VERSION = true ? "0.1.57" : "0.0.0-dev";
|
|
5387
|
+
READ_TOOLS = [
|
|
5388
|
+
"get_context",
|
|
5389
|
+
"get_checks",
|
|
5390
|
+
"get_guardrails",
|
|
5391
|
+
"get_project_rules",
|
|
5392
|
+
"get_prompt_templates",
|
|
5393
|
+
"get_specs",
|
|
5394
|
+
"get_docs",
|
|
5395
|
+
"get_tech_stack",
|
|
5396
|
+
"get_planning_context",
|
|
5397
|
+
"get_plan_board",
|
|
5398
|
+
"get_change",
|
|
5399
|
+
"get_team_setup",
|
|
5400
|
+
"get_document_links",
|
|
5401
|
+
"trace_document_path",
|
|
5402
|
+
"get_graph_stats",
|
|
5403
|
+
"list_skills",
|
|
5404
|
+
"install_skill",
|
|
5405
|
+
"resolve_person"
|
|
5406
|
+
];
|
|
5407
|
+
WRITE_TOOLS = [
|
|
5408
|
+
"create_check",
|
|
5409
|
+
"create_guardrail",
|
|
5410
|
+
"create_project_rule",
|
|
5411
|
+
"create_prompt_template",
|
|
5412
|
+
"update_spec",
|
|
5413
|
+
"report_decision",
|
|
5414
|
+
"publish_skill",
|
|
5415
|
+
"publish_setup_part"
|
|
5416
|
+
];
|
|
5350
5417
|
}
|
|
5351
5418
|
});
|
|
5352
5419
|
|