@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
|
|
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.
|
|
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(
|
|
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.
|
|
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",
|
package/scripts/smoke-test.mjs
CHANGED
|
@@ -1,88 +1,98 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
});
|
|
3
|
+
await runSmokeTest("content-length");
|
|
4
|
+
await runSmokeTest("newline");
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
48
|
-
child.
|
|
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
|
-
|
|
51
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
function readNextResponseLine() {
|
|
71
|
+
const lineEnd = output.indexOf("\n");
|
|
72
|
+
if (lineEnd === -1) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
66
75
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
76
|
-
|
|
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
|
-
|
|
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) {
|