@ebowwa/run-path-mcp 1.0.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.
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@ebowwa/run-path-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for PATH manipulation across all package managers (npm, bun, pnpm, yarn, deno)",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "run-path-mcp": "./dist/index.js"
10
+ },
11
+ "scripts": {
12
+ "build": "bun build ./src/index.ts --outdir ./dist --target bun --minify --sourcemap && tsc --emitDeclarationOnly",
13
+ "dev": "bun run --watch src/index.ts",
14
+ "start": "bun dist/index.js",
15
+ "typecheck": "tsc --noEmit"
16
+ },
17
+ "files": [
18
+ "dist/",
19
+ "src/",
20
+ "README.md"
21
+ ],
22
+ "dependencies": {
23
+ "@ebowwa/run-path": "^1.0.0",
24
+ "@modelcontextprotocol/sdk": "^1.0.0",
25
+ "zod": "^3.24.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^22.0.0",
29
+ "typescript": "^5.0.0"
30
+ },
31
+ "engines": {
32
+ "bun": ">=1.0.0"
33
+ },
34
+ "keywords": [
35
+ "mcp",
36
+ "path",
37
+ "npm",
38
+ "bun",
39
+ "pnpm",
40
+ "yarn",
41
+ "deno",
42
+ "environment",
43
+ "cli"
44
+ ],
45
+ "author": "ebowwa",
46
+ "license": "MIT",
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "https://github.com/ebowwa/codespaces",
50
+ "directory": "packages/mcp/run-path"
51
+ }
52
+ }
package/src/index.ts ADDED
@@ -0,0 +1,495 @@
1
+ #!/usr/bin/env bun
2
+ // ============================================================================
3
+ // Run Path MCP Server
4
+ // ============================================================================
5
+ // MCP server for PATH manipulation across all package managers.
6
+ // Provides tools to get augmented PATH for npm, bun, pnpm, yarn, deno.
7
+ // ============================================================================
8
+
9
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
10
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
11
+ import {
12
+ CallToolRequestSchema,
13
+ ListToolsRequestSchema,
14
+ } from '@modelcontextprotocol/sdk/types.js';
15
+ import { z } from 'zod';
16
+ import {
17
+ runPath,
18
+ runPathEnv,
19
+ bunRunPath,
20
+ bunRunPathEnv,
21
+ npmRunPath,
22
+ npmRunPathEnv,
23
+ pnpmRunPath,
24
+ pnpmRunPathEnv,
25
+ yarnRunPath,
26
+ yarnRunPathEnv,
27
+ } from '@ebowwa/run-path';
28
+
29
+ // ============================================================================
30
+ // HTTP Transport (Future Implementation)
31
+ // ============================================================================
32
+ // For HTTP/SSE transport, you would use:
33
+ //
34
+ // import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
35
+ // import express from 'express';
36
+ //
37
+ // const app = express();
38
+ // const server = new Server(...);
39
+ //
40
+ // app.get('/sse', async (req, res) => {
41
+ // const transport = new SSEServerTransport('/message', res);
42
+ // await server.connect(transport);
43
+ // });
44
+ //
45
+ // app.post('/message', async (req, res) => {
46
+ // // Handle incoming messages
47
+ // });
48
+ //
49
+ // app.listen(3000, () => {
50
+ // console.log('MCP server running on http://localhost:3000');
51
+ // });
52
+ //
53
+ // See: https://modelcontextprotocol.io/docs/concepts/transports
54
+ // ============================================================================
55
+
56
+ // ============================================================================
57
+ // MCP Server Setup
58
+ // ============================================================================
59
+
60
+ const server = new Server(
61
+ {
62
+ name: '@ebowwa/run-path-mcp',
63
+ version: '1.0.0',
64
+ },
65
+ {
66
+ capabilities: {
67
+ tools: {},
68
+ },
69
+ }
70
+ );
71
+
72
+ // ============================================================================
73
+ // Tool Definitions
74
+ // ============================================================================
75
+
76
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
77
+ tools: [
78
+ // Main PATH tools
79
+ {
80
+ name: 'get_run_path',
81
+ description: 'Get augmented PATH string with all package manager binaries (npm, bun, pnpm, yarn, deno). Use this before spawning child processes to ensure they can find locally installed binaries.',
82
+ inputSchema: {
83
+ type: 'object',
84
+ properties: {
85
+ cwd: {
86
+ type: 'string',
87
+ description: 'Working directory (default: process.cwd())',
88
+ },
89
+ preferLocal: {
90
+ type: 'boolean',
91
+ description: 'Include local node_modules/.bin (default: true)',
92
+ },
93
+ includeGlobal: {
94
+ type: 'boolean',
95
+ description: 'Include global package manager paths (default: true)',
96
+ },
97
+ includeBun: {
98
+ type: 'boolean',
99
+ description: 'Include Bun global paths (default: true)',
100
+ },
101
+ includeNpm: {
102
+ type: 'boolean',
103
+ description: 'Include npm global paths (default: true)',
104
+ },
105
+ includePnpm: {
106
+ type: 'boolean',
107
+ description: 'Include pnpm global paths (default: true)',
108
+ },
109
+ includeYarn: {
110
+ type: 'boolean',
111
+ description: 'Include Yarn global paths (default: true)',
112
+ },
113
+ includeDeno: {
114
+ type: 'boolean',
115
+ description: 'Include Deno bin path (default: true)',
116
+ },
117
+ },
118
+ },
119
+ },
120
+ {
121
+ name: 'get_run_path_env',
122
+ description: 'Get complete environment object with augmented PATH. Use this as the `env` option when spawning child processes.',
123
+ inputSchema: {
124
+ type: 'object',
125
+ properties: {
126
+ cwd: {
127
+ type: 'string',
128
+ description: 'Working directory (default: process.cwd())',
129
+ },
130
+ preferLocal: {
131
+ type: 'boolean',
132
+ description: 'Include local node_modules/.bin (default: true)',
133
+ },
134
+ includeGlobal: {
135
+ type: 'boolean',
136
+ description: 'Include global package manager paths (default: true)',
137
+ },
138
+ },
139
+ },
140
+ },
141
+
142
+ // Package manager specific tools
143
+ {
144
+ name: 'get_bun_path',
145
+ description: 'Get PATH with only Bun-specific paths added (~/.bun/bin, ~/.bun/install/global/)',
146
+ inputSchema: {
147
+ type: 'object',
148
+ properties: {
149
+ cwd: {
150
+ type: 'string',
151
+ description: 'Working directory (default: process.cwd())',
152
+ },
153
+ },
154
+ },
155
+ },
156
+ {
157
+ name: 'get_npm_path',
158
+ description: 'Get PATH with only npm-specific paths added (npm global prefix, /usr/local/bin)',
159
+ inputSchema: {
160
+ type: 'object',
161
+ properties: {
162
+ cwd: {
163
+ type: 'string',
164
+ description: 'Working directory (default: process.cwd())',
165
+ },
166
+ },
167
+ },
168
+ },
169
+ {
170
+ name: 'get_pnpm_path',
171
+ description: 'Get PATH with only pnpm-specific paths added (PNPM_HOME, ~/.local/share/pnpm)',
172
+ inputSchema: {
173
+ type: 'object',
174
+ properties: {
175
+ cwd: {
176
+ type: 'string',
177
+ description: 'Working directory (default: process.cwd())',
178
+ },
179
+ },
180
+ },
181
+ },
182
+ {
183
+ name: 'get_yarn_path',
184
+ description: 'Get PATH with only Yarn-specific paths added (~/.yarn/bin, yarn global)',
185
+ inputSchema: {
186
+ type: 'object',
187
+ properties: {
188
+ cwd: {
189
+ type: 'string',
190
+ description: 'Working directory (default: process.cwd())',
191
+ },
192
+ },
193
+ },
194
+ },
195
+
196
+ // Utility tools
197
+ {
198
+ name: 'detect_package_managers',
199
+ description: 'Detect which package managers are installed and available on the system',
200
+ inputSchema: {
201
+ type: 'object',
202
+ properties: {},
203
+ },
204
+ },
205
+ {
206
+ name: 'get_bin_paths',
207
+ description: 'List all binary directories that would be added to PATH',
208
+ inputSchema: {
209
+ type: 'object',
210
+ properties: {
211
+ cwd: {
212
+ type: 'string',
213
+ description: 'Working directory (default: process.cwd())',
214
+ },
215
+ includeExisting: {
216
+ type: 'boolean',
217
+ description: 'Include paths that already exist in PATH (default: false)',
218
+ },
219
+ },
220
+ },
221
+ },
222
+ ],
223
+ }));
224
+
225
+ // ============================================================================
226
+ // Tool Handlers
227
+ // ============================================================================
228
+
229
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
230
+ const { name, arguments: args } = request.params;
231
+
232
+ try {
233
+ switch (name) {
234
+ case 'get_run_path':
235
+ return handleGetRunPath(args);
236
+ case 'get_run_path_env':
237
+ return handleGetRunPathEnv(args);
238
+ case 'get_bun_path':
239
+ return handleGetBunPath(args);
240
+ case 'get_npm_path':
241
+ return handleGetNpmPath(args);
242
+ case 'get_pnpm_path':
243
+ return handleGetPnpmPath(args);
244
+ case 'get_yarn_path':
245
+ return handleGetYarnPath(args);
246
+ case 'detect_package_managers':
247
+ return handleDetectPackageManagers();
248
+ case 'get_bin_paths':
249
+ return handleGetBinPaths(args);
250
+ default:
251
+ throw new Error(`Unknown tool: ${name}`);
252
+ }
253
+ } catch (error) {
254
+ return {
255
+ content: [
256
+ {
257
+ type: 'text',
258
+ text: JSON.stringify({
259
+ error: true,
260
+ message: error instanceof Error ? error.message : String(error),
261
+ }, null, 2),
262
+ },
263
+ ],
264
+ isError: true,
265
+ };
266
+ }
267
+ });
268
+
269
+ // ============================================================================
270
+ // Handlers
271
+ // ============================================================================
272
+
273
+ function handleGetRunPath(args: unknown) {
274
+ const schema = z.object({
275
+ cwd: z.string().optional(),
276
+ preferLocal: z.boolean().optional(),
277
+ includeGlobal: z.boolean().optional(),
278
+ includeBun: z.boolean().optional(),
279
+ includeNpm: z.boolean().optional(),
280
+ includePnpm: z.boolean().optional(),
281
+ includeYarn: z.boolean().optional(),
282
+ includeDeno: z.boolean().optional(),
283
+ });
284
+
285
+ const options = schema.parse(args ?? {});
286
+ const path = runPath(options);
287
+
288
+ return {
289
+ content: [
290
+ {
291
+ type: 'text',
292
+ text: JSON.stringify({
293
+ path,
294
+ options,
295
+ paths: path.split(':'),
296
+ }, null, 2),
297
+ },
298
+ ],
299
+ };
300
+ }
301
+
302
+ function handleGetRunPathEnv(args: unknown) {
303
+ const schema = z.object({
304
+ cwd: z.string().optional(),
305
+ preferLocal: z.boolean().optional(),
306
+ includeGlobal: z.boolean().optional(),
307
+ });
308
+
309
+ const options = schema.parse(args ?? {});
310
+ const env = runPathEnv(options);
311
+
312
+ return {
313
+ content: [
314
+ {
315
+ type: 'text',
316
+ text: JSON.stringify(env, null, 2),
317
+ },
318
+ ],
319
+ };
320
+ }
321
+
322
+ function handleGetBunPath(args: unknown) {
323
+ const schema = z.object({
324
+ cwd: z.string().optional(),
325
+ });
326
+
327
+ const { cwd } = schema.parse(args ?? {});
328
+ const path = bunRunPath({ cwd });
329
+
330
+ return {
331
+ content: [
332
+ {
333
+ type: 'text',
334
+ text: JSON.stringify({
335
+ path,
336
+ paths: path.split(':'),
337
+ }, null, 2),
338
+ },
339
+ ],
340
+ };
341
+ }
342
+
343
+ function handleGetNpmPath(args: unknown) {
344
+ const schema = z.object({
345
+ cwd: z.string().optional(),
346
+ });
347
+
348
+ const { cwd } = schema.parse(args ?? {});
349
+ const path = npmRunPath({ cwd });
350
+
351
+ return {
352
+ content: [
353
+ {
354
+ type: 'text',
355
+ text: JSON.stringify({
356
+ path,
357
+ paths: path.split(':'),
358
+ }, null, 2),
359
+ },
360
+ ],
361
+ };
362
+ }
363
+
364
+ function handleGetPnpmPath(args: unknown) {
365
+ const schema = z.object({
366
+ cwd: z.string().optional(),
367
+ });
368
+
369
+ const { cwd } = schema.parse(args ?? {});
370
+ const path = pnpmRunPath({ cwd });
371
+
372
+ return {
373
+ content: [
374
+ {
375
+ type: 'text',
376
+ text: JSON.stringify({
377
+ path,
378
+ paths: path.split(':'),
379
+ }, null, 2),
380
+ },
381
+ ],
382
+ };
383
+ }
384
+
385
+ function handleGetYarnPath(args: unknown) {
386
+ const schema = z.object({
387
+ cwd: z.string().optional(),
388
+ });
389
+
390
+ const { cwd } = schema.parse(args ?? {});
391
+ const path = yarnRunPath({ cwd });
392
+
393
+ return {
394
+ content: [
395
+ {
396
+ type: 'text',
397
+ text: JSON.stringify({
398
+ path,
399
+ paths: path.split(':'),
400
+ }, null, 2),
401
+ },
402
+ ],
403
+ };
404
+ }
405
+
406
+ async function handleDetectPackageManagers() {
407
+ const { exec } = await import('node:child_process');
408
+ const { promisify } = await import('node:util');
409
+ const execAsync = promisify(exec);
410
+
411
+ const managers = [
412
+ { name: 'npm', command: 'npm --version' },
413
+ { name: 'bun', command: 'bun --version' },
414
+ { name: 'pnpm', command: 'pnpm --version' },
415
+ { name: 'yarn', command: 'yarn --version' },
416
+ { name: 'deno', command: 'deno --version' },
417
+ ];
418
+
419
+ const results: Record<string, { installed: boolean; version?: string; error?: string }> = {};
420
+
421
+ await Promise.all(
422
+ managers.map(async ({ name, command }) => {
423
+ try {
424
+ const { stdout } = await execAsync(command, { timeout: 5000 });
425
+ const version = stdout.trim().split('\n')[0];
426
+ results[name] = { installed: true, version };
427
+ } catch {
428
+ results[name] = { installed: false };
429
+ }
430
+ })
431
+ );
432
+
433
+ return {
434
+ content: [
435
+ {
436
+ type: 'text',
437
+ text: JSON.stringify(results, null, 2),
438
+ },
439
+ ],
440
+ };
441
+ }
442
+
443
+ function handleGetBinPaths(args: unknown) {
444
+ const schema = z.object({
445
+ cwd: z.string().optional(),
446
+ includeExisting: z.boolean().optional(),
447
+ });
448
+
449
+ const { cwd, includeExisting = false } = schema.parse(args ?? {});
450
+
451
+ // Get augmented path and original path
452
+ const augmentedPath = runPath({ cwd, addExecPath: false });
453
+ const originalPath = process.env.PATH ?? '';
454
+
455
+ const augmentedPaths = augmentedPath.split(':');
456
+ const originalPaths = originalPath.split(':');
457
+
458
+ // Find paths that were added
459
+ const addedPaths = augmentedPaths.filter(
460
+ (p) => includeExisting || !originalPaths.includes(p)
461
+ );
462
+
463
+ return {
464
+ content: [
465
+ {
466
+ type: 'text',
467
+ text: JSON.stringify({
468
+ addedCount: addedPaths.length,
469
+ addedPaths,
470
+ originalCount: originalPaths.length,
471
+ augmentedCount: augmentedPaths.length,
472
+ }, null, 2),
473
+ },
474
+ ],
475
+ };
476
+ }
477
+
478
+ // ============================================================================
479
+ // Server Startup
480
+ // ============================================================================
481
+
482
+ async function main() {
483
+ // Stdio transport (current implementation)
484
+ const transport = new StdioServerTransport();
485
+ await server.connect(transport);
486
+
487
+ console.error('Run Path MCP Server running');
488
+ console.error('Version: 1.0.0');
489
+ console.error('Transport: stdio');
490
+ }
491
+
492
+ main().catch((error) => {
493
+ console.error('Fatal error:', error);
494
+ process.exit(1);
495
+ });