fix: reject unknown call flags (#35)

This commit is contained in:
Peter Steinberger 2026-03-28 21:33:15 +00:00
parent 97ac209049
commit d7cfc29bff
No known key found for this signature in database
7 changed files with 42 additions and 0 deletions

View File

@ -328,6 +328,7 @@
- Expanded the ad-hoc/OAuth story across `README.md`, `docs/adhoc.md`, `docs/local.md`, `docs/known-issues.md`, and `docs/supabase-auth-issue.md`, detailing when servers auto-promote to OAuth, how retries behave, and how to persist generated definitions safely.
- Updated the README, CLI reference, and generator docs to cover the new `--all-parameters` flag, list formatter, metadata embedding, the `mcporter emit-ts` workflow, and refreshed branding so the CLI and docs consistently introduce the project as **MCPorter**.
- Tightened `docs/RELEASE.md` with a zero-warning policy so `pnpm check`, `pnpm test`, `npm pack --dry-run`, and friends must run clean before publishing.
- `mcporter call` now rejects unknown long flags like `--source` instead of silently treating them as positional tool arguments; use `key=value`, `--args`, or `--` for literal `--value` positionals (PR #35, thanks @beverm2391).
## [0.2.0] - 2025-11-06

View File

@ -71,5 +71,6 @@ Key details:
- By default, arguments keep the same validation pipeline as the function-call syntax—enums, numbers, and booleans are coerced automatically, and missing required fields raise errors.
- `--raw-strings` disables numeric coercion for flag-style and positional values so IDs/codes stay literal strings (`code=12345` stays `"12345"`).
- `--no-coerce` disables all coercion for flag-style and positional values (`true`, `null`, and JSON-like values remain strings).
- Unknown long flags like `--source` now fail fast instead of silently becoming positional tool arguments. Use `source=value`, `--args '{"source":"value"}'`, or insert `--` before literal positional values that start with `--`.
- `--save-images <dir>` keeps stdout formatting untouched while writing image content blocks to disk when a tool response includes `type: "image"` entries.
- `tool=value`/`tool:value` and `server=value` still act as aliases for `--tool` / `--server` when you need to override the selector.

View File

@ -26,6 +26,7 @@ A quick reference for the primary `mcporter` subcommands. Each command inherits
pseudo-TS syntax and `--arg` flags.
- Useful flags:
- `--server`, `--tool` alternate way to target a tool.
- `--` stop flag parsing so remaining tokens stay literal positional values.
- `--timeout <ms>` override call timeout (defaults to `CALL_TIMEOUT_MS`).
- `--output text|markdown|json|raw` choose how to render the `CallResult`.
- `--save-images <dir>` persist image content blocks to files under the specified directory.

View File

@ -31,6 +31,7 @@ mcporter call context7.resolve-library-id libraryName: value
- Use `--flag value` when you prefer long-form CLI syntax.
- Mixed forms are fine: `mcporter call linear.create_issue --team ENG title=value due: tomorrow`.
- `--args '{"title":"Bug"}'` still ingests JSON payloads directly.
- Unknown long flags now error instead of silently becoming tool arguments; use `title=value`, `--args`, or `--` before literal positional values beginning with `--`.
## 3. Function-Call Syntax

View File

@ -7,6 +7,7 @@ import {
shouldPromoteSelectorToCommand,
} from './call-argument-values.js';
import { extractEphemeralServerFlags } from './ephemeral-flags.js';
import { CliUsageError } from './errors.js';
import { consumeOutputFormat } from './output-format.js';
import type { OutputFormat } from './output-utils.js';
import { consumeTimeoutFlag } from './timeouts.js';
@ -60,6 +61,7 @@ export function parseCallArguments(args: string[]): CallArgsParseResult {
defaultFormat: 'auto',
});
const positional: string[] = [];
const literalPositional: string[] = [];
let index = 0;
while (index < args.length) {
const token = args[index];
@ -67,11 +69,25 @@ export function parseCallArguments(args: string[]): CallArgsParseResult {
index += 1;
continue;
}
if (token === '--') {
literalPositional.push(...args.slice(index + 1).filter(Boolean));
break;
}
const flagHandler = FLAG_HANDLERS[token];
if (flagHandler) {
index = flagHandler({ args, index, result, state: flagState });
continue;
}
if (token.startsWith('--')) {
throw new CliUsageError(
[
`Unknown flag '${token}' passed to call command.`,
`If you intended to pass a tool argument, use '${token.slice(2)}=<value>' or --args '{"${token.slice(2)}": ...}'.`,
"If you intended to pass a literal positional value, insert '--' before it.",
"Run 'mcporter call --help' to see available flags.",
].join('\n')
);
}
positional.push(token);
index += 1;
}
@ -168,6 +184,12 @@ export function parseCallArguments(args: string[]): CallArgsParseResult {
if (trailingPositional.length > 0) {
result.positionalArgs = [...(result.positionalArgs ?? []), ...trailingPositional];
}
if (literalPositional.length > 0) {
result.positionalArgs = [
...(result.positionalArgs ?? []),
...literalPositional.map((token) => coerceValue(token, flagState.coercionMode)),
];
}
return result;
}

View File

@ -174,6 +174,7 @@ export function printCallHelp(): void {
' function-call syntax \'server.tool(arg: "value", other: 1)\'.',
' --args <json> Provide a JSON object payload.',
' positional values Accepted when schema order is known.',
' -- Treat remaining tokens as literal positional values.',
'',
'Runtime flags:',
' --timeout <ms> Override the call timeout.',

View File

@ -40,6 +40,21 @@ describe('parseCallArguments', () => {
expect(parsed.args.orderBy).toBe('updatedAt');
});
it.each([
['--source', '--source'],
['--source=import', '--source=import'],
] as const)('throws on unknown long flags like %s', (flag, expectedToken) => {
expect(() => parseCallArguments(['server.tool', flag])).toThrow(
new RegExp(`Unknown flag '${expectedToken.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}'`)
);
});
it('treats values after -- as literal positional arguments', () => {
const parsed = parseCallArguments(['server.tool', '--', '--source', 'import', '--raw=true']);
expect(parsed.selector).toBe('server.tool');
expect(parsed.positionalArgs).toEqual(['--source', 'import', '--raw=true']);
});
it('throws when flags conflict with call expression content', () => {
expect(() => parseCallArguments(['--server', 'linear', 'cursor.list_documents(limit:1)'])).toThrow(
/Conflicting server names/