npm-run-mcp-server 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+
package/README.md ADDED
@@ -0,0 +1,308 @@
1
+ # npm-run-mcp-server
2
+
3
+ <div align="center">
4
+
5
+ *A Model Context Protocol (MCP) server that exposes your project's `package.json` scripts as tools for AI agents.*
6
+
7
+ [![Test](https://github.com/fstubner/npm-run-mcp-server/workflows/Test/badge.svg)](https://github.com/fstubner/npm-run-mcp-server/actions/workflows/test.yml)
8
+ [![Build & Publish](https://github.com/fstubner/npm-run-mcp-server/workflows/Build%20&%20Publish/badge.svg)](https://github.com/fstubner/npm-run-mcp-server/actions/workflows/build-and-publish.yml)
9
+ [![NPM Version](https://img.shields.io/npm/v/npm-run-mcp-server.svg)](https://www.npmjs.com/package/npm-run-mcp-server)
10
+ [![NPM Installs](https://img.shields.io/npm/dt/npm-run-mcp-server.svg)](https://www.npmjs.com/package/npm-run-mcp-server)
11
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
12
+
13
+ </div>
14
+
15
+
16
+
17
+ ## Table of Contents
18
+
19
+ - [Install](#install)
20
+ - [Usage](#usage)
21
+ - [Configuration](#configuration)
22
+ - [GitHub Copilot Chat (VS Code)](#github-copilot-chat-vs-code)
23
+ - [Claude Code (VS Code extension)](#claude-code-vs-code-extension)
24
+ - [Claude Code (terminal / standalone)](#claude-code-terminal--standalone)
25
+ - [Cursor](#cursor)
26
+ - [Install from source](#install-from-source)
27
+ - [Testing with MCP Inspector](#testing-with-mcp-inspector)
28
+ - [CLI Options](#cli-options)
29
+ - [Contributing](#contributing)
30
+ - [License](#license)
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ npm i -D npm-run-mcp-server
36
+ # or globally
37
+ npm i -g npm-run-mcp-server
38
+ # ad-hoc
39
+ npx npm-run-mcp-server
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ ### As an MCP Server
45
+
46
+ Add this server to your MCP host configuration. It uses stdio and dynamically exposes each script from the closest `package.json` (walking up from `process.cwd()`).
47
+
48
+ The tool names match your script names. Each tool accepts an optional `args` string that is appended after `--` when running the script. The server detects your package manager (npm, pnpm, yarn, bun).
49
+
50
+ ### As a CLI Tool
51
+
52
+ You can also use this package directly from the command line:
53
+
54
+ ```bash
55
+ # List available scripts in current directory
56
+ npx npm-run-mcp-server --list-scripts
57
+
58
+ # Run with verbose output
59
+ npx npm-run-mcp-server --verbose
60
+
61
+ # Specify a different working directory
62
+ npx npm-run-mcp-server --cwd /path/to/project --list-scripts
63
+
64
+ # Override package manager detection
65
+ npx npm-run-mcp-server --pm yarn --list-scripts
66
+ ```
67
+
68
+ ## Configuration
69
+
70
+ ### Install in GitHub Copilot Chat (VS Code)
71
+
72
+ Option A — per-workspace via `.vscode/mcp.json` (recommended for multi-project use):
73
+
74
+ ```json
75
+ {
76
+ "servers": {
77
+ "npm-scripts": {
78
+ "command": "npx",
79
+ "args": ["-y", "npm-run-mcp-server"]
80
+ }
81
+ }
82
+ }
83
+ ```
84
+
85
+ Option B — user settings (`settings.json`):
86
+
87
+ ```json
88
+ {
89
+ "mcp.servers": {
90
+ "npm-scripts": {
91
+ "command": "npx",
92
+ "args": ["-y", "npm-run-mcp-server"]
93
+ }
94
+ }
95
+ }
96
+ ```
97
+
98
+ **Note**: The server automatically detects the current project's `package.json` using `process.cwd()`, so no hardcoded paths are needed. It works seamlessly across all your projects.
99
+
100
+ Then open Copilot Chat, switch to Agent mode, and start the `npm-scripts` server from the tools panel.
101
+
102
+ ### Multi-Project Workflow
103
+
104
+ The MCP server is designed to work seamlessly across multiple projects without configuration changes:
105
+
106
+ - **VS Code/Cursor**: Use workspace settings (`.vscode/mcp.json` or `.vscode/settings.json`) - the server automatically targets the current project
107
+ - **Claude Desktop**: The server uses the working directory where Claude is launched
108
+ - **No Hardcoded Paths**: All examples use `npx npm-run-mcp-server` without `--cwd` flags
109
+ - **Automatic Detection**: The server walks up the directory tree to find the nearest `package.json`
110
+
111
+ This means you can use the same MCP configuration across all your projects, and the server will automatically target the correct project based on where your MCP client is running.
112
+
113
+ ### Install in Claude Code (VS Code extension)
114
+
115
+ Add to VS Code user/workspace settings (`settings.json`):
116
+
117
+ ```json
118
+ {
119
+ "claude.mcpServers": {
120
+ "npm-scripts": {
121
+ "command": "npx",
122
+ "args": ["-y", "npm-run-mcp-server"]
123
+ }
124
+ }
125
+ }
126
+ ```
127
+
128
+ **Note**: Workspace settings (`.vscode/settings.json`) are recommended for multi-project use, as they automatically target the current project.
129
+
130
+ Restart the extension and confirm the server/tools appear.
131
+
132
+ ### Install in Claude Code (terminal / standalone)
133
+
134
+ Add this server to Claude's global config file (paths vary by OS). Create the file if it doesn't exist.
135
+
136
+ - Windows: `%APPDATA%/Claude/claude_desktop_config.json`
137
+ - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
138
+ - Linux: `~/.config/Claude/claude_desktop_config.json`
139
+
140
+ **Recommended approach** - Using npx (works across all projects):
141
+
142
+ ```json
143
+ {
144
+ "mcpServers": {
145
+ "npm-scripts": {
146
+ "command": "npx",
147
+ "args": ["-y", "npm-run-mcp-server"]
148
+ }
149
+ }
150
+ }
151
+ ```
152
+
153
+ **Alternative** - Using a local build (requires absolute path):
154
+
155
+ ```json
156
+ {
157
+ "mcpServers": {
158
+ "npm-scripts": {
159
+ "command": "node",
160
+ "args": ["/absolute/path/to/npm-run-mcp-server/dist/index.js"]
161
+ }
162
+ }
163
+ }
164
+ ```
165
+
166
+ **Note**: The npx approach is recommended as it automatically targets the current working directory where Claude is launched.
167
+
168
+ Optional: include environment variables
169
+
170
+ ```json
171
+ {
172
+ "mcpServers": {
173
+ "npm-scripts": {
174
+ "command": "npx",
175
+ "args": ["-y", "npm-run-mcp-server"],
176
+ "env": {
177
+ "NODE_ENV": "production"
178
+ }
179
+ }
180
+ }
181
+ }
182
+ ```
183
+
184
+ Restart Claude after editing the config so it picks up the new server.
185
+
186
+ ### Install in Cursor
187
+
188
+ - Open Settings → MCP Servers → Add MCP Server
189
+ - Type: NPX Package
190
+ - Command: `npx`
191
+ - Arguments: `-y npm-run-mcp-server`
192
+ - Save and start the server from the tools list
193
+
194
+ **Note**: This configuration automatically works across all your projects. The server will target the current project's `package.json` wherever Cursor is opened.
195
+
196
+ ### Install from source (for testing in another project)
197
+
198
+ Clone, build, and link globally:
199
+
200
+ ```bash
201
+ git clone https://github.com/your-org-or-user/npm-run-mcp-server.git
202
+ cd npm-run-mcp-server
203
+ npm install
204
+ npm run build
205
+ npm link
206
+ ```
207
+
208
+ In your other project, either reference the global binary or the built file directly:
209
+
210
+ - Using the linked binary:
211
+
212
+ ```json
213
+ {
214
+ "servers": {
215
+ "npm-scripts": {
216
+ "command": "npm-run-mcp-server"
217
+ }
218
+ }
219
+ }
220
+ ```
221
+
222
+ - Using an explicit Node command (no global link needed):
223
+
224
+ ```json
225
+ {
226
+ "servers": {
227
+ "npm-scripts": {
228
+ "command": "node",
229
+ "args": ["/absolute/path/to/npm-run-mcp-server/dist/index.js"]
230
+ }
231
+ }
232
+ }
233
+ ```
234
+
235
+ Optional CLI flags you can pass in `args`:
236
+ - `--cwd /path/to/project` to choose which project to read `package.json` from (rarely needed - server auto-detects by default)
237
+ - `--pm npm|pnpm|yarn|bun` to override package manager detection
238
+
239
+ ## Testing with MCP Inspector
240
+
241
+ Test the server locally before integrating with AI agents:
242
+
243
+ ```bash
244
+ # Start MCP Inspector
245
+ npx @modelcontextprotocol/inspector
246
+
247
+ # In the Inspector UI:
248
+ # 1. Transport Type: STDIO
249
+ # 2. Command: npx
250
+ # 3. Arguments: npm-run-mcp-server --cwd /path/to/your/project --verbose
251
+ # 4. Click "Connect"
252
+ ```
253
+
254
+ You should see your package.json scripts listed as available tools. Try running one - it executes the script and returns the output.
255
+
256
+ ## CLI Options
257
+
258
+ Available command-line flags:
259
+
260
+ - `--cwd <path>` - Specify working directory (defaults to current directory)
261
+ - `--pm <manager>` - Override package manager detection (npm|pnpm|yarn|bun)
262
+ - `--verbose` - Enable detailed logging to stderr
263
+ - `--list-scripts` - List available scripts and exit
264
+
265
+ ## Contributing
266
+
267
+ We welcome contributions! Here's how you can help:
268
+
269
+ ### Reporting Issues
270
+
271
+ - Use the [issue tracker](https://github.com/fstubner/npm-run-mcp-server/issues) to report bugs
272
+ - Include your Node.js version, package manager, and operating system
273
+ - Provide a minimal reproduction case when possible
274
+
275
+ ### Submitting Changes
276
+
277
+ 1. **Fork** the repository
278
+ 2. **Create** a feature branch: `git checkout -b feature/amazing-feature`
279
+ 3. **Make** your changes and add tests if applicable
280
+ 4. **Test** your changes: `npm run build && npm run test`
281
+ 5. **Commit** your changes: `git commit -m 'Add amazing feature'`
282
+ 6. **Push** to the branch: `git push origin feature/amazing-feature`
283
+ 7. **Submit** a pull request
284
+
285
+ ### Development Setup
286
+
287
+ ```bash
288
+ git clone https://github.com/fstubner/npm-run-mcp-server.git
289
+ cd npm-run-mcp-server
290
+ npm install
291
+ npm run build
292
+ npm run test
293
+ ```
294
+
295
+ The project uses a custom build script located in `scripts/build.cjs` that handles TypeScript compilation and shebang injection for the executable.
296
+
297
+ ### Guidelines
298
+
299
+ - Follow the existing code style
300
+ - Add tests for new features
301
+ - Update documentation as needed
302
+ - Keep commits focused and descriptive
303
+
304
+ ## License
305
+
306
+ MIT
307
+
308
+
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, existsSync } from 'fs';
3
+ import { promises as fsp } from 'fs';
4
+ import { dirname, resolve } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { exec as nodeExec } from 'child_process';
7
+ import { promisify } from 'util';
8
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
+ const exec = promisify(nodeExec);
11
+ function parseCliArgs(argv) {
12
+ const args = {};
13
+ for (let i = 2; i < argv.length; i += 1) {
14
+ const token = argv[i];
15
+ if (!token)
16
+ continue;
17
+ if (token.startsWith('--')) {
18
+ const key = token.slice(2);
19
+ const next = argv[i + 1];
20
+ if (next && !next.startsWith('--')) {
21
+ args[key] = next;
22
+ i += 1;
23
+ }
24
+ else {
25
+ args[key] = true;
26
+ }
27
+ }
28
+ }
29
+ return args;
30
+ }
31
+ async function findNearestPackageJson(startDir) {
32
+ let current = resolve(startDir);
33
+ while (true) {
34
+ const candidate = resolve(current, 'package.json');
35
+ if (existsSync(candidate))
36
+ return candidate;
37
+ const parent = dirname(current);
38
+ if (parent === current)
39
+ break;
40
+ current = parent;
41
+ }
42
+ return null;
43
+ }
44
+ async function readPackageJson(pathToPackageJson) {
45
+ const raw = await fsp.readFile(pathToPackageJson, 'utf8');
46
+ return JSON.parse(raw);
47
+ }
48
+ function detectPackageManager(projectDir, pkg, override) {
49
+ if (override)
50
+ return override;
51
+ // Prefer explicit packageManager field if present
52
+ if (pkg.packageManager) {
53
+ const pm = pkg.packageManager.split('@')[0];
54
+ if (pm === 'npm' || pm === 'pnpm' || pm === 'yarn' || pm === 'bun')
55
+ return pm;
56
+ }
57
+ // Lockfile heuristic
58
+ if (existsSync(resolve(projectDir, 'pnpm-lock.yaml')))
59
+ return 'pnpm';
60
+ if (existsSync(resolve(projectDir, 'yarn.lock')))
61
+ return 'yarn';
62
+ if (existsSync(resolve(projectDir, 'bun.lockb')) || existsSync(resolve(projectDir, 'bun.lock')))
63
+ return 'bun';
64
+ return 'npm';
65
+ }
66
+ function buildRunCommand(pm, scriptName, extraArgs) {
67
+ const quoted = scriptName.replace(/"/g, '\\"');
68
+ const suffix = extraArgs && extraArgs.trim().length > 0 ? ` -- ${extraArgs}` : '';
69
+ switch (pm) {
70
+ case 'pnpm':
71
+ return `pnpm run "${quoted}"${suffix}`;
72
+ case 'yarn':
73
+ return `yarn run "${quoted}"${suffix}`;
74
+ case 'bun':
75
+ return `bun run "${quoted}"${suffix}`;
76
+ case 'npm':
77
+ default:
78
+ return `npm run "${quoted}"${suffix}`;
79
+ }
80
+ }
81
+ function trimOutput(out, limit = 12000) {
82
+ if (out.length <= limit)
83
+ return { text: out, truncated: false };
84
+ return { text: out.slice(0, limit) + `\n...[truncated ${out.length - limit} chars]`, truncated: true };
85
+ }
86
+ async function main() {
87
+ const args = parseCliArgs(process.argv);
88
+ const startCwd = args.cwd ? resolve(String(args.cwd)) : process.cwd();
89
+ const pkgJsonPath = await findNearestPackageJson(startCwd);
90
+ if (!pkgJsonPath) {
91
+ console.error(`npm-run-mcp-server: No package.json found starting from ${startCwd}`);
92
+ process.exit(1);
93
+ }
94
+ const projectDir = dirname(pkgJsonPath);
95
+ const projectPkg = await readPackageJson(pkgJsonPath);
96
+ const verbose = Boolean(args.verbose ||
97
+ process.env.MCP_VERBOSE ||
98
+ (process.env.DEBUG && process.env.DEBUG.toLowerCase().includes('mcp')));
99
+ if (verbose) {
100
+ console.error(`[mcp] server starting: cwd=${startCwd}`);
101
+ console.error(`[mcp] using package.json: ${pkgJsonPath}`);
102
+ }
103
+ const __filename = fileURLToPath(import.meta.url);
104
+ const __dirname = dirname(__filename);
105
+ const selfPkgPath = resolve(__dirname, '..', 'package.json');
106
+ let serverName = 'npm-run-mcp-server';
107
+ let serverVersion = '0.0.0';
108
+ try {
109
+ if (existsSync(selfPkgPath)) {
110
+ const selfPkg = JSON.parse(readFileSync(selfPkgPath, 'utf8'));
111
+ if (selfPkg.name)
112
+ serverName = selfPkg.name;
113
+ if (selfPkg.version)
114
+ serverVersion = selfPkg.version;
115
+ }
116
+ }
117
+ catch { }
118
+ const pm = detectPackageManager(projectDir, projectPkg, args.pm);
119
+ if (verbose) {
120
+ console.error(`[mcp] detected package manager: ${pm}`);
121
+ }
122
+ const server = new McpServer({ name: serverName, version: serverVersion });
123
+ const scripts = projectPkg.scripts ?? {};
124
+ const scriptNames = Object.keys(scripts);
125
+ if (scriptNames.length === 0) {
126
+ console.error(`npm-run-mcp-server: No scripts found in ${pkgJsonPath}`);
127
+ }
128
+ if (args['list-scripts']) {
129
+ for (const name of scriptNames) {
130
+ console.error(`${name}: ${scripts[name]}`);
131
+ }
132
+ process.exit(0);
133
+ }
134
+ // Register a tool per script
135
+ for (const scriptName of scriptNames) {
136
+ server.tool(scriptName, {
137
+ description: `Run package script '${scriptName}' via ${pm} in ${projectDir}`,
138
+ inputSchema: {
139
+ type: 'object',
140
+ properties: {
141
+ args: {
142
+ type: 'string',
143
+ description: 'Optional arguments appended after -- to the script'
144
+ }
145
+ }
146
+ },
147
+ }, async ({ args: extraArgs }) => {
148
+ const command = buildRunCommand(pm, scriptName, extraArgs);
149
+ try {
150
+ const { stdout, stderr } = await exec(command, {
151
+ cwd: projectDir,
152
+ env: process.env,
153
+ maxBuffer: 16 * 1024 * 1024, // 16MB
154
+ windowsHide: true,
155
+ });
156
+ const combined = stdout && stderr ? `${stdout}\n${stderr}` : stdout || stderr || '';
157
+ const { text } = trimOutput(combined);
158
+ return {
159
+ content: [
160
+ {
161
+ type: 'text',
162
+ text,
163
+ },
164
+ ],
165
+ };
166
+ }
167
+ catch (error) {
168
+ const stdout = error?.stdout ?? '';
169
+ const stderr = error?.stderr ?? '';
170
+ const message = error?.message ? String(error.message) : 'Script failed';
171
+ const combined = [message, stdout, stderr].filter(Boolean).join('\n');
172
+ const { text } = trimOutput(combined);
173
+ return {
174
+ content: [
175
+ {
176
+ type: 'text',
177
+ text,
178
+ },
179
+ ],
180
+ };
181
+ }
182
+ });
183
+ }
184
+ const transport = new StdioServerTransport();
185
+ if (verbose) {
186
+ console.error(`[mcp] registered ${scriptNames.length} tools; awaiting stdio client...`);
187
+ }
188
+ await server.connect(transport);
189
+ if (verbose) {
190
+ console.error(`[mcp] stdio transport connected (waiting for initialize)`);
191
+ }
192
+ }
193
+ // Run
194
+ main().catch((err) => {
195
+ console.error(err);
196
+ process.exit(1);
197
+ });
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "npm-run-mcp-server",
3
+ "version": "0.1.1",
4
+ "description": "An MCP server that exposes package.json scripts as tools for agents.",
5
+ "bin": {
6
+ "npm-run-mcp-server": "dist/index.js"
7
+ },
8
+ "type": "module",
9
+ "exports": {
10
+ ".": "./dist/index.js"
11
+ },
12
+ "files": [
13
+ "dist/index.js",
14
+ "dist/index.d.ts"
15
+ ],
16
+ "scripts": {
17
+ "build": "node scripts/build.cjs",
18
+ "start": "node ./dist/index.js",
19
+ "test": "node dist/index.js --list-scripts && echo 'MCP server test completed successfully'",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "engines": {
23
+ "node": ">=18.18.0"
24
+ },
25
+ "dependencies": {
26
+ "@modelcontextprotocol/sdk": "^1.0.0",
27
+ "zod": "^3.23.8"
28
+ },
29
+ "devDependencies": {
30
+ "typescript": "^5.4.0",
31
+ "@types/node": "^20.14.9"
32
+ },
33
+ "keywords": [
34
+ "mcp",
35
+ "model-context-protocol",
36
+ "agent",
37
+ "npm",
38
+ "scripts",
39
+ "ai",
40
+ "claude",
41
+ "cursor",
42
+ "copilot",
43
+ "automation"
44
+ ],
45
+ "author": "fstubner",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/fstubner/npm-run-mcp-server.git"
49
+ },
50
+ "bugs": {
51
+ "url": "https://github.com/fstubner/npm-run-mcp-server/issues"
52
+ },
53
+ "homepage": "https://github.com/fstubner/npm-run-mcp-server#readme",
54
+ "license": "MIT",
55
+ "publishConfig": {
56
+ "access": "public",
57
+ "registry": "https://registry.npmjs.org"
58
+ }
59
+ }