fix: parse ALL --allowed-tools flags, not just the first one (#801)

The parseAllowedTools() function previously used .match() which only
returns the first match. This caused tools specified in subsequent
--allowed-tools flags to be ignored during MCP server initialization.

Changes:
- Add /g flag to regex patterns for global matching
- Use matchAll() to find all occurrences
- Deduplicate tools while preserving order
- Make unquoted pattern not match quoted values

Fixes #800

 #vibe

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Alexander Bartash
2026-01-08 22:06:12 +02:00
committed by GitHub
parent 1b8ee3b941
commit 005436f51d
2 changed files with 55 additions and 12 deletions

View File

@@ -1,22 +1,33 @@
export function parseAllowedTools(claudeArgs: string): string[] {
// Match --allowedTools or --allowed-tools followed by the value
// Handle both quoted and unquoted values
// Use /g flag to find ALL occurrences, not just the first one
const patterns = [
/--(?:allowedTools|allowed-tools)\s+"([^"]+)"/, // Double quoted
/--(?:allowedTools|allowed-tools)\s+'([^']+)'/, // Single quoted
/--(?:allowedTools|allowed-tools)\s+([^\s]+)/, // Unquoted
/--(?:allowedTools|allowed-tools)\s+"([^"]+)"/g, // Double quoted
/--(?:allowedTools|allowed-tools)\s+'([^']+)'/g, // Single quoted
/--(?:allowedTools|allowed-tools)\s+([^'"\s][^\s]*)/g, // Unquoted (must not start with quote)
];
const tools: string[] = [];
const seen = new Set<string>();
for (const pattern of patterns) {
const match = claudeArgs.match(pattern);
if (match && match[1]) {
// Don't return if the value starts with -- (another flag)
for (const match of claudeArgs.matchAll(pattern)) {
if (match[1]) {
// Don't add if the value starts with -- (another flag)
if (match[1].startsWith("--")) {
return [];
continue;
}
for (const tool of match[1].split(",")) {
const trimmed = tool.trim();
if (trimmed && !seen.has(trimmed)) {
seen.add(trimmed);
tools.push(trimmed);
}
}
}
return match[1].split(",").map((t) => t.trim());
}
}
return [];
return tools;
}

View File

@@ -35,12 +35,44 @@ describe("parseAllowedTools", () => {
expect(parseAllowedTools("")).toEqual([]);
});
test("handles duplicate --allowedTools flags", () => {
test("handles --allowedTools followed by another --allowedTools flag", () => {
const args = "--allowedTools --allowedTools mcp__github__*";
// Should not match the first one since the value is another flag
// The second --allowedTools is consumed as a value of the first, then skipped.
// This is an edge case with malformed input - returns empty.
expect(parseAllowedTools(args)).toEqual([]);
});
test("parses multiple separate --allowed-tools flags", () => {
const args =
"--allowed-tools 'mcp__context7__*' --allowed-tools 'Read,Glob' --allowed-tools 'mcp__github_inline_comment__*'";
expect(parseAllowedTools(args)).toEqual([
"mcp__context7__*",
"Read",
"Glob",
"mcp__github_inline_comment__*",
]);
});
test("parses multiple --allowed-tools flags on separate lines", () => {
const args = `--model 'claude-haiku'
--allowed-tools 'mcp__context7__*'
--allowed-tools 'Read,Glob,Grep'
--allowed-tools 'mcp__github_inline_comment__create_inline_comment'`;
expect(parseAllowedTools(args)).toEqual([
"mcp__context7__*",
"Read",
"Glob",
"Grep",
"mcp__github_inline_comment__create_inline_comment",
]);
});
test("deduplicates tools from multiple flags", () => {
const args =
"--allowed-tools 'Read,Glob' --allowed-tools 'Glob,Grep' --allowed-tools 'Read'";
expect(parseAllowedTools(args)).toEqual(["Read", "Glob", "Grep"]);
});
test("handles typo --alloedTools", () => {
const args = "--alloedTools mcp__github__*";
expect(parseAllowedTools(args)).toEqual([]);