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.
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.1.0"
2
+ ".": "0.1.2"
3
3
  }
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 = randomUUID();
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.0",
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": {