@grainulation/silo 1.0.0
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/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/bin/silo.js +327 -0
- package/lib/analytics.js +76 -0
- package/lib/import-export.js +174 -0
- package/lib/index.js +28 -0
- package/lib/packs.js +184 -0
- package/lib/search.js +128 -0
- package/lib/serve-mcp.js +337 -0
- package/lib/server.js +425 -0
- package/lib/store.js +145 -0
- package/lib/templates.js +139 -0
- package/package.json +48 -0
- package/packs/api-design.json +189 -0
- package/packs/architecture.json +175 -0
- package/packs/ci-cd.json +175 -0
- package/packs/compliance.json +203 -0
- package/packs/data-engineering.json +175 -0
- package/packs/frontend.json +175 -0
- package/packs/migration.json +147 -0
- package/packs/observability.json +175 -0
- package/packs/security.json +175 -0
- package/packs/team-process.json +175 -0
- package/packs/testing.json +147 -0
- package/public/grainulation-tokens.css +321 -0
- package/public/index.html +803 -0
package/lib/serve-mcp.js
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* silo serve-mcp — Local MCP server for Claude Code
|
|
3
|
+
*
|
|
4
|
+
* Exposes cross-sprint search and knowledge pack tools over stdio.
|
|
5
|
+
* Zero npm dependencies.
|
|
6
|
+
*
|
|
7
|
+
* Tools:
|
|
8
|
+
* silo/search — Full-text search across all stored claims
|
|
9
|
+
* silo/list — List stored collections and packs
|
|
10
|
+
* silo/pull — Pull claims from a pack into current sprint
|
|
11
|
+
* silo/store — Store current sprint claims in silo
|
|
12
|
+
* silo/packs — List available knowledge packs
|
|
13
|
+
*
|
|
14
|
+
* Resources:
|
|
15
|
+
* silo://index — Silo index (all collections)
|
|
16
|
+
* silo://packs — Available knowledge packs
|
|
17
|
+
*
|
|
18
|
+
* Install:
|
|
19
|
+
* claude mcp add silo -- npx @grainulation/silo serve-mcp
|
|
20
|
+
*/
|
|
21
|
+
|
|
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
|
+
|
|
30
|
+
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
const SERVER_NAME = 'silo';
|
|
33
|
+
const SERVER_VERSION = '1.0.0';
|
|
34
|
+
const PROTOCOL_VERSION = '2024-11-05';
|
|
35
|
+
|
|
36
|
+
// ─── JSON-RPC helpers ───────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
function jsonRpcResponse(id, result) {
|
|
39
|
+
return JSON.stringify({ jsonrpc: '2.0', id, result });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function jsonRpcError(id, code, message) {
|
|
43
|
+
return JSON.stringify({ jsonrpc: '2.0', id, error: { code, message } });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ─── Initialize silo components ─────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
const store = new Store();
|
|
49
|
+
const search = new Search(store);
|
|
50
|
+
const io = new ImportExport(store);
|
|
51
|
+
const packs = new Packs(store);
|
|
52
|
+
|
|
53
|
+
// ─── Tool implementations ───────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
function toolSearch(args) {
|
|
56
|
+
const { query, type, evidence, limit } = args;
|
|
57
|
+
if (!query) {
|
|
58
|
+
return { status: 'error', message: 'Required field: query' };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const results = search.query(query, {
|
|
62
|
+
type: type || null,
|
|
63
|
+
tier: evidence || null,
|
|
64
|
+
limit: limit || 20,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
status: 'ok',
|
|
69
|
+
count: results.length,
|
|
70
|
+
claims: results.map(r => ({
|
|
71
|
+
id: r.claim.id,
|
|
72
|
+
type: r.claim.type,
|
|
73
|
+
topic: r.claim.topic,
|
|
74
|
+
evidence: r.claim.evidence,
|
|
75
|
+
content: (r.claim.content || r.claim.text || '').slice(0, 200) + ((r.claim.content || r.claim.text || '').length > 200 ? '...' : ''),
|
|
76
|
+
score: r.score,
|
|
77
|
+
collection: r.collection,
|
|
78
|
+
})),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function toolList() {
|
|
83
|
+
const collections = store.list();
|
|
84
|
+
return {
|
|
85
|
+
status: 'ok',
|
|
86
|
+
count: collections.length,
|
|
87
|
+
collections: collections.map(c => ({
|
|
88
|
+
id: c.id,
|
|
89
|
+
name: c.name,
|
|
90
|
+
claimCount: c.claimCount,
|
|
91
|
+
storedAt: c.storedAt,
|
|
92
|
+
})),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function toolPull(dir, args) {
|
|
97
|
+
const { pack, into } = args;
|
|
98
|
+
if (!pack) {
|
|
99
|
+
return { status: 'error', message: 'Required field: pack (pack name or collection ID)' };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const targetFile = into || path.join(dir, 'claims.json');
|
|
103
|
+
if (!fs.existsSync(targetFile)) {
|
|
104
|
+
return { status: 'error', message: `Target file not found: ${targetFile}. Run wheat init first.` };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const result = io.pull(pack, targetFile, {});
|
|
109
|
+
return {
|
|
110
|
+
status: 'ok',
|
|
111
|
+
message: `Pulled ${result.imported} claims from "${pack}" into ${path.basename(targetFile)}.`,
|
|
112
|
+
imported: result.imported,
|
|
113
|
+
skipped: result.skipped || 0,
|
|
114
|
+
};
|
|
115
|
+
} catch (err) {
|
|
116
|
+
return { status: 'error', message: err.message };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function toolStore(dir, args) {
|
|
121
|
+
const { name, from } = args;
|
|
122
|
+
if (!name) {
|
|
123
|
+
return { status: 'error', message: 'Required field: name' };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const sourceFile = from || path.join(dir, 'claims.json');
|
|
127
|
+
if (!fs.existsSync(sourceFile)) {
|
|
128
|
+
return { status: 'error', message: `Source file not found: ${sourceFile}` };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const data = JSON.parse(fs.readFileSync(sourceFile, 'utf8'));
|
|
133
|
+
const claims = data.claims || data;
|
|
134
|
+
const meta = data.meta || {};
|
|
135
|
+
const result = store.storeClaims(name, claims, meta);
|
|
136
|
+
return {
|
|
137
|
+
status: 'ok',
|
|
138
|
+
message: `Stored ${result.claimCount} claims as "${name}".`,
|
|
139
|
+
id: result.id,
|
|
140
|
+
claimCount: result.claimCount,
|
|
141
|
+
hash: result.hash,
|
|
142
|
+
};
|
|
143
|
+
} catch (err) {
|
|
144
|
+
return { status: 'error', message: err.message };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function toolPacks() {
|
|
149
|
+
const available = packs.list();
|
|
150
|
+
return {
|
|
151
|
+
status: 'ok',
|
|
152
|
+
count: available.length,
|
|
153
|
+
packs: available.map(p => ({
|
|
154
|
+
name: p.name,
|
|
155
|
+
description: p.description || '',
|
|
156
|
+
claimCount: p.claims ? p.claims.length : p.claimCount || 0,
|
|
157
|
+
source: p.source || 'unknown',
|
|
158
|
+
})),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ─── Tool & Resource definitions ────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
const TOOLS = [
|
|
165
|
+
{
|
|
166
|
+
name: 'silo/search',
|
|
167
|
+
description: 'Full-text search across all stored claims and knowledge packs. Returns ranked results with relevance scores.',
|
|
168
|
+
inputSchema: {
|
|
169
|
+
type: 'object',
|
|
170
|
+
properties: {
|
|
171
|
+
query: { type: 'string', description: 'Search query (space-separated terms, OR logic)' },
|
|
172
|
+
type: { type: 'string', enum: ['constraint', 'factual', 'estimate', 'risk', 'recommendation', 'feedback'], description: 'Filter by claim type' },
|
|
173
|
+
evidence: { type: 'string', enum: ['stated', 'web', 'documented', 'tested', 'production'], description: 'Filter by evidence tier' },
|
|
174
|
+
limit: { type: 'number', description: 'Max results (default: 20)' },
|
|
175
|
+
},
|
|
176
|
+
required: ['query'],
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: 'silo/list',
|
|
181
|
+
description: 'List all stored collections in the silo (completed sprints, imported knowledge).',
|
|
182
|
+
inputSchema: { type: 'object', properties: {} },
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: 'silo/pull',
|
|
186
|
+
description: 'Pull claims from a knowledge pack or stored collection into the current sprint claims.json. Deduplicates and re-prefixes IDs to avoid collisions.',
|
|
187
|
+
inputSchema: {
|
|
188
|
+
type: 'object',
|
|
189
|
+
properties: {
|
|
190
|
+
pack: { type: 'string', description: 'Pack name (e.g., "compliance", "security") or stored collection ID' },
|
|
191
|
+
into: { type: 'string', description: 'Target claims.json path (default: ./claims.json)' },
|
|
192
|
+
},
|
|
193
|
+
required: ['pack'],
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: 'silo/store',
|
|
198
|
+
description: 'Store the current sprint claims into the silo for future reuse across other sprints.',
|
|
199
|
+
inputSchema: {
|
|
200
|
+
type: 'object',
|
|
201
|
+
properties: {
|
|
202
|
+
name: { type: 'string', description: 'Collection name (e.g., "q4-migration-findings")' },
|
|
203
|
+
from: { type: 'string', description: 'Source claims.json path (default: ./claims.json)' },
|
|
204
|
+
},
|
|
205
|
+
required: ['name'],
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
name: 'silo/packs',
|
|
210
|
+
description: 'List available knowledge packs (built-in: compliance, security, architecture, migration, etc.).',
|
|
211
|
+
inputSchema: { type: 'object', properties: {} },
|
|
212
|
+
},
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
const RESOURCES = [
|
|
216
|
+
{
|
|
217
|
+
uri: 'silo://index',
|
|
218
|
+
name: 'Silo Index',
|
|
219
|
+
description: 'All stored collections — IDs, names, claim counts, timestamps.',
|
|
220
|
+
mimeType: 'application/json',
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
uri: 'silo://packs',
|
|
224
|
+
name: 'Knowledge Packs',
|
|
225
|
+
description: 'Available built-in and local knowledge packs.',
|
|
226
|
+
mimeType: 'application/json',
|
|
227
|
+
},
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
// ─── Request handler ────────────────────────────────────────────────────────
|
|
231
|
+
|
|
232
|
+
function handleRequest(dir, method, params, id) {
|
|
233
|
+
switch (method) {
|
|
234
|
+
case 'initialize':
|
|
235
|
+
return jsonRpcResponse(id, {
|
|
236
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
237
|
+
capabilities: { tools: {}, resources: {} },
|
|
238
|
+
serverInfo: { name: SERVER_NAME, version: SERVER_VERSION },
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
case 'notifications/initialized':
|
|
242
|
+
return null;
|
|
243
|
+
|
|
244
|
+
case 'tools/list':
|
|
245
|
+
return jsonRpcResponse(id, { tools: TOOLS });
|
|
246
|
+
|
|
247
|
+
case 'tools/call': {
|
|
248
|
+
const toolName = params.name;
|
|
249
|
+
const toolArgs = params.arguments || {};
|
|
250
|
+
let result;
|
|
251
|
+
|
|
252
|
+
switch (toolName) {
|
|
253
|
+
case 'silo/search': result = toolSearch(toolArgs); break;
|
|
254
|
+
case 'silo/list': result = toolList(); break;
|
|
255
|
+
case 'silo/pull': result = toolPull(dir, toolArgs); break;
|
|
256
|
+
case 'silo/store': result = toolStore(dir, toolArgs); break;
|
|
257
|
+
case 'silo/packs': result = toolPacks(); break;
|
|
258
|
+
default:
|
|
259
|
+
return jsonRpcError(id, -32601, `Unknown tool: ${toolName}`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return jsonRpcResponse(id, {
|
|
263
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
264
|
+
isError: result.status === 'error',
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
case 'resources/list':
|
|
269
|
+
return jsonRpcResponse(id, { resources: RESOURCES });
|
|
270
|
+
|
|
271
|
+
case 'resources/read': {
|
|
272
|
+
const uri = params.uri;
|
|
273
|
+
let text;
|
|
274
|
+
|
|
275
|
+
switch (uri) {
|
|
276
|
+
case 'silo://index':
|
|
277
|
+
text = JSON.stringify(store.list(), null, 2);
|
|
278
|
+
break;
|
|
279
|
+
case 'silo://packs':
|
|
280
|
+
text = JSON.stringify(packs.list().map(p => ({
|
|
281
|
+
name: p.name, description: p.description, claimCount: p.claims ? p.claims.length : 0, source: p.source,
|
|
282
|
+
})), null, 2);
|
|
283
|
+
break;
|
|
284
|
+
default:
|
|
285
|
+
return jsonRpcError(id, -32602, `Unknown resource: ${uri}`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return jsonRpcResponse(id, {
|
|
289
|
+
contents: [{ uri, mimeType: 'application/json', text }],
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
case 'ping':
|
|
294
|
+
return jsonRpcResponse(id, {});
|
|
295
|
+
|
|
296
|
+
default:
|
|
297
|
+
if (id === undefined || id === null) return null;
|
|
298
|
+
return jsonRpcError(id, -32601, `Method not found: ${method}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ─── Stdio transport ────────────────────────────────────────────────────────
|
|
303
|
+
|
|
304
|
+
function startServer(dir) {
|
|
305
|
+
const rl = readline.createInterface({ input: process.stdin, terminal: false });
|
|
306
|
+
|
|
307
|
+
if (process.stdout._handle && process.stdout._handle.setBlocking) {
|
|
308
|
+
process.stdout._handle.setBlocking(true);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
rl.on('line', (line) => {
|
|
312
|
+
if (!line.trim()) return;
|
|
313
|
+
let msg;
|
|
314
|
+
try { msg = JSON.parse(line); } catch {
|
|
315
|
+
process.stdout.write(jsonRpcError(null, -32700, 'Parse error') + '\n');
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const response = handleRequest(dir, msg.method, msg.params || {}, msg.id);
|
|
319
|
+
if (response !== null) process.stdout.write(response + '\n');
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
rl.on('close', () => process.exit(0));
|
|
323
|
+
|
|
324
|
+
process.stderr.write(`silo MCP server v${SERVER_VERSION} ready on stdio\n`);
|
|
325
|
+
process.stderr.write(` Silo root: ${store.root}\n`);
|
|
326
|
+
process.stderr.write(` Tools: ${TOOLS.length} | Resources: ${RESOURCES.length}\n`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ─── Entry point ────────────────────────────────────────────────────────────
|
|
330
|
+
|
|
331
|
+
if (require.main === module) {
|
|
332
|
+
startServer(process.cwd());
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async function run(dir) { startServer(dir); }
|
|
336
|
+
|
|
337
|
+
module.exports = { startServer, handleRequest, TOOLS, RESOURCES, run };
|