par5-mcp 0.1.0 → 0.1.2
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/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +14 -0
- package/dist/index.js +135 -1
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.2](https://github.com/jrandolf/par5-mcp/compare/v0.1.1...v0.1.2) (2025-12-30)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add process cleanup on shutdown to manage active child processes ([68cbbbe](https://github.com/jrandolf/par5-mcp/commit/68cbbbec5188d9caba55bf17f8aa6a022e70cc17))
|
|
9
|
+
|
|
10
|
+
## [0.1.1](https://github.com/jrandolf/par5-mcp/compare/v0.1.0...v0.1.1) (2025-12-30)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* implement diceware list names and create_list_from_shell ([5e3e0ff](https://github.com/jrandolf/par5-mcp/commit/5e3e0ff9cbdce32a7d399f9eea8a7bd36b7d876f))
|
|
16
|
+
|
|
3
17
|
## 0.1.0 (2025-12-30)
|
|
4
18
|
|
|
5
19
|
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,31 @@ import { tmpdir } from "node:os";
|
|
|
6
6
|
import { basename, join } from "node:path";
|
|
7
7
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
8
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
|
+
import generatePassphrase from "eff-diceware-passphrase";
|
|
9
10
|
import { z } from "zod";
|
|
11
|
+
// Track all active child processes for cleanup on shutdown
|
|
12
|
+
const activeProcesses = new Set();
|
|
13
|
+
// Cleanup function to kill all active child processes
|
|
14
|
+
function cleanupProcesses() {
|
|
15
|
+
for (const child of activeProcesses) {
|
|
16
|
+
if (!child.killed) {
|
|
17
|
+
child.kill("SIGTERM");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
activeProcesses.clear();
|
|
21
|
+
}
|
|
22
|
+
// Register shutdown handlers
|
|
23
|
+
process.on("SIGTERM", () => {
|
|
24
|
+
cleanupProcesses();
|
|
25
|
+
process.exit(0);
|
|
26
|
+
});
|
|
27
|
+
process.on("SIGINT", () => {
|
|
28
|
+
cleanupProcesses();
|
|
29
|
+
process.exit(0);
|
|
30
|
+
});
|
|
31
|
+
process.on("exit", () => {
|
|
32
|
+
cleanupProcesses();
|
|
33
|
+
});
|
|
10
34
|
// Helper to create a safe filename from an item string
|
|
11
35
|
function toSafeFilename(item) {
|
|
12
36
|
// Get basename if it's a path
|
|
@@ -28,6 +52,8 @@ function runCommandToFiles(command, stdoutFile, stderrFile, options = {}) {
|
|
|
28
52
|
const child = spawn("sh", ["-c", command], {
|
|
29
53
|
stdio: ["ignore", "pipe", "pipe"],
|
|
30
54
|
});
|
|
55
|
+
// Track the child process for cleanup on shutdown
|
|
56
|
+
activeProcesses.add(child);
|
|
31
57
|
let timeoutId;
|
|
32
58
|
if (options.timeout) {
|
|
33
59
|
timeoutId = setTimeout(() => {
|
|
@@ -37,6 +63,7 @@ function runCommandToFiles(command, stdoutFile, stderrFile, options = {}) {
|
|
|
37
63
|
child.stdout.pipe(stdoutStream);
|
|
38
64
|
child.stderr.pipe(stderrStream);
|
|
39
65
|
child.on("close", async () => {
|
|
66
|
+
activeProcesses.delete(child);
|
|
40
67
|
if (timeoutId)
|
|
41
68
|
clearTimeout(timeoutId);
|
|
42
69
|
stdoutStream.end();
|
|
@@ -46,6 +73,7 @@ function runCommandToFiles(command, stdoutFile, stderrFile, options = {}) {
|
|
|
46
73
|
resolve();
|
|
47
74
|
});
|
|
48
75
|
child.on("error", async (err) => {
|
|
76
|
+
activeProcesses.delete(child);
|
|
49
77
|
if (timeoutId)
|
|
50
78
|
clearTimeout(timeoutId);
|
|
51
79
|
stderrStream.write(`\nERROR: ${err.message}\n`);
|
|
@@ -71,6 +99,17 @@ async function runInBatches(tasks) {
|
|
|
71
99
|
const outputDir = join(tmpdir(), "par5-mcp-results");
|
|
72
100
|
// Store for lists
|
|
73
101
|
const lists = new Map();
|
|
102
|
+
// Generate a unique diceware list ID (3 words joined with hyphens)
|
|
103
|
+
function generateListId() {
|
|
104
|
+
const words = generatePassphrase(3);
|
|
105
|
+
let id = words.join("-");
|
|
106
|
+
// Ensure uniqueness by appending more words if needed
|
|
107
|
+
while (lists.has(id)) {
|
|
108
|
+
const extraWord = generatePassphrase(1)[0];
|
|
109
|
+
id = `${id}-${extraWord}`;
|
|
110
|
+
}
|
|
111
|
+
return id;
|
|
112
|
+
}
|
|
74
113
|
// Create the MCP server
|
|
75
114
|
const server = new McpServer({
|
|
76
115
|
name: "par5-mcp",
|
|
@@ -96,7 +135,7 @@ EXAMPLE: To process files ["src/a.ts", "src/b.ts", "src/c.ts"], first create a l
|
|
|
96
135
|
.describe("Array of items to store in the list. Each item can be a file path, URL, identifier, or any string that will be substituted into commands or prompts."),
|
|
97
136
|
},
|
|
98
137
|
}, async ({ items }) => {
|
|
99
|
-
const id =
|
|
138
|
+
const id = generateListId();
|
|
100
139
|
lists.set(id, items);
|
|
101
140
|
return {
|
|
102
141
|
content: [
|
|
@@ -107,6 +146,101 @@ EXAMPLE: To process files ["src/a.ts", "src/b.ts", "src/c.ts"], first create a l
|
|
|
107
146
|
],
|
|
108
147
|
};
|
|
109
148
|
});
|
|
149
|
+
// Tool: create_list_from_shell
|
|
150
|
+
server.registerTool("create_list_from_shell", {
|
|
151
|
+
description: `Creates a list by running a shell command and parsing its newline-delimited output.
|
|
152
|
+
|
|
153
|
+
WHEN TO USE:
|
|
154
|
+
- When you need to create a list from command output (e.g., find, ls, grep, git ls-files)
|
|
155
|
+
- When the list of items to process is determined by a shell command
|
|
156
|
+
- As an alternative to manually specifying items in create_list
|
|
157
|
+
|
|
158
|
+
EXAMPLES:
|
|
159
|
+
- "find src -name '*.ts'" to get all TypeScript files
|
|
160
|
+
- "git ls-files '*.tsx'" to get all tracked TSX files
|
|
161
|
+
- "ls *.json" to get all JSON files in current directory
|
|
162
|
+
- "grep -l 'TODO' src/**/*.ts" to get files containing TODO
|
|
163
|
+
|
|
164
|
+
WORKFLOW:
|
|
165
|
+
1. Call create_list_from_shell with your command
|
|
166
|
+
2. The command's stdout is split by newlines to create list items
|
|
167
|
+
3. Empty lines are filtered out
|
|
168
|
+
4. Use the returned list_id with run_shell_across_list or run_agent_across_list`,
|
|
169
|
+
inputSchema: {
|
|
170
|
+
command: z
|
|
171
|
+
.string()
|
|
172
|
+
.describe("Shell command to run. Its stdout will be split by newlines to create list items. Example: 'find src -name \"*.ts\"' or 'git ls-files'"),
|
|
173
|
+
},
|
|
174
|
+
}, async ({ command }) => {
|
|
175
|
+
return new Promise((resolve) => {
|
|
176
|
+
const child = spawn("sh", ["-c", command], {
|
|
177
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
178
|
+
});
|
|
179
|
+
// Track the child process for cleanup on shutdown
|
|
180
|
+
activeProcesses.add(child);
|
|
181
|
+
let stdout = "";
|
|
182
|
+
let stderr = "";
|
|
183
|
+
child.stdout.on("data", (data) => {
|
|
184
|
+
stdout += data.toString();
|
|
185
|
+
});
|
|
186
|
+
child.stderr.on("data", (data) => {
|
|
187
|
+
stderr += data.toString();
|
|
188
|
+
});
|
|
189
|
+
child.on("close", (code) => {
|
|
190
|
+
activeProcesses.delete(child);
|
|
191
|
+
if (code !== 0 && stderr) {
|
|
192
|
+
resolve({
|
|
193
|
+
content: [
|
|
194
|
+
{
|
|
195
|
+
type: "text",
|
|
196
|
+
text: `Error: Command exited with code ${code}.\n\nstderr:\n${stderr}`,
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
isError: true,
|
|
200
|
+
});
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
// Split by newlines and filter out empty lines
|
|
204
|
+
const items = stdout
|
|
205
|
+
.split("\n")
|
|
206
|
+
.map((line) => line.trim())
|
|
207
|
+
.filter((line) => line.length > 0);
|
|
208
|
+
if (items.length === 0) {
|
|
209
|
+
resolve({
|
|
210
|
+
content: [
|
|
211
|
+
{
|
|
212
|
+
type: "text",
|
|
213
|
+
text: `Warning: Command produced no output. No list was created.${stderr ? `\n\nstderr:\n${stderr}` : ""}`,
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
});
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const id = generateListId();
|
|
220
|
+
lists.set(id, items);
|
|
221
|
+
resolve({
|
|
222
|
+
content: [
|
|
223
|
+
{
|
|
224
|
+
type: "text",
|
|
225
|
+
text: `Successfully created a list with ${items.length} items from command output. The list ID is "${id}". You can now use this ID with run_shell_across_list or run_agent_across_list to process each item in parallel.${stderr ? `\n\nNote: Command produced stderr output:\n${stderr}` : ""}`,
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
child.on("error", (err) => {
|
|
231
|
+
activeProcesses.delete(child);
|
|
232
|
+
resolve({
|
|
233
|
+
content: [
|
|
234
|
+
{
|
|
235
|
+
type: "text",
|
|
236
|
+
text: `Error: Failed to execute command: ${err.message}`,
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
isError: true,
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
});
|
|
110
244
|
// Tool: get_list
|
|
111
245
|
server.registerTool("get_list", {
|
|
112
246
|
description: `Retrieves the items in an existing list by its ID.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "par5-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "MCP server for parallel list operations - run shell commands and AI agents across lists in parallel",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -39,6 +39,8 @@
|
|
|
39
39
|
"type": "module",
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
42
|
+
"@types/eff-diceware-passphrase": "^3.0.2",
|
|
43
|
+
"eff-diceware-passphrase": "^3.0.0",
|
|
42
44
|
"zod": "^4.2.1"
|
|
43
45
|
},
|
|
44
46
|
"devDependencies": {
|