sysprom 1.6.0 → 1.7.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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "./server.js";
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ // @fileoverview MCP server entry point - runs the SysProM MCP server
3
+ import "./server.js";
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,348 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import * as z from "zod";
5
+ import { loadDocument } from "../io.js";
6
+ import { RelationshipType } from "../schema.js";
7
+ import { validateOp, statsOp, queryNodesOp, queryNodeOp, queryRelationshipsOp, traceFromNodeOp, addNodeOp, removeNodeOp, updateNodeOp, addRelationshipOp, removeRelationshipOp, nextIdOp, } from "../operations/index.js";
8
+ // Create MCP server instance
9
+ const server = new McpServer({
10
+ name: "sysprom-mcp",
11
+ version: "1.0.0",
12
+ });
13
+ // Register validate tool
14
+ server.registerTool("validate", {
15
+ description: "Validate a SysProM document and return any validation issues",
16
+ inputSchema: z.object({
17
+ path: z.string().describe("Path to SysProM file"),
18
+ }),
19
+ }, ({ path }) => {
20
+ const { doc } = loadDocument(path);
21
+ const result = validateOp({ doc });
22
+ return {
23
+ content: [
24
+ {
25
+ type: "text",
26
+ text: JSON.stringify(result, null, 2),
27
+ },
28
+ ],
29
+ };
30
+ });
31
+ // Register stats tool
32
+ server.registerTool("stats", {
33
+ description: "Get statistics about a SysProM document",
34
+ inputSchema: z.object({
35
+ path: z.string().describe("Path to SysProM file"),
36
+ }),
37
+ }, ({ path }) => {
38
+ const { doc } = loadDocument(path);
39
+ const result = statsOp({ doc });
40
+ return {
41
+ content: [
42
+ {
43
+ type: "text",
44
+ text: JSON.stringify(result, null, 2),
45
+ },
46
+ ],
47
+ };
48
+ });
49
+ // Register query-nodes tool
50
+ server.registerTool("query-nodes", {
51
+ description: "Query nodes by type, status, or other criteria",
52
+ inputSchema: z.object({
53
+ path: z.string().describe("Path to SysProM file"),
54
+ type: z.string().optional().describe("Filter by node type"),
55
+ status: z.string().optional().describe("Filter by node status"),
56
+ }),
57
+ }, ({ path, type, status }) => {
58
+ const { doc } = loadDocument(path);
59
+ const results = queryNodesOp({
60
+ doc,
61
+ type,
62
+ status,
63
+ });
64
+ return {
65
+ content: [
66
+ {
67
+ type: "text",
68
+ text: JSON.stringify(results, null, 2),
69
+ },
70
+ ],
71
+ };
72
+ });
73
+ // Register query-node tool
74
+ server.registerTool("query-node", {
75
+ description: "Get a specific node by ID",
76
+ inputSchema: z.object({
77
+ path: z.string().describe("Path to SysProM file"),
78
+ id: z.string().describe("Node ID"),
79
+ }),
80
+ }, ({ path, id }) => {
81
+ const { doc } = loadDocument(path);
82
+ const result = queryNodeOp({ doc, id });
83
+ return {
84
+ content: [
85
+ {
86
+ type: "text",
87
+ text: JSON.stringify(result, null, 2),
88
+ },
89
+ ],
90
+ };
91
+ });
92
+ // Register query-relationships tool
93
+ server.registerTool("query-relationships", {
94
+ description: "Query relationships by source, target, or type",
95
+ inputSchema: z.object({
96
+ path: z.string().describe("Path to SysProM file"),
97
+ from: z.string().optional().describe("Filter by source node ID"),
98
+ to: z.string().optional().describe("Filter by target node ID"),
99
+ type: z.string().optional().describe("Filter by relationship type"),
100
+ }),
101
+ }, ({ path, from, to, type }) => {
102
+ const { doc } = loadDocument(path);
103
+ const results = queryRelationshipsOp({
104
+ doc,
105
+ from,
106
+ to,
107
+ type,
108
+ });
109
+ return {
110
+ content: [
111
+ {
112
+ type: "text",
113
+ text: JSON.stringify(results, null, 2),
114
+ },
115
+ ],
116
+ };
117
+ });
118
+ // Register trace tool
119
+ server.registerTool("trace", {
120
+ description: "Trace impacts from a node through the graph",
121
+ inputSchema: z.object({
122
+ path: z.string().describe("Path to SysProM file"),
123
+ from: z.string().describe("Starting node ID"),
124
+ }),
125
+ }, ({ path, from }) => {
126
+ const { doc } = loadDocument(path);
127
+ const result = traceFromNodeOp({ doc, startId: from });
128
+ return {
129
+ content: [
130
+ {
131
+ type: "text",
132
+ text: JSON.stringify(result, null, 2),
133
+ },
134
+ ],
135
+ };
136
+ });
137
+ // Register add-node tool
138
+ server.registerTool("add-node", {
139
+ description: "Add a new node to the SysProM document",
140
+ inputSchema: z.object({
141
+ path: z.string().describe("Path to SysProM file"),
142
+ type: z.string().describe("Node type"),
143
+ id: z.string().optional().describe("Node ID (auto-generated if omitted)"),
144
+ name: z.string().describe("Node name"),
145
+ description: z.string().optional().describe("Node description"),
146
+ }),
147
+ }, ({ path, type, id, name, description }) => {
148
+ const { doc } = loadDocument(path);
149
+ const nodeType = z
150
+ .enum([
151
+ "intent",
152
+ "concept",
153
+ "capability",
154
+ "element",
155
+ "realisation",
156
+ "invariant",
157
+ "principle",
158
+ "policy",
159
+ "protocol",
160
+ "stage",
161
+ "role",
162
+ "gate",
163
+ "mode",
164
+ "artefact",
165
+ "artefact_flow",
166
+ "decision",
167
+ "change",
168
+ "view",
169
+ "version",
170
+ ])
171
+ .safeParse(type);
172
+ if (!nodeType.success) {
173
+ throw new Error(`Invalid node type: ${type}`);
174
+ }
175
+ const nodeId = id ?? nextIdOp({ doc, type: nodeType.data });
176
+ const updated = addNodeOp({
177
+ doc,
178
+ node: {
179
+ id: nodeId,
180
+ type: nodeType.data,
181
+ name,
182
+ ...(description && { description }),
183
+ },
184
+ });
185
+ return {
186
+ content: [
187
+ {
188
+ type: "text",
189
+ text: JSON.stringify({
190
+ message: "Node added",
191
+ id: nodeId,
192
+ nodeCount: updated.nodes.length,
193
+ }, null, 2),
194
+ },
195
+ ],
196
+ };
197
+ });
198
+ // Register remove-node tool
199
+ server.registerTool("remove-node", {
200
+ description: "Remove a node from the SysProM document",
201
+ inputSchema: z.object({
202
+ path: z.string().describe("Path to SysProM file"),
203
+ id: z.string().describe("Node ID"),
204
+ }),
205
+ }, ({ path, id }) => {
206
+ const { doc } = loadDocument(path);
207
+ const result = removeNodeOp({ doc, id });
208
+ return {
209
+ content: [
210
+ {
211
+ type: "text",
212
+ text: JSON.stringify({
213
+ message: `Node ${id} removed`,
214
+ nodeCount: result.doc.nodes.length,
215
+ warnings: result.warnings,
216
+ }, null, 2),
217
+ },
218
+ ],
219
+ };
220
+ });
221
+ // Register update-node tool
222
+ server.registerTool("update-node", {
223
+ description: "Update node properties",
224
+ inputSchema: z.object({
225
+ path: z.string().describe("Path to SysProM file"),
226
+ id: z.string().describe("Node ID"),
227
+ fields: z.record(z.string(), z.unknown()).describe("Fields to update"),
228
+ }),
229
+ }, ({ path, id, fields }) => {
230
+ const { doc } = loadDocument(path);
231
+ // Validate fields are valid node property updates
232
+ const validFields = Object.entries(fields).reduce((acc, [key, value]) => {
233
+ // Allow common node fields; unknown fields are silently ignored
234
+ if ([
235
+ "name",
236
+ "description",
237
+ "status",
238
+ "context",
239
+ "options",
240
+ "selected",
241
+ "rationale",
242
+ "scope",
243
+ "operations",
244
+ "plan",
245
+ "propagation",
246
+ "includes",
247
+ "input",
248
+ "output",
249
+ "external_references",
250
+ ].includes(key)) {
251
+ acc[key] = value;
252
+ }
253
+ return acc;
254
+ }, {});
255
+ const updated = updateNodeOp({
256
+ doc,
257
+ id,
258
+ fields: validFields,
259
+ });
260
+ const node = updated.nodes.find((n) => n.id === id);
261
+ return {
262
+ content: [
263
+ {
264
+ type: "text",
265
+ text: JSON.stringify({ message: "Node updated", node }, null, 2),
266
+ },
267
+ ],
268
+ };
269
+ });
270
+ // Register add-relationship tool
271
+ server.registerTool("add-relationship", {
272
+ description: "Add a relationship between two nodes",
273
+ inputSchema: z.object({
274
+ path: z.string().describe("Path to SysProM file"),
275
+ from: z.string().describe("Source node ID"),
276
+ to: z.string().describe("Target node ID"),
277
+ type: z.string().describe("Relationship type"),
278
+ }),
279
+ }, ({ path, from, to, type }) => {
280
+ const { doc } = loadDocument(path);
281
+ const relType = RelationshipType.safeParse(type);
282
+ if (!relType.success) {
283
+ throw new Error(`Invalid relationship type: ${type}`);
284
+ }
285
+ const updated = addRelationshipOp({
286
+ doc,
287
+ rel: {
288
+ from,
289
+ to,
290
+ type: relType.data,
291
+ },
292
+ });
293
+ return {
294
+ content: [
295
+ {
296
+ type: "text",
297
+ text: JSON.stringify({
298
+ message: "Relationship added",
299
+ relationshipCount: (updated.relationships ?? []).length,
300
+ }, null, 2),
301
+ },
302
+ ],
303
+ };
304
+ });
305
+ // Register remove-relationship tool
306
+ server.registerTool("remove-relationship", {
307
+ description: "Remove a relationship between two nodes",
308
+ inputSchema: z.object({
309
+ path: z.string().describe("Path to SysProM file"),
310
+ from: z.string().describe("Source node ID"),
311
+ to: z.string().describe("Target node ID"),
312
+ type: z.string().describe("Relationship type"),
313
+ }),
314
+ }, ({ path, from, to, type }) => {
315
+ const { doc } = loadDocument(path);
316
+ const relType = RelationshipType.safeParse(type);
317
+ if (!relType.success) {
318
+ throw new Error(`Invalid relationship type: ${type}`);
319
+ }
320
+ const result = removeRelationshipOp({
321
+ doc,
322
+ from,
323
+ to,
324
+ type: relType.data,
325
+ });
326
+ return {
327
+ content: [
328
+ {
329
+ type: "text",
330
+ text: JSON.stringify({
331
+ message: "Relationship removed",
332
+ relationshipCount: (result.doc.relationships ?? []).length,
333
+ }, null, 2),
334
+ },
335
+ ],
336
+ };
337
+ });
338
+ // Start server
339
+ async function main() {
340
+ const transport = new StdioServerTransport();
341
+ await server.connect(transport);
342
+ console.error("SysProM MCP server running...");
343
+ }
344
+ main().catch((error) => {
345
+ const message = error instanceof Error ? error.message : String(error);
346
+ console.error("Server error:", message);
347
+ process.exit(1);
348
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sysprom",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "SysProM — System Provenance Model CLI and library",
5
5
  "author": "ExaDev",
6
6
  "homepage": "https://exadev.github.io/SysProM",
@@ -35,7 +35,8 @@
35
35
  ],
36
36
  "bin": {
37
37
  "sysprom": "dist/src/cli/index.js",
38
- "spm": "dist/src/cli/index.js"
38
+ "spm": "dist/src/cli/index.js",
39
+ "sysprom-mcp": "dist/src/mcp/index.js"
39
40
  },
40
41
  "scripts": {
41
42
  "build": "turbo run _build",
@@ -59,6 +60,7 @@
59
60
  "prepare": "husky"
60
61
  },
61
62
  "dependencies": {
63
+ "@modelcontextprotocol/sdk": "1.27.1",
62
64
  "commander": "14.0.3",
63
65
  "picocolors": "1.1.1",
64
66
  "zod": "4.3.6"
@@ -66,6 +68,7 @@
66
68
  "devDependencies": {
67
69
  "@commitlint/cli": "20.5.0",
68
70
  "@commitlint/config-conventional": "20.5.0",
71
+ "@eslint-community/eslint-plugin-eslint-comments": "4.7.1",
69
72
  "@eslint/js": "10.0.1",
70
73
  "@semantic-release/changelog": "6.0.3",
71
74
  "@semantic-release/git": "10.0.1",