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 { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { McpResponse } from "./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 };
// List available prompts // List available prompts
export async function listPrompts(client: Client): Promise<McpResponse> { export async function listPrompts(client: Client): Promise<McpResponse> {
try { try {
@ -17,12 +27,26 @@ export async function listPrompts(client: Client): Promise<McpResponse> {
export async function getPrompt( export async function getPrompt(
client: Client, client: Client,
name: string, name: string,
args?: Record<string, string>, args?: Record<string, JsonValue>,
): Promise<McpResponse> { ): Promise<McpResponse> {
try { 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({ const response = await client.getPrompt({
name, name,
arguments: args || {}, arguments: stringArgs,
}); });
return response; return response;

View file

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

View file

@ -20,15 +20,25 @@ import {
import { handleError } from "./error-handler.js"; import { handleError } from "./error-handler.js";
import { createTransport, TransportOptions } from "./transport.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 = { type Args = {
target: string[]; target: string[];
method?: string; method?: string;
promptName?: string; promptName?: string;
promptArgs?: Record<string, string>; promptArgs?: Record<string, JsonValue>;
uri?: string; uri?: string;
logLevel?: LogLevel; logLevel?: LogLevel;
toolName?: string; toolName?: string;
toolArg?: Record<string, string>; toolArg?: Record<string, JsonValue>;
transport?: "sse" | "stdio" | "http"; transport?: "sse" | "stdio" | "http";
}; };
@ -162,8 +172,8 @@ async function callMethod(args: Args): Promise<void> {
function parseKeyValuePair( function parseKeyValuePair(
value: string, value: string,
previous: Record<string, string> = {}, previous: Record<string, JsonValue> = {},
): Record<string, string> { ): Record<string, JsonValue> {
const parts = value.split("="); const parts = value.split("=");
const key = parts[0]; const key = parts[0];
const val = parts.slice(1).join("="); 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 { function parseArgs(): Args {