microservice-kg 0.1.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,260 @@
1
+ #!/usr/bin/env node
2
+
3
+ import process from "node:process";
4
+ import path from "node:path";
5
+ import {
6
+ GraphStore,
7
+ getDependencyPath,
8
+ getEdgeDetails,
9
+ getServiceContext,
10
+ getServiceImpact,
11
+ listServices,
12
+ } from "./graph-query.mjs";
13
+
14
+ const DEFAULT_GRAPH_PATH = process.env.MICROSERVICE_KG_GRAPH
15
+ ? path.resolve(process.env.MICROSERVICE_KG_GRAPH)
16
+ : path.resolve(process.cwd(), "output", "service-graph.json");
17
+
18
+ const store = new GraphStore({ defaultGraphPath: DEFAULT_GRAPH_PATH });
19
+
20
+ const TOOLS = [
21
+ {
22
+ name: "analyze_workspace",
23
+ description: "Analyze a workspace directory and generate a consolidated microservice graph.",
24
+ inputSchema: {
25
+ type: "object",
26
+ properties: {
27
+ inputDir: { type: "string", description: "Root directory containing multiple services." },
28
+ outputDir: { type: "string", description: "Optional output directory for graph artifacts." },
29
+ },
30
+ required: ["inputDir"],
31
+ additionalProperties: false,
32
+ },
33
+ },
34
+ {
35
+ name: "list_services",
36
+ description: "List all logical services in the currently loaded graph.",
37
+ inputSchema: {
38
+ type: "object",
39
+ properties: {
40
+ includeStats: { type: "boolean", description: "Include endpoint, client, and linkage counts." },
41
+ },
42
+ additionalProperties: false,
43
+ },
44
+ },
45
+ {
46
+ name: "service_context",
47
+ description: "Show incoming and outgoing service links for one service.",
48
+ inputSchema: {
49
+ type: "object",
50
+ properties: {
51
+ serviceId: { type: "string", description: "Logical service id." },
52
+ },
53
+ required: ["serviceId"],
54
+ additionalProperties: false,
55
+ },
56
+ },
57
+ {
58
+ name: "edge_details",
59
+ description: "Show detailed evidence for one directed service edge, including client classes and call sites.",
60
+ inputSchema: {
61
+ type: "object",
62
+ properties: {
63
+ sourceServiceId: { type: "string" },
64
+ targetServiceId: { type: "string" },
65
+ },
66
+ required: ["sourceServiceId", "targetServiceId"],
67
+ additionalProperties: false,
68
+ },
69
+ },
70
+ {
71
+ name: "dependency_path",
72
+ description: "Find a service-to-service path between two services.",
73
+ inputSchema: {
74
+ type: "object",
75
+ properties: {
76
+ sourceServiceId: { type: "string" },
77
+ targetServiceId: { type: "string" },
78
+ maxDepth: {
79
+ type: "integer",
80
+ minimum: 1,
81
+ description: "Optional hop limit. If omitted, traversal uses the full logical graph depth.",
82
+ },
83
+ direction: {
84
+ type: "string",
85
+ enum: ["downstream", "upstream"],
86
+ description: "Use downstream for normal call direction or upstream to walk reverse dependencies.",
87
+ },
88
+ },
89
+ required: ["sourceServiceId", "targetServiceId"],
90
+ additionalProperties: false,
91
+ },
92
+ },
93
+ {
94
+ name: "service_impact",
95
+ description: "Traverse service-level blast radius from one service.",
96
+ inputSchema: {
97
+ type: "object",
98
+ properties: {
99
+ serviceId: { type: "string" },
100
+ direction: {
101
+ type: "string",
102
+ enum: ["downstream", "upstream"],
103
+ },
104
+ maxDepth: {
105
+ type: "integer",
106
+ minimum: 1,
107
+ description: "Optional hop limit. If omitted, traversal uses the full logical graph depth.",
108
+ },
109
+ },
110
+ required: ["serviceId"],
111
+ additionalProperties: false,
112
+ },
113
+ },
114
+ ];
115
+
116
+ async function dispatchToolCall(name, args = {}) {
117
+ switch (name) {
118
+ case "analyze_workspace":
119
+ return store.analyzeAndLoad(args);
120
+ case "list_services":
121
+ return listServices(store, args);
122
+ case "service_context":
123
+ return getServiceContext(store, args);
124
+ case "edge_details":
125
+ return getEdgeDetails(store, args);
126
+ case "dependency_path":
127
+ return getDependencyPath(store, args);
128
+ case "service_impact":
129
+ return getServiceImpact(store, args);
130
+ default:
131
+ throw new Error(`Unknown tool: ${name}`);
132
+ }
133
+ }
134
+
135
+ function send(message) {
136
+ const json = JSON.stringify(message);
137
+ const payload = `Content-Length: ${Buffer.byteLength(json, "utf8")}\r\n\r\n${json}`;
138
+ process.stdout.write(payload);
139
+ }
140
+
141
+ function success(id, result) {
142
+ send({ jsonrpc: "2.0", id, result });
143
+ }
144
+
145
+ function failure(id, error) {
146
+ send({
147
+ jsonrpc: "2.0",
148
+ id,
149
+ error: {
150
+ code: -32000,
151
+ message: error?.message || String(error),
152
+ },
153
+ });
154
+ }
155
+
156
+ async function handleMessage(message) {
157
+ const { id, method, params } = message;
158
+
159
+ if (method === "initialize") {
160
+ success(id, {
161
+ protocolVersion: "2024-11-05",
162
+ capabilities: {
163
+ tools: {},
164
+ },
165
+ serverInfo: {
166
+ name: "microservice-kg",
167
+ version: "0.1.0",
168
+ },
169
+ });
170
+ return;
171
+ }
172
+
173
+ if (method === "notifications/initialized") {
174
+ return;
175
+ }
176
+
177
+ if (method === "ping") {
178
+ success(id, {});
179
+ return;
180
+ }
181
+
182
+ if (method === "tools/list") {
183
+ success(id, { tools: TOOLS });
184
+ return;
185
+ }
186
+
187
+ if (method === "tools/call") {
188
+ try {
189
+ const result = await dispatchToolCall(params?.name, params?.arguments || {});
190
+ success(id, {
191
+ content: [
192
+ {
193
+ type: "text",
194
+ text: JSON.stringify(result, null, 2),
195
+ },
196
+ ],
197
+ });
198
+ } catch (error) {
199
+ success(id, {
200
+ isError: true,
201
+ content: [
202
+ {
203
+ type: "text",
204
+ text: error?.stack || error?.message || String(error),
205
+ },
206
+ ],
207
+ });
208
+ }
209
+ return;
210
+ }
211
+
212
+ if (id !== undefined) {
213
+ failure(id, new Error(`Unsupported method: ${method}`));
214
+ }
215
+ }
216
+
217
+ let buffer = Buffer.alloc(0);
218
+ process.stdin.on("data", async (chunk) => {
219
+ buffer = Buffer.concat([buffer, chunk]);
220
+
221
+ while (true) {
222
+ const separatorIndex = buffer.indexOf("\r\n\r\n");
223
+ if (separatorIndex === -1) {
224
+ return;
225
+ }
226
+
227
+ const headerText = buffer.slice(0, separatorIndex).toString("utf8");
228
+ const lengthMatch = headerText.match(/Content-Length:\s*(\d+)/i);
229
+ if (!lengthMatch) {
230
+ buffer = buffer.slice(separatorIndex + 4);
231
+ continue;
232
+ }
233
+
234
+ const contentLength = Number(lengthMatch[1]);
235
+ const totalLength = separatorIndex + 4 + contentLength;
236
+ if (buffer.length < totalLength) {
237
+ return;
238
+ }
239
+
240
+ const body = buffer.slice(separatorIndex + 4, totalLength).toString("utf8");
241
+ buffer = buffer.slice(totalLength);
242
+
243
+ try {
244
+ const message = JSON.parse(body);
245
+ await handleMessage(message);
246
+ } catch (error) {
247
+ if (typeof error !== "undefined") {
248
+ send({
249
+ jsonrpc: "2.0",
250
+ error: {
251
+ code: -32700,
252
+ message: error?.message || String(error),
253
+ },
254
+ });
255
+ }
256
+ }
257
+ }
258
+ });
259
+
260
+ process.stdin.resume();