Parse tool args from CLI as JsonValue instead of just strings

This commit is contained in:
olaservo 2025-07-26 09:15:27 -07:00
commit 99b962d445
3 changed files with 79 additions and 15 deletions

View file

@ -1,6 +1,16 @@
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { McpResponse } from "./types.js";
// JSON value type matching the client utils
type JsonValue =
| string
| number
| boolean
| null
| undefined
| JsonValue[]
| { [key: string]: JsonValue };
// List available prompts
export async function listPrompts(client: Client): Promise<McpResponse> {
try {
@ -17,12 +27,26 @@ export async function listPrompts(client: Client): Promise<McpResponse> {
export async function getPrompt(
client: Client,
name: string,
args?: Record<string, string>,
args?: Record<string, JsonValue>,
): Promise<McpResponse> {
try {
// Convert all arguments to strings for prompt arguments
const stringArgs: Record<string, string> = {};
if (args) {
for (const [key, value] of Object.entries(args)) {
if (typeof value === 'string') {
stringArgs[key] = value;
} else if (value === null || value === undefined) {
stringArgs[key] = String(value);
} else {
stringArgs[key] = JSON.stringify(value);
}
}
}
const response = await client.getPrompt({
name,
arguments: args || {},
arguments: stringArgs,
});
return response;

View file

@ -2,6 +2,16 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { Tool } from "@modelcontextprotocol/sdk/types.js";
import { McpResponse } from "./types.js";
// JSON value type matching the client utils
type JsonValue =
| string
| number
| boolean
| null
| undefined
| JsonValue[]
| { [key: string]: JsonValue };
type JsonSchemaType = {
type: "string" | "number" | "integer" | "boolean" | "array" | "object";
description?: string;
@ -20,7 +30,7 @@ export async function listTools(client: Client): Promise<McpResponse> {
}
}
function convertParameterValue(value: string, schema: JsonSchemaType): unknown {
function convertParameterValue(value: string, schema: JsonSchemaType): JsonValue {
if (!value) {
return value;
}
@ -35,7 +45,7 @@ function convertParameterValue(value: string, schema: JsonSchemaType): unknown {
if (schema.type === "object" || schema.type === "array") {
try {
return JSON.parse(value);
return JSON.parse(value) as JsonValue;
} catch (error) {
return value;
}
@ -47,8 +57,8 @@ function convertParameterValue(value: string, schema: JsonSchemaType): unknown {
function convertParameters(
tool: Tool,
params: Record<string, string>,
): Record<string, unknown> {
const result: Record<string, unknown> = {};
): Record<string, JsonValue> {
const result: Record<string, JsonValue> = {};
const properties = tool.inputSchema.properties || {};
for (const [key, value] of Object.entries(params)) {
@ -68,18 +78,29 @@ function convertParameters(
export async function callTool(
client: Client,
name: string,
args: Record<string, string>,
args: Record<string, JsonValue>,
): Promise<McpResponse> {
try {
const toolsResponse = await listTools(client);
const tools = toolsResponse.tools as Tool[];
const tool = tools.find((t) => t.name === name);
let convertedArgs: Record<string, unknown> = args;
let convertedArgs: Record<string, JsonValue> = args;
if (tool) {
// Convert parameters based on the tool's schema
convertedArgs = convertParameters(tool, args);
// Convert parameters based on the tool's schema, but only for string values
// since we now accept pre-parsed values from the CLI
const stringArgs: Record<string, string> = {};
for (const [key, value] of Object.entries(args)) {
if (typeof value === 'string') {
stringArgs[key] = value;
}
}
if (Object.keys(stringArgs).length > 0) {
const convertedStringArgs = convertParameters(tool, stringArgs);
convertedArgs = { ...args, ...convertedStringArgs };
}
}
const response = await client.callTool({

View file

@ -20,15 +20,25 @@ import {
import { handleError } from "./error-handler.js";
import { createTransport, TransportOptions } from "./transport.js";
// JSON value type for CLI arguments
type JsonValue =
| string
| number
| boolean
| null
| undefined
| JsonValue[]
| { [key: string]: JsonValue };
type Args = {
target: string[];
method?: string;
promptName?: string;
promptArgs?: Record<string, string>;
promptArgs?: Record<string, JsonValue>;
uri?: string;
logLevel?: LogLevel;
toolName?: string;
toolArg?: Record<string, string>;
toolArg?: Record<string, JsonValue>;
transport?: "sse" | "stdio" | "http";
};
@ -162,8 +172,8 @@ async function callMethod(args: Args): Promise<void> {
function parseKeyValuePair(
value: string,
previous: Record<string, string> = {},
): Record<string, string> {
previous: Record<string, JsonValue> = {},
): Record<string, JsonValue> {
const parts = value.split("=");
const key = parts[0];
const val = parts.slice(1).join("=");
@ -174,7 +184,16 @@ function parseKeyValuePair(
);
}
return { ...previous, [key as string]: val };
// Try to parse as JSON first
let parsedValue: JsonValue;
try {
parsedValue = JSON.parse(val) as JsonValue;
} catch {
// If JSON parsing fails, keep as string
parsedValue = val;
}
return { ...previous, [key as string]: parsedValue };
}
function parseArgs(): Args {