@systemdox/mcp-server 0.1.49 → 0.1.51
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 +30 -1
- package/dist/cli.js +578 -169
- package/dist/http.js +1107 -730
- package/dist/index.d.ts +2 -2
- package/dist/index.js +751 -371
- package/dist/install-skill.js +13 -16
- package/dist/lib/api-client.d.ts +2 -3
- package/dist/lib/api-client.js +7 -14
- package/dist/lib/context-builder.js +14 -7
- package/dist/lib/session-brief.d.ts +59 -0
- package/dist/lib/session-brief.js +174 -0
- package/dist/lib/skills.d.ts +7 -0
- package/dist/lib/skills.js +7 -2
- package/dist/lib/team-setup-types.d.ts +18 -0
- package/dist/plugin-path.js +7 -14
- package/dist/server.d.ts +5 -1
- package/dist/server.js +3582 -0
- package/dist/setup-mcp.js +11 -15
- package/dist/setup-team.js +7 -14
- package/dist/sync-memory.js +7 -14
- package/dist/tools/document-graph.d.ts +87 -0
- package/dist/tools/get-team-setup.d.ts +6 -0
- package/dist/tools/skills.js +7 -3
- package/dist/tools/team-setup.js +2 -1
- package/package.json +1 -1
package/dist/setup-mcp.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/setup-mcp.ts
|
|
4
4
|
import { existsSync as existsSync3, readFileSync } from "fs";
|
|
5
|
+
import { mkdir as mkdir4 } from "fs/promises";
|
|
5
6
|
import { join as join4 } from "path";
|
|
6
7
|
|
|
7
8
|
// src/lib/cache.ts
|
|
@@ -64,14 +65,6 @@ var SystemDoxApiClient = class {
|
|
|
64
65
|
this.config = config;
|
|
65
66
|
this.cache = new ResponseCache(config.cacheTtlMs);
|
|
66
67
|
}
|
|
67
|
-
async validate() {
|
|
68
|
-
const res = await this.fetchRaw("/v1/health");
|
|
69
|
-
if (!res.ok) {
|
|
70
|
-
throw new Error(
|
|
71
|
-
`SystemDox API authentication failed (${res.status}). Check your SYSTEMDOX_API_KEY.`
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
68
|
async get(path, params) {
|
|
76
69
|
const url = this.buildUrl(path, params);
|
|
77
70
|
const cacheKey = url.toString();
|
|
@@ -85,8 +78,8 @@ var SystemDoxApiClient = class {
|
|
|
85
78
|
}
|
|
86
79
|
/** A read that skips the cache: an install must take the version that is
|
|
87
80
|
* latest now, not the one cached before someone published another. */
|
|
88
|
-
async getFresh(path) {
|
|
89
|
-
const res = await this.fetchRaw(path);
|
|
81
|
+
async getFresh(path, signal) {
|
|
82
|
+
const res = await this.fetchRaw(path, void 0, signal);
|
|
90
83
|
if (!res.ok) throw new SystemDoxApiError(res.status, await res.text());
|
|
91
84
|
return await res.json();
|
|
92
85
|
}
|
|
@@ -116,8 +109,8 @@ var SystemDoxApiClient = class {
|
|
|
116
109
|
// One method per operationId. Every read skips the cache: setup, sync and
|
|
117
110
|
// plugin-path act on what the workspace holds now.
|
|
118
111
|
/** getOwnTeamSetup: the key's own workspace, named in `workspace`. */
|
|
119
|
-
getOwnTeamSetup() {
|
|
120
|
-
return this.getFresh(TEAM_SETUP);
|
|
112
|
+
getOwnTeamSetup(signal) {
|
|
113
|
+
return this.getFresh(TEAM_SETUP, signal);
|
|
121
114
|
}
|
|
122
115
|
getTeamSetup(workspace) {
|
|
123
116
|
return this.getFresh(`${TEAM_SETUP}/${seg(workspace)}`);
|
|
@@ -221,14 +214,15 @@ var SystemDoxApiClient = class {
|
|
|
221
214
|
}
|
|
222
215
|
return url;
|
|
223
216
|
}
|
|
224
|
-
async fetchRaw(path, params) {
|
|
217
|
+
async fetchRaw(path, params, signal) {
|
|
225
218
|
const url = this.buildUrl(path, params);
|
|
226
219
|
return fetch(url, {
|
|
227
220
|
headers: {
|
|
228
221
|
Authorization: `Bearer ${this.config.apiKey}`,
|
|
229
222
|
"X-API-Key": this.config.apiKey,
|
|
230
223
|
Accept: "application/json"
|
|
231
|
-
}
|
|
224
|
+
},
|
|
225
|
+
signal
|
|
232
226
|
});
|
|
233
227
|
}
|
|
234
228
|
};
|
|
@@ -598,6 +592,7 @@ async function setupMcpServer(opts) {
|
|
|
598
592
|
}
|
|
599
593
|
try {
|
|
600
594
|
if ((await readCredentials())[workspace]?.apiKey !== apiKey) await writeCredentials(workspace, apiKey);
|
|
595
|
+
await mkdir4(join4(homeDir(), ".claude", "skills"), { recursive: true });
|
|
601
596
|
const several = Object.keys(await readCredentials()).length > 1;
|
|
602
597
|
for (const target of selected) {
|
|
603
598
|
const removed = await writeServerEntry(target.path, apiUrl, several ? workspace : void 0);
|
|
@@ -615,9 +610,10 @@ async function setupMcpServer(opts) {
|
|
|
615
610
|
Your key for ${workspace} is in ${credentialsPath()} (readable by you alone), not in these configs.
|
|
616
611
|
|
|
617
612
|
Done! Restart your AI coding tool to activate the SystemDox MCP server.
|
|
618
|
-
The server provides
|
|
613
|
+
The server provides 25 tools: get_context, get_team_setup, get_checks,
|
|
619
614
|
get_project_rules, get_prompt_templates, get_specs, get_docs,
|
|
620
615
|
get_tech_stack, get_planning_context, get_plan_board, get_change,
|
|
616
|
+
get_document_links, trace_document_path, get_graph_stats,
|
|
621
617
|
create_check, create_project_rule, create_prompt_template,
|
|
622
618
|
update_spec, report_decision, publish_skill, list_skills,
|
|
623
619
|
install_skill, publish_setup_part, resolve_person, sync_memory.
|
package/dist/setup-team.js
CHANGED
|
@@ -68,14 +68,6 @@ var SystemDoxApiClient = class {
|
|
|
68
68
|
this.config = config;
|
|
69
69
|
this.cache = new ResponseCache(config.cacheTtlMs);
|
|
70
70
|
}
|
|
71
|
-
async validate() {
|
|
72
|
-
const res = await this.fetchRaw("/v1/health");
|
|
73
|
-
if (!res.ok) {
|
|
74
|
-
throw new Error(
|
|
75
|
-
`SystemDox API authentication failed (${res.status}). Check your SYSTEMDOX_API_KEY.`
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
71
|
async get(path, params) {
|
|
80
72
|
const url = this.buildUrl(path, params);
|
|
81
73
|
const cacheKey = url.toString();
|
|
@@ -89,8 +81,8 @@ var SystemDoxApiClient = class {
|
|
|
89
81
|
}
|
|
90
82
|
/** A read that skips the cache: an install must take the version that is
|
|
91
83
|
* latest now, not the one cached before someone published another. */
|
|
92
|
-
async getFresh(path) {
|
|
93
|
-
const res = await this.fetchRaw(path);
|
|
84
|
+
async getFresh(path, signal) {
|
|
85
|
+
const res = await this.fetchRaw(path, void 0, signal);
|
|
94
86
|
if (!res.ok) throw new SystemDoxApiError(res.status, await res.text());
|
|
95
87
|
return await res.json();
|
|
96
88
|
}
|
|
@@ -120,8 +112,8 @@ var SystemDoxApiClient = class {
|
|
|
120
112
|
// One method per operationId. Every read skips the cache: setup, sync and
|
|
121
113
|
// plugin-path act on what the workspace holds now.
|
|
122
114
|
/** getOwnTeamSetup: the key's own workspace, named in `workspace`. */
|
|
123
|
-
getOwnTeamSetup() {
|
|
124
|
-
return this.getFresh(TEAM_SETUP);
|
|
115
|
+
getOwnTeamSetup(signal) {
|
|
116
|
+
return this.getFresh(TEAM_SETUP, signal);
|
|
125
117
|
}
|
|
126
118
|
getTeamSetup(workspace) {
|
|
127
119
|
return this.getFresh(`${TEAM_SETUP}/${seg(workspace)}`);
|
|
@@ -225,14 +217,15 @@ var SystemDoxApiClient = class {
|
|
|
225
217
|
}
|
|
226
218
|
return url;
|
|
227
219
|
}
|
|
228
|
-
async fetchRaw(path, params) {
|
|
220
|
+
async fetchRaw(path, params, signal) {
|
|
229
221
|
const url = this.buildUrl(path, params);
|
|
230
222
|
return fetch(url, {
|
|
231
223
|
headers: {
|
|
232
224
|
Authorization: `Bearer ${this.config.apiKey}`,
|
|
233
225
|
"X-API-Key": this.config.apiKey,
|
|
234
226
|
Accept: "application/json"
|
|
235
|
-
}
|
|
227
|
+
},
|
|
228
|
+
signal
|
|
236
229
|
});
|
|
237
230
|
}
|
|
238
231
|
};
|
package/dist/sync-memory.js
CHANGED
|
@@ -65,14 +65,6 @@ var SystemDoxApiClient = class {
|
|
|
65
65
|
this.config = config;
|
|
66
66
|
this.cache = new ResponseCache(config.cacheTtlMs);
|
|
67
67
|
}
|
|
68
|
-
async validate() {
|
|
69
|
-
const res = await this.fetchRaw("/v1/health");
|
|
70
|
-
if (!res.ok) {
|
|
71
|
-
throw new Error(
|
|
72
|
-
`SystemDox API authentication failed (${res.status}). Check your SYSTEMDOX_API_KEY.`
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
68
|
async get(path, params) {
|
|
77
69
|
const url = this.buildUrl(path, params);
|
|
78
70
|
const cacheKey = url.toString();
|
|
@@ -86,8 +78,8 @@ var SystemDoxApiClient = class {
|
|
|
86
78
|
}
|
|
87
79
|
/** A read that skips the cache: an install must take the version that is
|
|
88
80
|
* latest now, not the one cached before someone published another. */
|
|
89
|
-
async getFresh(path) {
|
|
90
|
-
const res = await this.fetchRaw(path);
|
|
81
|
+
async getFresh(path, signal) {
|
|
82
|
+
const res = await this.fetchRaw(path, void 0, signal);
|
|
91
83
|
if (!res.ok) throw new SystemDoxApiError(res.status, await res.text());
|
|
92
84
|
return await res.json();
|
|
93
85
|
}
|
|
@@ -117,8 +109,8 @@ var SystemDoxApiClient = class {
|
|
|
117
109
|
// One method per operationId. Every read skips the cache: setup, sync and
|
|
118
110
|
// plugin-path act on what the workspace holds now.
|
|
119
111
|
/** getOwnTeamSetup: the key's own workspace, named in `workspace`. */
|
|
120
|
-
getOwnTeamSetup() {
|
|
121
|
-
return this.getFresh(TEAM_SETUP);
|
|
112
|
+
getOwnTeamSetup(signal) {
|
|
113
|
+
return this.getFresh(TEAM_SETUP, signal);
|
|
122
114
|
}
|
|
123
115
|
getTeamSetup(workspace) {
|
|
124
116
|
return this.getFresh(`${TEAM_SETUP}/${seg(workspace)}`);
|
|
@@ -222,14 +214,15 @@ var SystemDoxApiClient = class {
|
|
|
222
214
|
}
|
|
223
215
|
return url;
|
|
224
216
|
}
|
|
225
|
-
async fetchRaw(path, params) {
|
|
217
|
+
async fetchRaw(path, params, signal) {
|
|
226
218
|
const url = this.buildUrl(path, params);
|
|
227
219
|
return fetch(url, {
|
|
228
220
|
headers: {
|
|
229
221
|
Authorization: `Bearer ${this.config.apiKey}`,
|
|
230
222
|
"X-API-Key": this.config.apiKey,
|
|
231
223
|
Accept: "application/json"
|
|
232
|
-
}
|
|
224
|
+
},
|
|
225
|
+
signal
|
|
233
226
|
});
|
|
234
227
|
}
|
|
235
228
|
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { SystemDoxApiClient } from "../lib/api-client.js";
|
|
3
|
+
/**
|
|
4
|
+
* The document graph over MCP (ADR-224, specs/changes/document-graph/contract.md §5).
|
|
5
|
+
* Three read-only tools on the hosted and the local server. Every answer says
|
|
6
|
+
* how each link is known, and one that was cut says so.
|
|
7
|
+
*/
|
|
8
|
+
interface GraphNode {
|
|
9
|
+
id: string;
|
|
10
|
+
label: string;
|
|
11
|
+
type: string;
|
|
12
|
+
repo: string;
|
|
13
|
+
path: string;
|
|
14
|
+
}
|
|
15
|
+
interface NeighbourLink {
|
|
16
|
+
direction: "out" | "in";
|
|
17
|
+
relation: string;
|
|
18
|
+
provenance: string;
|
|
19
|
+
confidence: number;
|
|
20
|
+
file: string;
|
|
21
|
+
line: number;
|
|
22
|
+
other: GraphNode;
|
|
23
|
+
}
|
|
24
|
+
interface AmbiguousLink {
|
|
25
|
+
reference: string;
|
|
26
|
+
file: string;
|
|
27
|
+
line: number;
|
|
28
|
+
candidates: GraphNode[];
|
|
29
|
+
}
|
|
30
|
+
interface Neighbours {
|
|
31
|
+
doc: GraphNode;
|
|
32
|
+
links: NeighbourLink[];
|
|
33
|
+
ambiguous: AmbiguousLink[];
|
|
34
|
+
total: number;
|
|
35
|
+
truncated: boolean;
|
|
36
|
+
}
|
|
37
|
+
interface PathStep {
|
|
38
|
+
from: GraphNode;
|
|
39
|
+
to: GraphNode;
|
|
40
|
+
relation: string;
|
|
41
|
+
provenance: string;
|
|
42
|
+
confidence: number;
|
|
43
|
+
file: string;
|
|
44
|
+
line: number;
|
|
45
|
+
reversed: boolean;
|
|
46
|
+
}
|
|
47
|
+
interface GraphPath {
|
|
48
|
+
found: boolean;
|
|
49
|
+
hops: number;
|
|
50
|
+
confidence: number;
|
|
51
|
+
steps: PathStep[];
|
|
52
|
+
}
|
|
53
|
+
interface RepoFreshness {
|
|
54
|
+
repo: string;
|
|
55
|
+
documents: number;
|
|
56
|
+
links: number;
|
|
57
|
+
indexedCommit: string;
|
|
58
|
+
indexedAt: string;
|
|
59
|
+
headCommit: string;
|
|
60
|
+
pushedAt: string;
|
|
61
|
+
behind: boolean;
|
|
62
|
+
}
|
|
63
|
+
interface GraphStats {
|
|
64
|
+
nodeCount: number;
|
|
65
|
+
edgeCount: number;
|
|
66
|
+
links: {
|
|
67
|
+
declared: number;
|
|
68
|
+
extracted: number;
|
|
69
|
+
inferred: number;
|
|
70
|
+
ambiguous: number;
|
|
71
|
+
};
|
|
72
|
+
repos: RepoFreshness[];
|
|
73
|
+
}
|
|
74
|
+
interface RepoLink {
|
|
75
|
+
source: string;
|
|
76
|
+
target: string;
|
|
77
|
+
relation: string;
|
|
78
|
+
provenance: string;
|
|
79
|
+
confidence: number;
|
|
80
|
+
file: string;
|
|
81
|
+
line: number;
|
|
82
|
+
}
|
|
83
|
+
export declare function formatNeighbours(answer: Neighbours): string;
|
|
84
|
+
export declare function formatPath(answer: GraphPath, maxHops: number): string;
|
|
85
|
+
export declare function formatStats(stats: GraphStats, repoLinks: RepoLink[]): string;
|
|
86
|
+
export declare function registerDocumentGraph(server: McpServer, apiClient: SystemDoxApiClient): void;
|
|
87
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { SystemDoxApiClient } from "../lib/api-client.js";
|
|
3
|
+
export declare function registerGetTeamSetup(server: McpServer, apiClient: SystemDoxApiClient, { localFiles, keyProblem }: {
|
|
4
|
+
localFiles: boolean;
|
|
5
|
+
keyProblem?: string;
|
|
6
|
+
}): void;
|
package/dist/tools/skills.js
CHANGED
|
@@ -29,6 +29,7 @@ import { homedir } from "os";
|
|
|
29
29
|
import { dirname, join, relative, resolve, sep } from "path";
|
|
30
30
|
var MANIFEST_FILE = ".systemdox-skill.json";
|
|
31
31
|
var ENTRY_FILE = "SKILL.md";
|
|
32
|
+
var PLUGIN_MANIFEST_FOLDER = ".claude-plugin";
|
|
32
33
|
var MAX_FILES = 100;
|
|
33
34
|
var MAX_FILE_BYTES = 256 * 1024;
|
|
34
35
|
var MAX_TOTAL_BYTES = 1024 * 1024;
|
|
@@ -53,7 +54,7 @@ function isValidName(name) {
|
|
|
53
54
|
return name.length <= 64 && NAME_RE.test(name);
|
|
54
55
|
}
|
|
55
56
|
function isValidPath(path) {
|
|
56
|
-
return path.length > 0 && path.length <= 512 && !path.startsWith("/") && path.split("/").every((s) => SEGMENT_RE.test(s) && s !== "." && s !== "..");
|
|
57
|
+
return path.length > 0 && path.length <= 512 && !path.startsWith("/") && path.split("/").every((s) => SEGMENT_RE.test(s) && s !== "." && s !== ".." && s !== PLUGIN_MANIFEST_FOLDER);
|
|
57
58
|
}
|
|
58
59
|
function frontmatterName(skillMd) {
|
|
59
60
|
const block = /^---\r?\n([\s\S]*?)\r?\n---/.exec(skillMd);
|
|
@@ -202,6 +203,9 @@ function fenceFor(text2) {
|
|
|
202
203
|
const longest = Math.max(0, ...(text2.match(/`+/g) ?? []).map((r) => r.length));
|
|
203
204
|
return "`".repeat(Math.max(3, longest + 1));
|
|
204
205
|
}
|
|
206
|
+
function afterInstall(skillMd) {
|
|
207
|
+
return `No restart is needed: a running Claude Code session picks it up. If one does not list it yet, read ${skillMd} and follow it, or run /reload-skills.`;
|
|
208
|
+
}
|
|
205
209
|
function describeInstall(name, r) {
|
|
206
210
|
const lines = [
|
|
207
211
|
`Installed ${name} version ${r.version} into ${r.dest} (${r.written.length} file(s)).`
|
|
@@ -211,7 +215,7 @@ function describeInstall(name, r) {
|
|
|
211
215
|
lines.push(`Kept, edited here and no longer in the skill: ${r.keptEdited.join(", ")}`);
|
|
212
216
|
}
|
|
213
217
|
if (r.keptUntracked.length) lines.push(`Kept, added here: ${r.keptUntracked.join(", ")}`);
|
|
214
|
-
lines.push("
|
|
218
|
+
lines.push(afterInstall(join(r.dest, "SKILL.md")));
|
|
215
219
|
return lines.join("\n");
|
|
216
220
|
}
|
|
217
221
|
|
|
@@ -341,7 +345,7 @@ ${fence}
|
|
|
341
345
|
${f.content}${f.content.endsWith("\n") ? "" : "\n"}${fence}`;
|
|
342
346
|
}).join("\n\n");
|
|
343
347
|
return text(
|
|
344
|
-
`Skill **${version.name}** version ${version.version}: write these ${version.files.length} file(s) into ${dest}/ exactly as shown (the content between the fences), creating folders as needed. If a file there differs from ${MANIFEST_FILE}'s hash for it, it was edited: ask before replacing it. Then write ${MANIFEST_FILE} with this content
|
|
348
|
+
`Skill **${version.name}** version ${version.version}: write these ${version.files.length} file(s) into ${dest}/ exactly as shown (the content between the fences), creating folders as needed. If a file there differs from ${MANIFEST_FILE}'s hash for it, it was edited: ask before replacing it. Then write ${MANIFEST_FILE} with this content. ${afterInstall(`${dest}/SKILL.md`)}
|
|
345
349
|
|
|
346
350
|
### ${MANIFEST_FILE}
|
|
347
351
|
|
package/dist/tools/team-setup.js
CHANGED
|
@@ -14,13 +14,14 @@ import { existsSync } from "fs";
|
|
|
14
14
|
import { homedir } from "os";
|
|
15
15
|
import { dirname, join, relative, resolve, sep } from "path";
|
|
16
16
|
var MANIFEST_FILE = ".systemdox-skill.json";
|
|
17
|
+
var PLUGIN_MANIFEST_FOLDER = ".claude-plugin";
|
|
17
18
|
var MAX_FILES = 100;
|
|
18
19
|
var MAX_FILE_BYTES = 256 * 1024;
|
|
19
20
|
var MAX_TOTAL_BYTES = 1024 * 1024;
|
|
20
21
|
var SEGMENT_RE = /^[A-Za-z0-9._-]+$/;
|
|
21
22
|
var SKIPPED = /* @__PURE__ */ new Set([MANIFEST_FILE, ".git", "node_modules", "__pycache__", ".DS_Store"]);
|
|
22
23
|
function isValidPath(path) {
|
|
23
|
-
return path.length > 0 && path.length <= 512 && !path.startsWith("/") && path.split("/").every((s) => SEGMENT_RE.test(s) && s !== "." && s !== "..");
|
|
24
|
+
return path.length > 0 && path.length <= 512 && !path.startsWith("/") && path.split("/").every((s) => SEGMENT_RE.test(s) && s !== "." && s !== ".." && s !== PLUGIN_MANIFEST_FOLDER);
|
|
24
25
|
}
|
|
25
26
|
async function walk(root, { skipHidden }, dir = root, out = { files: [], links: [], hidden: [] }) {
|
|
26
27
|
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@systemdox/mcp-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.51",
|
|
4
4
|
"mcpName": "io.github.puglieseweb/systemdox",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Give AI coding agents your architecture context over MCP — checks, specs, ADRs, project rules, and docs from SystemDox, delivered at write time",
|