mcp-filter 0.2.0 → 0.4.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/CHANGELOG.md +47 -0
- package/README.md +141 -5
- package/dist/index.js +3 -24
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.4.0] - 2025-10-08
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Fixed connection failures caused by double-spawning upstream MCP server subprocess
|
|
12
|
+
- Fixed "command not found" errors with `npx` by properly passing environment variables
|
|
13
|
+
- Fixed stderr forwarding from upstream servers
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Delegated subprocess management entirely to `StdioClientTransport` (breaking change in internal architecture)
|
|
17
|
+
- Improved error forwarding by using `stderr: "inherit"` instead of `"pipe"`
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- Comprehensive integration tests for subprocess management and real-world MCP server compatibility
|
|
21
|
+
- Architecture validation tests to prevent regression of subprocess spawning anti-patterns
|
|
22
|
+
- Debugging guide for common MCP communication issues in CLAUDE.md
|
|
23
|
+
- ADR-003 documenting subprocess delegation pattern
|
|
24
|
+
- Claude Code configuration with custom slash commands
|
|
25
|
+
|
|
26
|
+
## [0.3.0] - 2025-01-XX
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- Rsync-style pattern evaluation (first match wins)
|
|
30
|
+
- Support for both `--include` and `--exclude` patterns
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- Pattern matching now evaluates in order with first-match-wins semantics
|
|
34
|
+
|
|
35
|
+
## [0.2.0] - 2025-01-XX
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
- Comprehensive documentation updates
|
|
39
|
+
- Testing infrastructure with Vitest
|
|
40
|
+
|
|
41
|
+
## [0.1.0] - 2025-01-XX
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
- Initial release
|
|
45
|
+
- Basic MCP proxy server with pattern-based filtering
|
|
46
|
+
- Support for filtering tools, resources, and prompts
|
|
47
|
+
- Glob pattern matching using minimatch
|
package/README.md
CHANGED
|
@@ -24,8 +24,8 @@ npx mcp-filter --exclude "playwright*" -- npx @playwright/mcp
|
|
|
24
24
|
# Include mode: only allow specific tools
|
|
25
25
|
npx mcp-filter --include "browser_navigate" --include "browser_screenshot" -- npx @playwright/mcp
|
|
26
26
|
|
|
27
|
-
# Combination: include with exceptions
|
|
28
|
-
npx mcp-filter --
|
|
27
|
+
# Combination: include with exceptions (order matters!)
|
|
28
|
+
npx mcp-filter --exclude "browser_close" --include "browser_*" -- npx @playwright/mcp
|
|
29
29
|
|
|
30
30
|
# Multiple patterns
|
|
31
31
|
npx mcp-filter --exclude "playwright*" --exclude "unsafe_*" -- npx @playwright/mcp
|
|
@@ -68,18 +68,21 @@ npx mcp-filter --include "browser_navigate" --include "browser_screenshot" -- np
|
|
|
68
68
|
|
|
69
69
|
**Rsync-style filtering**: patterns evaluated in order, first match wins.
|
|
70
70
|
|
|
71
|
+
⚠️ **Common mistake**: Putting `--include` before `--exclude` means the exclude never applies!
|
|
72
|
+
|
|
71
73
|
```bash
|
|
72
|
-
# Example 1: Include first, then exclude
|
|
74
|
+
# Example 1: Include first, then exclude (DOES NOT WORK AS EXPECTED!)
|
|
73
75
|
npx mcp-filter --include "browser_*" --exclude "browser_close" -- npx @playwright/mcp
|
|
74
76
|
# Result: All browser_* tools are INCLUDED (browser_* matched first)
|
|
75
77
|
# browser_close is also included because it matches browser_* first
|
|
78
|
+
# The --exclude "browser_close" is never evaluated!
|
|
76
79
|
|
|
77
|
-
# Example 2: Exclude first, then include (
|
|
80
|
+
# Example 2: Exclude first, then include (CORRECT way to exclude exceptions!)
|
|
78
81
|
npx mcp-filter --exclude "browser_close" --include "browser_*" -- npx @playwright/mcp
|
|
79
82
|
# Result: browser_close is EXCLUDED (matched exclude first)
|
|
80
83
|
# Other browser_* tools are included
|
|
81
84
|
|
|
82
|
-
# Example 3:
|
|
85
|
+
# Example 3: Multiple exclusions, then broad include (recommended pattern)
|
|
83
86
|
npx mcp-filter --exclude "browser_close" --exclude "browser_evaluate" --include "browser_*" -- npx @playwright/mcp
|
|
84
87
|
# Result: browser_close and browser_evaluate excluded (matched first)
|
|
85
88
|
# All other browser_* tools included
|
|
@@ -127,6 +130,139 @@ npx mcp-filter \
|
|
|
127
130
|
# 4. other_tool doesn't match any pattern, but --include exists → excluded (whitelist mode)
|
|
128
131
|
```
|
|
129
132
|
|
|
133
|
+
## Using with Claude Code
|
|
134
|
+
|
|
135
|
+
### Adding Filtered MCP Servers
|
|
136
|
+
|
|
137
|
+
Add filtered MCP servers to Claude Code using the `claude mcp add` command:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# Basic syntax
|
|
141
|
+
claude mcp add <name> -- npx mcp-filter [filter-options] -- <upstream-command>
|
|
142
|
+
|
|
143
|
+
# Example: Safe Playwright with read-only browser access
|
|
144
|
+
claude mcp add playwright-safe -- \
|
|
145
|
+
npx mcp-filter \
|
|
146
|
+
--include "browser_navigate" \
|
|
147
|
+
--include "browser_screenshot" \
|
|
148
|
+
--include "browser_snapshot" \
|
|
149
|
+
-- npx @playwright/mcp@latest
|
|
150
|
+
|
|
151
|
+
# Example: Block dangerous operations
|
|
152
|
+
claude mcp add playwright-filtered -- \
|
|
153
|
+
npx mcp-filter \
|
|
154
|
+
--exclude "browser_close" \
|
|
155
|
+
--exclude "browser_evaluate" \
|
|
156
|
+
-- npx @playwright/mcp@latest
|
|
157
|
+
|
|
158
|
+
# Example: Include category with exceptions (rsync-style)
|
|
159
|
+
claude mcp add playwright -- \
|
|
160
|
+
npx mcp-filter \
|
|
161
|
+
--exclude "browser_close" \
|
|
162
|
+
--exclude "browser_evaluate" \
|
|
163
|
+
--include "browser_*" \
|
|
164
|
+
-- npx @playwright/mcp@latest
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Understanding the command structure:**
|
|
168
|
+
|
|
169
|
+
- First `--` separates Claude's options from the mcp-filter command
|
|
170
|
+
- Second `--` separates mcp-filter options from the upstream MCP server command
|
|
171
|
+
|
|
172
|
+
### Scope Options
|
|
173
|
+
|
|
174
|
+
Choose where to store the configuration:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# Local scope (default): Only you, only this project
|
|
178
|
+
claude mcp add playwright-safe -- npx mcp-filter --include "browser_*" -- npx @playwright/mcp@latest
|
|
179
|
+
|
|
180
|
+
# User scope: Available across all your projects
|
|
181
|
+
claude mcp add --scope user playwright-safe -- \
|
|
182
|
+
npx mcp-filter --include "browser_*" -- npx @playwright/mcp@latest
|
|
183
|
+
|
|
184
|
+
# Project scope: Share with team via .mcp.json (checked into git)
|
|
185
|
+
claude mcp add --scope project playwright-safe -- \
|
|
186
|
+
npx mcp-filter --include "browser_*" -- npx @playwright/mcp@latest
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Security Note**: Claude Code prompts for approval before using project-scoped servers from `.mcp.json` files. To reset approval choices, use `claude mcp reset-project-choices`.
|
|
190
|
+
|
|
191
|
+
### Managing Filtered Servers
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
# List all configured servers
|
|
195
|
+
claude mcp list
|
|
196
|
+
|
|
197
|
+
# Get details for a specific server
|
|
198
|
+
claude mcp get playwright-safe
|
|
199
|
+
|
|
200
|
+
# Remove a server
|
|
201
|
+
claude mcp remove playwright-safe
|
|
202
|
+
|
|
203
|
+
# Check server status in Claude Code
|
|
204
|
+
/mcp
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Practical Examples
|
|
208
|
+
|
|
209
|
+
**Monitoring agent (read-only)**
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
claude mcp add browser-monitor -- \
|
|
213
|
+
npx mcp-filter \
|
|
214
|
+
--include "browser_navigate" \
|
|
215
|
+
--include "browser_snapshot" \
|
|
216
|
+
--include "browser_console_messages" \
|
|
217
|
+
--include "browser_network_requests" \
|
|
218
|
+
--include "browser_take_screenshot" \
|
|
219
|
+
-- npx @playwright/mcp@latest
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Testing agent (no destructive actions)**
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
claude mcp add browser-test -- \
|
|
226
|
+
npx mcp-filter \
|
|
227
|
+
--exclude "browser_close" \
|
|
228
|
+
--exclude "browser_tabs" \
|
|
229
|
+
--exclude "browser_evaluate" \
|
|
230
|
+
--include "browser_*" \
|
|
231
|
+
-- npx @playwright/mcp@latest
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Note: Exclude patterns must come BEFORE the include pattern to match first.
|
|
235
|
+
|
|
236
|
+
**Production debugging (safe operations only)**
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
# Add as user-scoped for use across projects
|
|
240
|
+
claude mcp add --scope user prod-debugger -- \
|
|
241
|
+
npx mcp-filter \
|
|
242
|
+
--exclude "browser_click" \
|
|
243
|
+
--exclude "browser_type" \
|
|
244
|
+
--exclude "browser_evaluate" \
|
|
245
|
+
--exclude "browser_fill_form" \
|
|
246
|
+
--include "browser_*" \
|
|
247
|
+
-- npx @playwright/mcp@latest
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Updating Server Configuration
|
|
251
|
+
|
|
252
|
+
To update filter rules, remove and re-add the server:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
# Remove existing configuration
|
|
256
|
+
claude mcp remove playwright-safe
|
|
257
|
+
|
|
258
|
+
# Add with new filter rules
|
|
259
|
+
claude mcp add playwright-safe -- \
|
|
260
|
+
npx mcp-filter \
|
|
261
|
+
--include "browser_navigate" \
|
|
262
|
+
--include "browser_screenshot" \
|
|
263
|
+
-- npx @playwright/mcp@latest
|
|
264
|
+
```
|
|
265
|
+
|
|
130
266
|
## Using with Cursor IDE
|
|
131
267
|
|
|
132
268
|
Add to your `.cursor/mcp.json` or `~/.cursor/mcp.json`:
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { spawn } from "child_process";
|
|
3
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
3
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
5
4
|
import { parseArgs } from "./cli.js";
|
|
@@ -28,8 +27,6 @@ async function main() {
|
|
|
28
27
|
if (hasInclude && hasExclude) {
|
|
29
28
|
console.error("Note: Using rsync-style filtering - patterns evaluated in order, first match wins.");
|
|
30
29
|
}
|
|
31
|
-
// Spawn upstream server
|
|
32
|
-
const upstreamProcess = spawnUpstream(config.upstreamCommand);
|
|
33
30
|
// Create filter
|
|
34
31
|
const filter = new Filter(config.patterns);
|
|
35
32
|
// Create proxy server
|
|
@@ -38,10 +35,12 @@ async function main() {
|
|
|
38
35
|
version: "0.2.0",
|
|
39
36
|
}, filter);
|
|
40
37
|
// Connect client to upstream server via subprocess stdio
|
|
38
|
+
// StdioClientTransport spawns and manages the subprocess
|
|
41
39
|
const clientTransport = new StdioClientTransport({
|
|
42
40
|
command: config.upstreamCommand[0],
|
|
43
41
|
args: config.upstreamCommand.slice(1),
|
|
44
|
-
|
|
42
|
+
env: process.env, // Pass current environment
|
|
43
|
+
stderr: "inherit", // Forward upstream stderr to our stderr
|
|
45
44
|
});
|
|
46
45
|
await proxy.getClient().connect(clientTransport);
|
|
47
46
|
console.error("Connected to upstream server");
|
|
@@ -52,31 +51,11 @@ async function main() {
|
|
|
52
51
|
// Handle cleanup
|
|
53
52
|
const cleanup = () => {
|
|
54
53
|
console.error("Shutting down...");
|
|
55
|
-
upstreamProcess.kill();
|
|
56
54
|
process.exit(0);
|
|
57
55
|
};
|
|
58
56
|
process.on("SIGINT", cleanup);
|
|
59
57
|
process.on("SIGTERM", cleanup);
|
|
60
58
|
}
|
|
61
|
-
function spawnUpstream(command) {
|
|
62
|
-
const [cmd, ...args] = command;
|
|
63
|
-
const proc = spawn(cmd, args, {
|
|
64
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
65
|
-
});
|
|
66
|
-
// Forward stderr to our stderr
|
|
67
|
-
proc.stderr.on("data", (data) => {
|
|
68
|
-
process.stderr.write(data);
|
|
69
|
-
});
|
|
70
|
-
proc.on("error", (error) => {
|
|
71
|
-
console.error(`Failed to start upstream server: ${error.message}`);
|
|
72
|
-
process.exit(1);
|
|
73
|
-
});
|
|
74
|
-
proc.on("exit", (code) => {
|
|
75
|
-
console.error(`Upstream server exited with code ${code}`);
|
|
76
|
-
process.exit(code || 0);
|
|
77
|
-
});
|
|
78
|
-
return proc;
|
|
79
|
-
}
|
|
80
59
|
main().catch((error) => {
|
|
81
60
|
console.error("Fatal error:", error);
|
|
82
61
|
process.exit(1);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-filter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "MCP server proxy to filter tools, resources, and prompts from upstream MCP servers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
11
|
"README.md",
|
|
12
|
+
"CHANGELOG.md",
|
|
12
13
|
"LICENSE"
|
|
13
14
|
],
|
|
14
15
|
"keywords": [
|