@miraigent/ai-ops-templates 0.1.1 → 0.1.3

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 CHANGED
@@ -93,6 +93,7 @@ mcp/ai-ops-template-server/
93
93
  The server exposes tools for listing the public template catalog, fetching one template, building a short AI operations review checklist, and recommending a practical template sequence.
94
94
 
95
95
  This repository currently publishes a starter MCP server. It does not claim to provide a full orchestration or automation engine.
96
+ The server uses newline-delimited JSON-RPC over stdio for standard MCP clients.
96
97
 
97
98
  After npm publication, the intended command will be npx @miraigent/ai-ops-templates.
98
99
  Because this is a scoped public npm package, publication uses npm publish --access public.
@@ -16,7 +16,9 @@ review, and support process mapping.
16
16
  npm run mcp
17
17
  ```
18
18
 
19
- The server communicates over stdio with JSON-RPC Content-Length framing and implements:
19
+ The server communicates over stdio with newline-delimited JSON-RPC for standard MCP clients.
20
+
21
+ It implements:
20
22
 
21
23
  - `initialize`
22
24
  - `tools/list`
@@ -150,6 +150,18 @@ process.stdin.on("data", (chunk) => {
150
150
  });
151
151
 
152
152
  function readNextMessage() {
153
+ if (startsWithContentLength(buffer)) {
154
+ return readContentLengthMessage();
155
+ }
156
+
157
+ return readNewlineMessage();
158
+ }
159
+
160
+ function startsWithContentLength(input) {
161
+ return input.slice(0, 15).toString("utf8").toLowerCase().startsWith("content-length:");
162
+ }
163
+
164
+ function readContentLengthMessage() {
153
165
  const separator = buffer.indexOf("\r\n\r\n");
154
166
  if (separator === -1) {
155
167
  return false;
@@ -176,6 +188,22 @@ function readNextMessage() {
176
188
  return true;
177
189
  }
178
190
 
191
+ function readNewlineMessage() {
192
+ const lineEnd = buffer.indexOf("\n");
193
+ if (lineEnd === -1) {
194
+ return false;
195
+ }
196
+
197
+ const line = buffer.slice(0, lineEnd).toString("utf8").trim();
198
+ buffer = buffer.slice(lineEnd + 1);
199
+ if (!line) {
200
+ return true;
201
+ }
202
+
203
+ handleMessage(line);
204
+ return true;
205
+ }
206
+
179
207
  function handleMessage(body) {
180
208
  let request;
181
209
  try {
@@ -207,7 +235,7 @@ function route(method, params) {
207
235
  },
208
236
  serverInfo: {
209
237
  name: "miraigent-ai-ops-template-server",
210
- version: "0.1.0"
238
+ version: "0.1.3"
211
239
  }
212
240
  };
213
241
  }
@@ -280,10 +308,19 @@ function recommendTemplateSequence(args) {
280
308
  return {
281
309
  operation: args.operation ?? "general-ai-operations",
282
310
  note: "This is a non-memory AI operations helper sequence. It is not a MIRAI Memory engine or working memory MCP.",
311
+ nextSteps: buildNextSteps(),
283
312
  templates: sequence.map((id) => templates.find((template) => template.id === id))
284
313
  };
285
314
  }
286
315
 
316
+ function buildNextSteps() {
317
+ return {
318
+ label: "Learn more",
319
+ note: "Use these public templates as a starting point, then review Miraigent and Agent Memories resources for practical AI operations support.",
320
+ links: []
321
+ };
322
+ }
323
+
287
324
  function moveBefore(items, target, before) {
288
325
  const targetIndex = items.indexOf(target);
289
326
  const beforeIndex = items.indexOf(before);
@@ -345,5 +382,5 @@ function respond(id, result = null, error = null) {
345
382
  message.result = result;
346
383
  }
347
384
  const body = JSON.stringify(message);
348
- process.stdout.write(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`);
385
+ process.stdout.write(`${body}\n`);
349
386
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miraigent/ai-ops-templates",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Public AI operations templates and a starter MCP server for human-reviewed AI operations.",
5
5
  "homepage": "https://github.com/Miraigent/miraigent-ai-ops-templates#readme",
6
6
  "type": "module",
@@ -1,88 +1,98 @@
1
1
  import { spawn } from "node:child_process";
2
2
 
3
- const child = spawn(process.execPath, ["mcp/ai-ops-template-server/server.mjs"], {
4
- stdio: ["pipe", "pipe", "inherit"]
5
- });
3
+ await runSmokeTest("content-length");
4
+ await runSmokeTest("newline");
6
5
 
7
- const responses = [];
8
- let buffer = Buffer.alloc(0);
9
- child.stdout.setEncoding("utf8");
10
- child.stdout.on("data", (chunk) => {
11
- buffer = Buffer.concat([buffer, Buffer.from(chunk, "utf8")]);
12
- while (readNextResponse()) {
13
- // Keep draining complete messages.
14
- }
15
- });
6
+ async function runSmokeTest(framing) {
7
+ const child = spawn(process.execPath, ["mcp/ai-ops-template-server/server.mjs"], {
8
+ stdio: ["pipe", "pipe", "inherit"]
9
+ });
16
10
 
17
- send({ jsonrpc: "2.0", id: 1, method: "initialize", params: {} });
18
- send({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} });
19
- send({
20
- jsonrpc: "2.0",
21
- id: 3,
22
- method: "tools/call",
23
- params: {
24
- name: "get_ai_ops_template",
25
- arguments: { id: "human-review-gate-ai-drafts" }
26
- }
27
- });
28
- send({
29
- jsonrpc: "2.0",
30
- id: 4,
31
- method: "tools/call",
32
- params: {
33
- name: "build_ai_ops_review_checklist",
34
- arguments: { operation: "customer-support", riskLevel: "high" }
35
- }
36
- });
37
- send({
38
- jsonrpc: "2.0",
39
- id: 5,
40
- method: "tools/call",
41
- params: {
42
- name: "recommend_ai_ops_template_sequence",
43
- arguments: { operation: "support", priorities: ["privacy", "faq"] }
44
- }
45
- });
11
+ const responses = [];
12
+ let output = "";
13
+ child.stdout.on("data", (chunk) => {
14
+ output += chunk.toString("utf8");
15
+ assert(!output.includes("Content-Length:"), `${framing}: stdout must not include Content-Length headers`);
16
+ while (readNextResponseLine()) {
17
+ // Keep draining complete newline-delimited responses.
18
+ }
19
+ });
46
20
 
47
- await waitForResponses(5);
48
- child.kill();
21
+ send(child, framing, { jsonrpc: "2.0", id: 1, method: "initialize", params: {} });
22
+ send(child, framing, { jsonrpc: "2.0", id: 2, method: "tools/list", params: {} });
23
+ send(child, framing, {
24
+ jsonrpc: "2.0",
25
+ id: 3,
26
+ method: "tools/call",
27
+ params: {
28
+ name: "list_ai_ops_templates",
29
+ arguments: {}
30
+ }
31
+ });
32
+ send(child, framing, {
33
+ jsonrpc: "2.0",
34
+ id: 4,
35
+ method: "tools/call",
36
+ params: {
37
+ name: "get_ai_ops_template",
38
+ arguments: { id: "human-review-gate-ai-drafts" }
39
+ }
40
+ });
41
+ send(child, framing, {
42
+ jsonrpc: "2.0",
43
+ id: 5,
44
+ method: "tools/call",
45
+ params: {
46
+ name: "build_ai_ops_review_checklist",
47
+ arguments: { operation: "customer-support", riskLevel: "high" }
48
+ }
49
+ });
50
+ send(child, framing, {
51
+ jsonrpc: "2.0",
52
+ id: 6,
53
+ method: "tools/call",
54
+ params: {
55
+ name: "recommend_ai_ops_template_sequence",
56
+ arguments: { operation: "support", priorities: ["privacy", "faq"] }
57
+ }
58
+ });
49
59
 
50
- assert(responses[0].result.serverInfo.name === "miraigent-ai-ops-template-server", "initialize failed");
51
- assert(responses[1].result.tools.length === 4, "tools/list failed");
52
- assert(responses[2].result.content[0].text.includes("Human Review Gate"), "template lookup failed");
53
- assert(responses[3].result.content[0].text.includes("escalation owner"), "checklist build failed");
54
- assert(responses[4].result.content[0].text.includes("not a MIRAI Memory engine"), "sequence recommendation failed");
60
+ await waitForResponses(responses, 6);
61
+ child.kill();
55
62
 
56
- function send(payload) {
57
- const body = JSON.stringify(payload);
58
- child.stdin.write(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`);
59
- }
63
+ assert(responses[0].result.serverInfo.name === "miraigent-ai-ops-template-server", `${framing}: initialize failed`);
64
+ assert(responses[1].result.tools.length === 4, `${framing}: tools/list failed`);
65
+ assert(responses[2].result.content[0].text.includes("Human Review Gate"), `${framing}: template listing failed`);
66
+ assert(responses[3].result.content[0].text.includes("Human Review Gate"), `${framing}: template lookup failed`);
67
+ assert(responses[4].result.content[0].text.includes("escalation owner"), `${framing}: checklist build failed`);
68
+ assert(responses[5].result.content[0].text.includes("not a MIRAI Memory engine"), `${framing}: sequence recommendation failed`);
60
69
 
61
- function readNextResponse() {
62
- const separator = buffer.indexOf("\r\n\r\n");
63
- if (separator === -1) {
64
- return false;
65
- }
70
+ function readNextResponseLine() {
71
+ const lineEnd = output.indexOf("\n");
72
+ if (lineEnd === -1) {
73
+ return false;
74
+ }
66
75
 
67
- const header = buffer.slice(0, separator).toString("utf8");
68
- const match = header.match(/Content-Length: (\d+)/i);
69
- if (!match) {
70
- throw new Error(`Missing Content-Length in response: ${header}`);
76
+ const line = output.slice(0, lineEnd).trim();
77
+ output = output.slice(lineEnd + 1);
78
+ if (line) {
79
+ responses.push(JSON.parse(line));
80
+ }
81
+ return true;
71
82
  }
83
+ }
72
84
 
73
- const bodyStart = separator + 4;
74
- const bodyEnd = bodyStart + Number(match[1]);
75
- if (buffer.length < bodyEnd) {
76
- return false;
85
+ function send(child, framing, payload) {
86
+ const body = JSON.stringify(payload);
87
+ if (framing === "newline") {
88
+ child.stdin.write(`${body}\n`);
89
+ return;
77
90
  }
78
91
 
79
- const body = buffer.slice(bodyStart, bodyEnd).toString("utf8");
80
- buffer = buffer.slice(bodyEnd);
81
- responses.push(JSON.parse(body));
82
- return true;
92
+ child.stdin.write(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`);
83
93
  }
84
94
 
85
- async function waitForResponses(count) {
95
+ async function waitForResponses(responses, count) {
86
96
  const started = Date.now();
87
97
  while (responses.length < count) {
88
98
  if (Date.now() - started > 3000) {