@grainulation/silo 1.0.0 → 1.0.1
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/CODE_OF_CONDUCT.md +25 -0
- package/CONTRIBUTING.md +103 -0
- package/README.md +67 -59
- package/bin/silo.js +212 -86
- package/lib/analytics.js +26 -11
- package/lib/confluence.js +343 -0
- package/lib/graph.js +414 -0
- package/lib/import-export.js +29 -24
- package/lib/index.js +15 -9
- package/lib/packs.js +60 -36
- package/lib/search.js +24 -16
- package/lib/serve-mcp.js +391 -95
- package/lib/server.js +205 -110
- package/lib/store.js +34 -18
- package/lib/templates.js +28 -17
- package/package.json +7 -3
- package/packs/adr.json +219 -0
- package/packs/api-design.json +67 -14
- package/packs/architecture-decision.json +152 -0
- package/packs/architecture.json +45 -9
- package/packs/ci-cd.json +51 -11
- package/packs/compliance.json +70 -14
- package/packs/data-engineering.json +57 -12
- package/packs/frontend.json +56 -12
- package/packs/hackathon-best-ai.json +179 -0
- package/packs/hackathon-business-impact.json +180 -0
- package/packs/hackathon-innovation.json +210 -0
- package/packs/hackathon-most-innovative.json +179 -0
- package/packs/hackathon-most-rigorous.json +179 -0
- package/packs/hackathon-sprint-boost.json +173 -0
- package/packs/incident-postmortem.json +219 -0
- package/packs/migration.json +45 -9
- package/packs/observability.json +57 -12
- package/packs/security.json +61 -13
- package/packs/team-process.json +64 -13
- package/packs/testing.json +20 -4
- package/packs/vendor-eval.json +219 -0
- package/packs/vendor-evaluation.json +148 -0
package/lib/serve-mcp.js
CHANGED
|
@@ -19,28 +19,30 @@
|
|
|
19
19
|
* claude mcp add silo -- npx @grainulation/silo serve-mcp
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
const fs = require(
|
|
23
|
-
const path = require(
|
|
24
|
-
const readline = require(
|
|
25
|
-
const { Store } = require(
|
|
26
|
-
const { Search } = require(
|
|
27
|
-
const { ImportExport } = require(
|
|
28
|
-
const { Packs } = require(
|
|
22
|
+
const fs = require("fs");
|
|
23
|
+
const path = require("path");
|
|
24
|
+
const readline = require("readline");
|
|
25
|
+
const { Store } = require("./store.js");
|
|
26
|
+
const { Search } = require("./search.js");
|
|
27
|
+
const { ImportExport } = require("./import-export.js");
|
|
28
|
+
const { Packs } = require("./packs.js");
|
|
29
|
+
const { Graph } = require("./graph.js");
|
|
30
|
+
const { Confluence } = require("./confluence.js");
|
|
29
31
|
|
|
30
32
|
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
31
33
|
|
|
32
|
-
const SERVER_NAME =
|
|
33
|
-
const SERVER_VERSION =
|
|
34
|
-
const PROTOCOL_VERSION =
|
|
34
|
+
const SERVER_NAME = "silo";
|
|
35
|
+
const SERVER_VERSION = "1.0.0";
|
|
36
|
+
const PROTOCOL_VERSION = "2024-11-05";
|
|
35
37
|
|
|
36
38
|
// ─── JSON-RPC helpers ───────────────────────────────────────────────────────
|
|
37
39
|
|
|
38
40
|
function jsonRpcResponse(id, result) {
|
|
39
|
-
return JSON.stringify({ jsonrpc:
|
|
41
|
+
return JSON.stringify({ jsonrpc: "2.0", id, result });
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
function jsonRpcError(id, code, message) {
|
|
43
|
-
return JSON.stringify({ jsonrpc:
|
|
45
|
+
return JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } });
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
// ─── Initialize silo components ─────────────────────────────────────────────
|
|
@@ -49,13 +51,15 @@ const store = new Store();
|
|
|
49
51
|
const search = new Search(store);
|
|
50
52
|
const io = new ImportExport(store);
|
|
51
53
|
const packs = new Packs(store);
|
|
54
|
+
const graph = new Graph(store);
|
|
55
|
+
const confluence = new Confluence();
|
|
52
56
|
|
|
53
57
|
// ─── Tool implementations ───────────────────────────────────────────────────
|
|
54
58
|
|
|
55
59
|
function toolSearch(args) {
|
|
56
60
|
const { query, type, evidence, limit } = args;
|
|
57
61
|
if (!query) {
|
|
58
|
-
return { status:
|
|
62
|
+
return { status: "error", message: "Required field: query" };
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
const results = search.query(query, {
|
|
@@ -65,14 +69,16 @@ function toolSearch(args) {
|
|
|
65
69
|
});
|
|
66
70
|
|
|
67
71
|
return {
|
|
68
|
-
status:
|
|
72
|
+
status: "ok",
|
|
69
73
|
count: results.length,
|
|
70
|
-
claims: results.map(r => ({
|
|
74
|
+
claims: results.map((r) => ({
|
|
71
75
|
id: r.claim.id,
|
|
72
76
|
type: r.claim.type,
|
|
73
77
|
topic: r.claim.topic,
|
|
74
78
|
evidence: r.claim.evidence,
|
|
75
|
-
content:
|
|
79
|
+
content:
|
|
80
|
+
(r.claim.content || r.claim.text || "").slice(0, 200) +
|
|
81
|
+
((r.claim.content || r.claim.text || "").length > 200 ? "..." : ""),
|
|
76
82
|
score: r.score,
|
|
77
83
|
collection: r.collection,
|
|
78
84
|
})),
|
|
@@ -82,9 +88,9 @@ function toolSearch(args) {
|
|
|
82
88
|
function toolList() {
|
|
83
89
|
const collections = store.list();
|
|
84
90
|
return {
|
|
85
|
-
status:
|
|
91
|
+
status: "ok",
|
|
86
92
|
count: collections.length,
|
|
87
|
-
collections: collections.map(c => ({
|
|
93
|
+
collections: collections.map((c) => ({
|
|
88
94
|
id: c.id,
|
|
89
95
|
name: c.name,
|
|
90
96
|
claimCount: c.claimCount,
|
|
@@ -96,134 +102,376 @@ function toolList() {
|
|
|
96
102
|
function toolPull(dir, args) {
|
|
97
103
|
const { pack, into } = args;
|
|
98
104
|
if (!pack) {
|
|
99
|
-
return {
|
|
105
|
+
return {
|
|
106
|
+
status: "error",
|
|
107
|
+
message: "Required field: pack (pack name or collection ID)",
|
|
108
|
+
};
|
|
100
109
|
}
|
|
101
110
|
|
|
102
|
-
const targetFile = into || path.join(dir,
|
|
111
|
+
const targetFile = into || path.join(dir, "claims.json");
|
|
103
112
|
if (!fs.existsSync(targetFile)) {
|
|
104
|
-
return {
|
|
113
|
+
return {
|
|
114
|
+
status: "error",
|
|
115
|
+
message: `Target file not found: ${targetFile}. Run wheat init first.`,
|
|
116
|
+
};
|
|
105
117
|
}
|
|
106
118
|
|
|
107
119
|
try {
|
|
108
120
|
const result = io.pull(pack, targetFile, {});
|
|
109
121
|
return {
|
|
110
|
-
status:
|
|
122
|
+
status: "ok",
|
|
111
123
|
message: `Pulled ${result.imported} claims from "${pack}" into ${path.basename(targetFile)}.`,
|
|
112
124
|
imported: result.imported,
|
|
113
125
|
skipped: result.skipped || 0,
|
|
114
126
|
};
|
|
115
127
|
} catch (err) {
|
|
116
|
-
return { status:
|
|
128
|
+
return { status: "error", message: err.message };
|
|
117
129
|
}
|
|
118
130
|
}
|
|
119
131
|
|
|
120
132
|
function toolStore(dir, args) {
|
|
121
133
|
const { name, from } = args;
|
|
122
134
|
if (!name) {
|
|
123
|
-
return { status:
|
|
135
|
+
return { status: "error", message: "Required field: name" };
|
|
124
136
|
}
|
|
125
137
|
|
|
126
|
-
const sourceFile = from || path.join(dir,
|
|
138
|
+
const sourceFile = from || path.join(dir, "claims.json");
|
|
127
139
|
if (!fs.existsSync(sourceFile)) {
|
|
128
|
-
return { status:
|
|
140
|
+
return { status: "error", message: `Source file not found: ${sourceFile}` };
|
|
129
141
|
}
|
|
130
142
|
|
|
131
143
|
try {
|
|
132
|
-
const data = JSON.parse(fs.readFileSync(sourceFile,
|
|
144
|
+
const data = JSON.parse(fs.readFileSync(sourceFile, "utf8"));
|
|
133
145
|
const claims = data.claims || data;
|
|
134
146
|
const meta = data.meta || {};
|
|
135
147
|
const result = store.storeClaims(name, claims, meta);
|
|
136
148
|
return {
|
|
137
|
-
status:
|
|
149
|
+
status: "ok",
|
|
138
150
|
message: `Stored ${result.claimCount} claims as "${name}".`,
|
|
139
151
|
id: result.id,
|
|
140
152
|
claimCount: result.claimCount,
|
|
141
153
|
hash: result.hash,
|
|
142
154
|
};
|
|
143
155
|
} catch (err) {
|
|
144
|
-
return { status:
|
|
156
|
+
return { status: "error", message: err.message };
|
|
145
157
|
}
|
|
146
158
|
}
|
|
147
159
|
|
|
148
160
|
function toolPacks() {
|
|
149
161
|
const available = packs.list();
|
|
150
162
|
return {
|
|
151
|
-
status:
|
|
163
|
+
status: "ok",
|
|
152
164
|
count: available.length,
|
|
153
|
-
packs: available.map(p => ({
|
|
165
|
+
packs: available.map((p) => ({
|
|
154
166
|
name: p.name,
|
|
155
|
-
description: p.description ||
|
|
167
|
+
description: p.description || "",
|
|
156
168
|
claimCount: p.claims ? p.claims.length : p.claimCount || 0,
|
|
157
|
-
source: p.source ||
|
|
169
|
+
source: p.source || "unknown",
|
|
158
170
|
})),
|
|
159
171
|
};
|
|
160
172
|
}
|
|
161
173
|
|
|
174
|
+
function toolGraph(args) {
|
|
175
|
+
const { action, claimId, topic, tag, minSize } = args;
|
|
176
|
+
switch (action) {
|
|
177
|
+
case "build": {
|
|
178
|
+
const stats = graph.build();
|
|
179
|
+
return { status: "ok", message: "Knowledge graph built.", ...stats };
|
|
180
|
+
}
|
|
181
|
+
case "related": {
|
|
182
|
+
if (!claimId)
|
|
183
|
+
return { status: "error", message: "Required field: claimId" };
|
|
184
|
+
const results = graph.related(claimId, { limit: 20 });
|
|
185
|
+
return {
|
|
186
|
+
status: "ok",
|
|
187
|
+
count: results.length,
|
|
188
|
+
related: results.map((r) => ({
|
|
189
|
+
id: r.claim.id,
|
|
190
|
+
type: r.claim.type,
|
|
191
|
+
topic: r.claim.topic,
|
|
192
|
+
content: (r.claim.content || "").slice(0, 200),
|
|
193
|
+
source: r.source,
|
|
194
|
+
relation: r.relation,
|
|
195
|
+
weight: r.weight,
|
|
196
|
+
})),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
case "topic": {
|
|
200
|
+
if (!topic) return { status: "error", message: "Required field: topic" };
|
|
201
|
+
const results = graph.byTopic(topic);
|
|
202
|
+
return {
|
|
203
|
+
status: "ok",
|
|
204
|
+
count: results.length,
|
|
205
|
+
claims: results.map((r) => ({
|
|
206
|
+
id: r.claim.id,
|
|
207
|
+
type: r.claim.type,
|
|
208
|
+
content: (r.claim.content || "").slice(0, 200),
|
|
209
|
+
source: r.source,
|
|
210
|
+
})),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
case "clusters": {
|
|
214
|
+
const clusters = graph.clusters(minSize || 3);
|
|
215
|
+
return {
|
|
216
|
+
status: "ok",
|
|
217
|
+
count: clusters.length,
|
|
218
|
+
clusters: clusters.slice(0, 20).map((c) => ({
|
|
219
|
+
topic: c.topic,
|
|
220
|
+
claimCount: c.claimCount,
|
|
221
|
+
edgeCount: c.edgeCount,
|
|
222
|
+
})),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
case "stats": {
|
|
226
|
+
return { status: "ok", ...graph.stats() };
|
|
227
|
+
}
|
|
228
|
+
default:
|
|
229
|
+
return {
|
|
230
|
+
status: "error",
|
|
231
|
+
message: "Unknown action. Use: build, related, topic, clusters, stats",
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function toolConfluence(dir, args) {
|
|
237
|
+
const { action } = args;
|
|
238
|
+
if (!confluence.isConfigured()) {
|
|
239
|
+
return {
|
|
240
|
+
status: "error",
|
|
241
|
+
message:
|
|
242
|
+
"Confluence not configured. Set CONFLUENCE_BASE_URL, CONFLUENCE_TOKEN, and CONFLUENCE_EMAIL environment variables.",
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
switch (action) {
|
|
247
|
+
case "publish": {
|
|
248
|
+
const { title, from, spaceKey, parentId, pageId } = args;
|
|
249
|
+
if (!title) return { status: "error", message: "Required field: title" };
|
|
250
|
+
const sourceFile = from || path.join(dir, "claims.json");
|
|
251
|
+
if (!fs.existsSync(sourceFile))
|
|
252
|
+
return {
|
|
253
|
+
status: "error",
|
|
254
|
+
message: `Source file not found: ${sourceFile}`,
|
|
255
|
+
};
|
|
256
|
+
const raw = JSON.parse(fs.readFileSync(sourceFile, "utf8"));
|
|
257
|
+
const claims = Array.isArray(raw) ? raw : raw.claims || [];
|
|
258
|
+
return confluence
|
|
259
|
+
.publish(title, claims, { spaceKey, parentId, pageId })
|
|
260
|
+
.then((result) => ({
|
|
261
|
+
status: "ok",
|
|
262
|
+
message: `Published to Confluence: ${result.title}`,
|
|
263
|
+
...result,
|
|
264
|
+
}))
|
|
265
|
+
.catch((err) => ({ status: "error", message: err.message }));
|
|
266
|
+
}
|
|
267
|
+
case "pull": {
|
|
268
|
+
const { pageId: pid, title: searchTitle, into, spaceKey } = args;
|
|
269
|
+
const target = pid || searchTitle;
|
|
270
|
+
if (!target)
|
|
271
|
+
return { status: "error", message: "Required: pageId or title" };
|
|
272
|
+
return confluence
|
|
273
|
+
.pull(target, { spaceKey })
|
|
274
|
+
.then((result) => {
|
|
275
|
+
if (into) {
|
|
276
|
+
const targetFile = path.resolve(dir, into);
|
|
277
|
+
let existing = [];
|
|
278
|
+
if (fs.existsSync(targetFile)) {
|
|
279
|
+
const raw = JSON.parse(fs.readFileSync(targetFile, "utf-8"));
|
|
280
|
+
existing = Array.isArray(raw) ? raw : raw.claims || [];
|
|
281
|
+
}
|
|
282
|
+
const merged = [...existing, ...result.claims];
|
|
283
|
+
fs.writeFileSync(
|
|
284
|
+
targetFile,
|
|
285
|
+
JSON.stringify(merged, null, 2) + "\n",
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
status: "ok",
|
|
290
|
+
title: result.title,
|
|
291
|
+
claimCount: result.claims.length,
|
|
292
|
+
};
|
|
293
|
+
})
|
|
294
|
+
.catch((err) => ({ status: "error", message: err.message }));
|
|
295
|
+
}
|
|
296
|
+
case "list": {
|
|
297
|
+
const { spaceKey } = args;
|
|
298
|
+
return confluence
|
|
299
|
+
.listPages({ spaceKey })
|
|
300
|
+
.then((result) => ({ status: "ok", ...result }))
|
|
301
|
+
.catch((err) => ({ status: "error", message: err.message }));
|
|
302
|
+
}
|
|
303
|
+
default:
|
|
304
|
+
return {
|
|
305
|
+
status: "error",
|
|
306
|
+
message: "Unknown action. Use: publish, pull, list",
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
162
311
|
// ─── Tool & Resource definitions ────────────────────────────────────────────
|
|
163
312
|
|
|
164
313
|
const TOOLS = [
|
|
165
314
|
{
|
|
166
|
-
name:
|
|
167
|
-
description:
|
|
315
|
+
name: "silo/search",
|
|
316
|
+
description:
|
|
317
|
+
"Full-text search across all stored claims and knowledge packs. Returns ranked results with relevance scores.",
|
|
168
318
|
inputSchema: {
|
|
169
|
-
type:
|
|
319
|
+
type: "object",
|
|
170
320
|
properties: {
|
|
171
|
-
query: {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
321
|
+
query: {
|
|
322
|
+
type: "string",
|
|
323
|
+
description: "Search query (space-separated terms, OR logic)",
|
|
324
|
+
},
|
|
325
|
+
type: {
|
|
326
|
+
type: "string",
|
|
327
|
+
enum: [
|
|
328
|
+
"constraint",
|
|
329
|
+
"factual",
|
|
330
|
+
"estimate",
|
|
331
|
+
"risk",
|
|
332
|
+
"recommendation",
|
|
333
|
+
"feedback",
|
|
334
|
+
],
|
|
335
|
+
description: "Filter by claim type",
|
|
336
|
+
},
|
|
337
|
+
evidence: {
|
|
338
|
+
type: "string",
|
|
339
|
+
enum: ["stated", "web", "documented", "tested", "production"],
|
|
340
|
+
description: "Filter by evidence tier",
|
|
341
|
+
},
|
|
342
|
+
limit: { type: "number", description: "Max results (default: 20)" },
|
|
175
343
|
},
|
|
176
|
-
required: [
|
|
344
|
+
required: ["query"],
|
|
177
345
|
},
|
|
178
346
|
},
|
|
179
347
|
{
|
|
180
|
-
name:
|
|
181
|
-
description:
|
|
182
|
-
|
|
348
|
+
name: "silo/list",
|
|
349
|
+
description:
|
|
350
|
+
"List all stored collections in the silo (completed sprints, imported knowledge).",
|
|
351
|
+
inputSchema: { type: "object", properties: {} },
|
|
183
352
|
},
|
|
184
353
|
{
|
|
185
|
-
name:
|
|
186
|
-
description:
|
|
354
|
+
name: "silo/pull",
|
|
355
|
+
description:
|
|
356
|
+
"Pull claims from a knowledge pack or stored collection into the current sprint claims.json. Deduplicates and re-prefixes IDs to avoid collisions.",
|
|
187
357
|
inputSchema: {
|
|
188
|
-
type:
|
|
358
|
+
type: "object",
|
|
189
359
|
properties: {
|
|
190
|
-
pack: {
|
|
191
|
-
|
|
360
|
+
pack: {
|
|
361
|
+
type: "string",
|
|
362
|
+
description:
|
|
363
|
+
'Pack name (e.g., "compliance", "security") or stored collection ID',
|
|
364
|
+
},
|
|
365
|
+
into: {
|
|
366
|
+
type: "string",
|
|
367
|
+
description: "Target claims.json path (default: ./claims.json)",
|
|
368
|
+
},
|
|
192
369
|
},
|
|
193
|
-
required: [
|
|
370
|
+
required: ["pack"],
|
|
194
371
|
},
|
|
195
372
|
},
|
|
196
373
|
{
|
|
197
|
-
name:
|
|
198
|
-
description:
|
|
374
|
+
name: "silo/store",
|
|
375
|
+
description:
|
|
376
|
+
"Store the current sprint claims into the silo for future reuse across other sprints.",
|
|
199
377
|
inputSchema: {
|
|
200
|
-
type:
|
|
378
|
+
type: "object",
|
|
201
379
|
properties: {
|
|
202
|
-
name: {
|
|
203
|
-
|
|
380
|
+
name: {
|
|
381
|
+
type: "string",
|
|
382
|
+
description: 'Collection name (e.g., "q4-migration-findings")',
|
|
383
|
+
},
|
|
384
|
+
from: {
|
|
385
|
+
type: "string",
|
|
386
|
+
description: "Source claims.json path (default: ./claims.json)",
|
|
387
|
+
},
|
|
204
388
|
},
|
|
205
|
-
required: [
|
|
389
|
+
required: ["name"],
|
|
206
390
|
},
|
|
207
391
|
},
|
|
208
392
|
{
|
|
209
|
-
name:
|
|
210
|
-
description:
|
|
211
|
-
|
|
393
|
+
name: "silo/packs",
|
|
394
|
+
description:
|
|
395
|
+
"List available knowledge packs (built-in: compliance, security, architecture, migration, vendor-eval, adr, hackathon categories, etc.).",
|
|
396
|
+
inputSchema: { type: "object", properties: {} },
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
name: "silo/graph",
|
|
400
|
+
description:
|
|
401
|
+
"Cross-sprint knowledge graph — find related claims, explore topics, and discover clusters across all stored collections and packs.",
|
|
402
|
+
inputSchema: {
|
|
403
|
+
type: "object",
|
|
404
|
+
properties: {
|
|
405
|
+
action: {
|
|
406
|
+
type: "string",
|
|
407
|
+
enum: ["build", "related", "topic", "clusters", "stats"],
|
|
408
|
+
description: "Graph operation to perform",
|
|
409
|
+
},
|
|
410
|
+
claimId: {
|
|
411
|
+
type: "string",
|
|
412
|
+
description: 'Claim ID for "related" action',
|
|
413
|
+
},
|
|
414
|
+
topic: {
|
|
415
|
+
type: "string",
|
|
416
|
+
description: 'Topic string for "topic" action',
|
|
417
|
+
},
|
|
418
|
+
tag: { type: "string", description: "Tag for filtering" },
|
|
419
|
+
minSize: {
|
|
420
|
+
type: "number",
|
|
421
|
+
description:
|
|
422
|
+
'Minimum cluster size for "clusters" action (default: 3)',
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
required: ["action"],
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
name: "silo/confluence",
|
|
430
|
+
description:
|
|
431
|
+
"Confluence backend adapter — publish claims to Confluence pages or pull claims from existing pages. Requires CONFLUENCE_BASE_URL, CONFLUENCE_TOKEN, CONFLUENCE_EMAIL env vars.",
|
|
432
|
+
inputSchema: {
|
|
433
|
+
type: "object",
|
|
434
|
+
properties: {
|
|
435
|
+
action: {
|
|
436
|
+
type: "string",
|
|
437
|
+
enum: ["publish", "pull", "list"],
|
|
438
|
+
description: "Confluence operation",
|
|
439
|
+
},
|
|
440
|
+
title: {
|
|
441
|
+
type: "string",
|
|
442
|
+
description: "Page title (publish/pull by title)",
|
|
443
|
+
},
|
|
444
|
+
from: {
|
|
445
|
+
type: "string",
|
|
446
|
+
description:
|
|
447
|
+
"Source claims.json path (publish, default: ./claims.json)",
|
|
448
|
+
},
|
|
449
|
+
into: { type: "string", description: "Target claims.json path (pull)" },
|
|
450
|
+
pageId: {
|
|
451
|
+
type: "string",
|
|
452
|
+
description: "Confluence page ID (pull/publish update)",
|
|
453
|
+
},
|
|
454
|
+
spaceKey: { type: "string", description: "Confluence space key" },
|
|
455
|
+
parentId: { type: "string", description: "Parent page ID (publish)" },
|
|
456
|
+
},
|
|
457
|
+
required: ["action"],
|
|
458
|
+
},
|
|
212
459
|
},
|
|
213
460
|
];
|
|
214
461
|
|
|
215
462
|
const RESOURCES = [
|
|
216
463
|
{
|
|
217
|
-
uri:
|
|
218
|
-
name:
|
|
219
|
-
description:
|
|
220
|
-
|
|
464
|
+
uri: "silo://index",
|
|
465
|
+
name: "Silo Index",
|
|
466
|
+
description:
|
|
467
|
+
"All stored collections — IDs, names, claim counts, timestamps.",
|
|
468
|
+
mimeType: "application/json",
|
|
221
469
|
},
|
|
222
470
|
{
|
|
223
|
-
uri:
|
|
224
|
-
name:
|
|
225
|
-
description:
|
|
226
|
-
mimeType:
|
|
471
|
+
uri: "silo://packs",
|
|
472
|
+
name: "Knowledge Packs",
|
|
473
|
+
description: "Available built-in and local knowledge packs.",
|
|
474
|
+
mimeType: "application/json",
|
|
227
475
|
},
|
|
228
476
|
];
|
|
229
477
|
|
|
@@ -231,66 +479,99 @@ const RESOURCES = [
|
|
|
231
479
|
|
|
232
480
|
function handleRequest(dir, method, params, id) {
|
|
233
481
|
switch (method) {
|
|
234
|
-
case
|
|
482
|
+
case "initialize":
|
|
235
483
|
return jsonRpcResponse(id, {
|
|
236
484
|
protocolVersion: PROTOCOL_VERSION,
|
|
237
485
|
capabilities: { tools: {}, resources: {} },
|
|
238
486
|
serverInfo: { name: SERVER_NAME, version: SERVER_VERSION },
|
|
239
487
|
});
|
|
240
488
|
|
|
241
|
-
case
|
|
489
|
+
case "notifications/initialized":
|
|
242
490
|
return null;
|
|
243
491
|
|
|
244
|
-
case
|
|
492
|
+
case "tools/list":
|
|
245
493
|
return jsonRpcResponse(id, { tools: TOOLS });
|
|
246
494
|
|
|
247
|
-
case
|
|
495
|
+
case "tools/call": {
|
|
248
496
|
const toolName = params.name;
|
|
249
497
|
const toolArgs = params.arguments || {};
|
|
250
498
|
let result;
|
|
251
499
|
|
|
252
500
|
switch (toolName) {
|
|
253
|
-
case
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
case
|
|
257
|
-
|
|
501
|
+
case "silo/search":
|
|
502
|
+
result = toolSearch(toolArgs);
|
|
503
|
+
break;
|
|
504
|
+
case "silo/list":
|
|
505
|
+
result = toolList();
|
|
506
|
+
break;
|
|
507
|
+
case "silo/pull":
|
|
508
|
+
result = toolPull(dir, toolArgs);
|
|
509
|
+
break;
|
|
510
|
+
case "silo/store":
|
|
511
|
+
result = toolStore(dir, toolArgs);
|
|
512
|
+
break;
|
|
513
|
+
case "silo/packs":
|
|
514
|
+
result = toolPacks();
|
|
515
|
+
break;
|
|
516
|
+
case "silo/graph":
|
|
517
|
+
result = toolGraph(toolArgs);
|
|
518
|
+
break;
|
|
519
|
+
case "silo/confluence":
|
|
520
|
+
result = toolConfluence(dir, toolArgs);
|
|
521
|
+
break;
|
|
258
522
|
default:
|
|
259
523
|
return jsonRpcError(id, -32601, `Unknown tool: ${toolName}`);
|
|
260
524
|
}
|
|
261
525
|
|
|
526
|
+
// Handle async tool results (e.g., confluence)
|
|
527
|
+
if (result && typeof result.then === "function") {
|
|
528
|
+
return result.then((r) =>
|
|
529
|
+
jsonRpcResponse(id, {
|
|
530
|
+
content: [{ type: "text", text: JSON.stringify(r, null, 2) }],
|
|
531
|
+
isError: r.status === "error",
|
|
532
|
+
}),
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
|
|
262
536
|
return jsonRpcResponse(id, {
|
|
263
|
-
content: [{ type:
|
|
264
|
-
isError: result.status ===
|
|
537
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
538
|
+
isError: result.status === "error",
|
|
265
539
|
});
|
|
266
540
|
}
|
|
267
541
|
|
|
268
|
-
case
|
|
542
|
+
case "resources/list":
|
|
269
543
|
return jsonRpcResponse(id, { resources: RESOURCES });
|
|
270
544
|
|
|
271
|
-
case
|
|
545
|
+
case "resources/read": {
|
|
272
546
|
const uri = params.uri;
|
|
273
547
|
let text;
|
|
274
548
|
|
|
275
549
|
switch (uri) {
|
|
276
|
-
case
|
|
550
|
+
case "silo://index":
|
|
277
551
|
text = JSON.stringify(store.list(), null, 2);
|
|
278
552
|
break;
|
|
279
|
-
case
|
|
280
|
-
text = JSON.stringify(
|
|
281
|
-
|
|
282
|
-
|
|
553
|
+
case "silo://packs":
|
|
554
|
+
text = JSON.stringify(
|
|
555
|
+
packs.list().map((p) => ({
|
|
556
|
+
name: p.name,
|
|
557
|
+
description: p.description,
|
|
558
|
+
claimCount: p.claims ? p.claims.length : 0,
|
|
559
|
+
source: p.source,
|
|
560
|
+
})),
|
|
561
|
+
null,
|
|
562
|
+
2,
|
|
563
|
+
);
|
|
283
564
|
break;
|
|
284
565
|
default:
|
|
285
566
|
return jsonRpcError(id, -32602, `Unknown resource: ${uri}`);
|
|
286
567
|
}
|
|
287
568
|
|
|
288
569
|
return jsonRpcResponse(id, {
|
|
289
|
-
contents: [{ uri, mimeType:
|
|
570
|
+
contents: [{ uri, mimeType: "application/json", text }],
|
|
290
571
|
});
|
|
291
572
|
}
|
|
292
573
|
|
|
293
|
-
case
|
|
574
|
+
case "ping":
|
|
294
575
|
return jsonRpcResponse(id, {});
|
|
295
576
|
|
|
296
577
|
default:
|
|
@@ -302,28 +583,41 @@ function handleRequest(dir, method, params, id) {
|
|
|
302
583
|
// ─── Stdio transport ────────────────────────────────────────────────────────
|
|
303
584
|
|
|
304
585
|
function startServer(dir) {
|
|
305
|
-
const rl = readline.createInterface({
|
|
586
|
+
const rl = readline.createInterface({
|
|
587
|
+
input: process.stdin,
|
|
588
|
+
terminal: false,
|
|
589
|
+
});
|
|
306
590
|
|
|
307
591
|
if (process.stdout._handle && process.stdout._handle.setBlocking) {
|
|
308
592
|
process.stdout._handle.setBlocking(true);
|
|
309
593
|
}
|
|
310
594
|
|
|
311
|
-
rl.on(
|
|
595
|
+
rl.on("line", (line) => {
|
|
312
596
|
if (!line.trim()) return;
|
|
313
597
|
let msg;
|
|
314
|
-
try {
|
|
315
|
-
|
|
598
|
+
try {
|
|
599
|
+
msg = JSON.parse(line);
|
|
600
|
+
} catch {
|
|
601
|
+
process.stdout.write(jsonRpcError(null, -32700, "Parse error") + "\n");
|
|
316
602
|
return;
|
|
317
603
|
}
|
|
318
604
|
const response = handleRequest(dir, msg.method, msg.params || {}, msg.id);
|
|
319
|
-
if (response
|
|
605
|
+
if (response && typeof response.then === "function") {
|
|
606
|
+
response.then((r) => {
|
|
607
|
+
if (r !== null) process.stdout.write(r + "\n");
|
|
608
|
+
});
|
|
609
|
+
} else if (response !== null) {
|
|
610
|
+
process.stdout.write(response + "\n");
|
|
611
|
+
}
|
|
320
612
|
});
|
|
321
613
|
|
|
322
|
-
rl.on(
|
|
614
|
+
rl.on("close", () => process.exit(0));
|
|
323
615
|
|
|
324
616
|
process.stderr.write(`silo MCP server v${SERVER_VERSION} ready on stdio\n`);
|
|
325
617
|
process.stderr.write(` Silo root: ${store.root}\n`);
|
|
326
|
-
process.stderr.write(
|
|
618
|
+
process.stderr.write(
|
|
619
|
+
` Tools: ${TOOLS.length} | Resources: ${RESOURCES.length}\n`,
|
|
620
|
+
);
|
|
327
621
|
}
|
|
328
622
|
|
|
329
623
|
// ─── Entry point ────────────────────────────────────────────────────────────
|
|
@@ -332,6 +626,8 @@ if (require.main === module) {
|
|
|
332
626
|
startServer(process.cwd());
|
|
333
627
|
}
|
|
334
628
|
|
|
335
|
-
async function run(dir) {
|
|
629
|
+
async function run(dir) {
|
|
630
|
+
startServer(dir);
|
|
631
|
+
}
|
|
336
632
|
|
|
337
633
|
module.exports = { startServer, handleRequest, TOOLS, RESOURCES, run };
|