fix: align --raw-strings and --no-coerce semantics (#59) (thanks @nobrainer-tech)
This commit is contained in:
parent
079b217f63
commit
cb179c903d
@ -8,6 +8,7 @@
|
||||
- Added optional `oauthScope`/`oauth_scope` config override as an escape hatch for providers that require explicit scopes.
|
||||
- `createCallResult().json()` now collects all parseable JSON entries from MCP content arrays (single item stays backward-compatible), and raw inspect depth now stays readable without unbounded traversal. (PR #91, thanks @Blankdlh)
|
||||
- OAuth wait/redirect now share one deferred to eliminate authorization race windows and preserve stable close-path errors, including wait-before-redirect and repeated-redirect flows. (PR #70, thanks @monotykamary)
|
||||
- Added `--raw-strings` (numeric coercion off) and `--no-coerce` (all coercion off) for `mcporter call` argument parsing so IDs/codes can stay literal strings. (PR #59, thanks @nobrainer-tech)
|
||||
|
||||
### Tooling / Dependencies
|
||||
- Updated dependencies to latest releases (including MCP SDK, Rolldown RC, Zod, Biome, Oxlint, Vitest, Bun types).
|
||||
|
||||
@ -20,8 +20,11 @@ export interface CallArgsParseResult {
|
||||
rawStrings?: boolean;
|
||||
}
|
||||
|
||||
type CoercionMode = 'default' | 'raw-strings' | 'none';
|
||||
|
||||
export function parseCallArguments(args: string[]): CallArgsParseResult {
|
||||
const result: CallArgsParseResult = { args: {}, tailLog: false, output: 'auto' };
|
||||
let coercionMode: CoercionMode = 'default';
|
||||
const ephemeral = extractEphemeralServerFlags(args);
|
||||
result.ephemeral = ephemeral;
|
||||
result.output = consumeOutputFormat(args, {
|
||||
@ -69,7 +72,14 @@ export function parseCallArguments(args: string[]): CallArgsParseResult {
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (token === '--raw-strings' || token === '--no-coerce') {
|
||||
if (token === '--raw-strings') {
|
||||
coercionMode = 'raw-strings';
|
||||
result.rawStrings = true;
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (token === '--no-coerce') {
|
||||
coercionMode = 'none';
|
||||
result.rawStrings = true;
|
||||
index += 1;
|
||||
continue;
|
||||
@ -174,12 +184,12 @@ export function parseCallArguments(args: string[]): CallArgsParseResult {
|
||||
}
|
||||
const parsed = parseKeyValueToken(token, positional[index + 1]);
|
||||
if (!parsed) {
|
||||
trailingPositional.push(coerceValue(token, result.rawStrings));
|
||||
trailingPositional.push(coerceValue(token, coercionMode));
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
index += parsed.consumed;
|
||||
const value = coerceValue(parsed.rawValue, result.rawStrings);
|
||||
const value = coerceValue(parsed.rawValue, coercionMode);
|
||||
if (parsed.key === 'tool' && !result.tool) {
|
||||
if (typeof value !== 'string') {
|
||||
throw new Error("Argument 'tool' must be a string value.");
|
||||
@ -279,19 +289,21 @@ function extractHttpCallExpression(raw: string): ReturnType<typeof parseCallExpr
|
||||
};
|
||||
}
|
||||
|
||||
function coerceValue(value: string, rawStrings = false): unknown {
|
||||
function coerceValue(value: string, coercionMode: CoercionMode = 'default'): unknown {
|
||||
const trimmed = value.trim();
|
||||
if (trimmed === '') {
|
||||
return '';
|
||||
}
|
||||
if (coercionMode === 'none') {
|
||||
return trimmed;
|
||||
}
|
||||
if (trimmed === 'true' || trimmed === 'false') {
|
||||
return trimmed === 'true';
|
||||
}
|
||||
if (trimmed === 'null' || trimmed === 'none') {
|
||||
return null;
|
||||
}
|
||||
// Skip numeric coercion when --raw-strings (or --no-coerce) flag is used
|
||||
if (!rawStrings && !Number.isNaN(Number(trimmed)) && trimmed === `${Number(trimmed)}`) {
|
||||
if (coercionMode === 'default' && !Number.isNaN(Number(trimmed)) && trimmed === `${Number(trimmed)}`) {
|
||||
return Number(trimmed);
|
||||
}
|
||||
if ((trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
|
||||
|
||||
@ -130,6 +130,8 @@ export function printCallHelp(): void {
|
||||
'Runtime flags:',
|
||||
' --timeout <ms> Override the call timeout.',
|
||||
' --output text|markdown|json|raw Control formatting.',
|
||||
' --raw-strings Keep numeric-looking argument values as strings.',
|
||||
' --no-coerce Keep all key/value and positional arguments as raw strings.',
|
||||
' --tail-log Stream returned log handles.',
|
||||
'',
|
||||
'Ad-hoc servers:',
|
||||
|
||||
@ -69,9 +69,28 @@ describe('parseCallArguments', () => {
|
||||
expect(typeof parsed.args.pin).toBe('string');
|
||||
});
|
||||
|
||||
it('preserves numeric strings when --no-coerce alias is used', () => {
|
||||
const parsed = parseCallArguments(['--no-coerce', 'server.tool', 'id=007']);
|
||||
it('still coerces booleans, nulls, and JSON with --raw-strings', () => {
|
||||
const parsed = parseCallArguments(['--raw-strings', 'server.tool', 'enabled=true', 'value=null', 'meta={"a":1}']);
|
||||
expect(parsed.args.enabled).toBe(true);
|
||||
expect(parsed.args.value).toBeNull();
|
||||
expect(parsed.args.meta).toEqual({ a: 1 });
|
||||
});
|
||||
|
||||
it('keeps every value as a string when --no-coerce alias is used', () => {
|
||||
const parsed = parseCallArguments([
|
||||
'--no-coerce',
|
||||
'server.tool',
|
||||
'id=007',
|
||||
'enabled=true',
|
||||
'value=null',
|
||||
'meta={"a":1}',
|
||||
'123',
|
||||
]);
|
||||
expect(parsed.args.id).toBe('007');
|
||||
expect(parsed.args.enabled).toBe('true');
|
||||
expect(parsed.args.value).toBe('null');
|
||||
expect(parsed.args.meta).toBe('{"a":1}');
|
||||
expect(typeof parsed.args.id).toBe('string');
|
||||
expect(parsed.positionalArgs).toEqual(['123']);
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user