diff --git a/cli/scripts/cli-metadata-tests.js b/cli/scripts/cli-metadata-tests.js new file mode 100755 index 00000000..881e1894 --- /dev/null +++ b/cli/scripts/cli-metadata-tests.js @@ -0,0 +1,630 @@ +#!/usr/bin/env node + +// Colors for output +const colors = { + GREEN: "\x1b[32m", + YELLOW: "\x1b[33m", + RED: "\x1b[31m", + BLUE: "\x1b[34m", + ORANGE: "\x1b[33m", + NC: "\x1b[0m", // No Color +}; + +import fs from "fs"; +import path from "path"; +import { spawn } from "child_process"; +import os from "os"; +import { fileURLToPath } from "url"; + +// Get directory paths with ESM compatibility +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Track test results +let PASSED_TESTS = 0; +let FAILED_TESTS = 0; +let SKIPPED_TESTS = 0; +let TOTAL_TESTS = 0; + +console.log( + `${colors.YELLOW}=== MCP Inspector CLI Metadata Tests ===${colors.NC}`, +); +console.log( + `${colors.BLUE}This script tests the MCP Inspector CLI's metadata functionality:${colors.NC}`, +); +console.log( + `${colors.BLUE}- General metadata with --metadata option${colors.NC}`, +); +console.log( + `${colors.BLUE}- Tool-specific metadata with --tool-metadata option${colors.NC}`, +); +console.log( + `${colors.BLUE}- Metadata parsing with various data types${colors.NC}`, +); +console.log( + `${colors.BLUE}- Metadata merging (tool-specific overrides general)${colors.NC}`, +); +console.log( + `${colors.BLUE}- Metadata evaluation in different MCP methods${colors.NC}`, +); +console.log(`\n`); + +// Get directory paths +const SCRIPTS_DIR = __dirname; +const PROJECT_ROOT = path.join(SCRIPTS_DIR, "../../"); +const BUILD_DIR = path.resolve(SCRIPTS_DIR, "../build"); + +// Define the test server command using npx +const TEST_CMD = "npx"; +const TEST_ARGS = ["@modelcontextprotocol/server-everything"]; + +// Create output directory for test results +const OUTPUT_DIR = path.join(SCRIPTS_DIR, "metadata-test-output"); +if (!fs.existsSync(OUTPUT_DIR)) { + fs.mkdirSync(OUTPUT_DIR, { recursive: true }); +} + +// Create a temporary directory for test files +const TEMP_DIR = path.join(os.tmpdir(), "mcp-inspector-metadata-tests"); +fs.mkdirSync(TEMP_DIR, { recursive: true }); + +// Track servers for cleanup +let runningServers = []; + +process.on("exit", () => { + try { + fs.rmSync(TEMP_DIR, { recursive: true, force: true }); + } catch (err) { + console.error( + `${colors.RED}Failed to remove temp directory: ${err.message}${colors.NC}`, + ); + } + + runningServers.forEach((server) => { + try { + process.kill(-server.pid); + } catch (e) {} + }); +}); + +process.on("SIGINT", () => { + runningServers.forEach((server) => { + try { + process.kill(-server.pid); + } catch (e) {} + }); + process.exit(1); +}); + +// Function to run a basic test +async function runBasicTest(testName, ...args) { + const outputFile = path.join( + OUTPUT_DIR, + `${testName.replace(/\//g, "_")}.log`, + ); + + console.log(`\n${colors.YELLOW}Testing: ${testName}${colors.NC}`); + TOTAL_TESTS++; + + // Run the command and capture output + console.log( + `${colors.BLUE}Command: node ${BUILD_DIR}/cli.js ${args.join(" ")}${colors.NC}`, + ); + + try { + // Create a write stream for the output file + const outputStream = fs.createWriteStream(outputFile); + + // Spawn the process + return new Promise((resolve) => { + const child = spawn("node", [path.join(BUILD_DIR, "cli.js"), ...args], { + stdio: ["ignore", "pipe", "pipe"], + }); + + const timeout = setTimeout(() => { + console.log(`${colors.YELLOW}Test timed out: ${testName}${colors.NC}`); + child.kill(); + }, 15000); + + // Pipe stdout and stderr to the output file + child.stdout.pipe(outputStream); + child.stderr.pipe(outputStream); + + // Also capture output for display + let output = ""; + child.stdout.on("data", (data) => { + output += data.toString(); + }); + child.stderr.on("data", (data) => { + output += data.toString(); + }); + + child.on("close", (code) => { + clearTimeout(timeout); + outputStream.end(); + + // Check if we got valid JSON output (indicating success) even if process didn't exit cleanly + const hasValidJsonOutput = + output.includes('"tools"') || + output.includes('"resources"') || + output.includes('"prompts"') || + output.includes('"content"') || + output.includes('"messages"') || + output.includes('"contents"'); + + if (code === 0 || hasValidJsonOutput) { + console.log(`${colors.GREEN}✓ Test passed: ${testName}${colors.NC}`); + console.log(`${colors.BLUE}First few lines of output:${colors.NC}`); + const firstFewLines = output + .split("\n") + .slice(0, 5) + .map((line) => ` ${line}`) + .join("\n"); + console.log(firstFewLines); + PASSED_TESTS++; + resolve(true); + } else { + console.log(`${colors.RED}✗ Test failed: ${testName}${colors.NC}`); + console.log(`${colors.RED}Error output:${colors.NC}`); + console.log( + output + .split("\n") + .map((line) => ` ${line}`) + .join("\n"), + ); + FAILED_TESTS++; + + // Stop after any error is encountered + console.log( + `${colors.YELLOW}Stopping tests due to error. Please validate and fix before continuing.${colors.NC}`, + ); + process.exit(1); + } + }); + }); + } catch (error) { + console.error( + `${colors.RED}Error running test: ${error.message}${colors.NC}`, + ); + FAILED_TESTS++; + process.exit(1); + } +} + +// Function to run an error test (expected to fail) +async function runErrorTest(testName, ...args) { + const outputFile = path.join( + OUTPUT_DIR, + `${testName.replace(/\//g, "_")}.log`, + ); + + console.log(`\n${colors.YELLOW}Testing error case: ${testName}${colors.NC}`); + TOTAL_TESTS++; + + // Run the command and capture output + console.log( + `${colors.BLUE}Command: node ${BUILD_DIR}/cli.js ${args.join(" ")}${colors.NC}`, + ); + + try { + // Create a write stream for the output file + const outputStream = fs.createWriteStream(outputFile); + + // Spawn the process + return new Promise((resolve) => { + const child = spawn("node", [path.join(BUILD_DIR, "cli.js"), ...args], { + stdio: ["ignore", "pipe", "pipe"], + }); + + const timeout = setTimeout(() => { + console.log( + `${colors.YELLOW}Error test timed out: ${testName}${colors.NC}`, + ); + child.kill(); + }, 15000); + + // Pipe stdout and stderr to the output file + child.stdout.pipe(outputStream); + child.stderr.pipe(outputStream); + + // Also capture output for display + let output = ""; + child.stdout.on("data", (data) => { + output += data.toString(); + }); + child.stderr.on("data", (data) => { + output += data.toString(); + }); + + child.on("close", (code) => { + clearTimeout(timeout); + outputStream.end(); + + // For error tests, we expect a non-zero exit code + if (code !== 0) { + console.log( + `${colors.GREEN}✓ Error test passed: ${testName}${colors.NC}`, + ); + console.log(`${colors.BLUE}Error output (expected):${colors.NC}`); + const firstFewLines = output + .split("\n") + .slice(0, 5) + .map((line) => ` ${line}`) + .join("\n"); + console.log(firstFewLines); + PASSED_TESTS++; + resolve(true); + } else { + console.log( + `${colors.RED}✗ Error test failed: ${testName} (expected error but got success)${colors.NC}`, + ); + console.log(`${colors.RED}Output:${colors.NC}`); + console.log( + output + .split("\n") + .map((line) => ` ${line}`) + .join("\n"), + ); + FAILED_TESTS++; + + // Stop after any error is encountered + console.log( + `${colors.YELLOW}Stopping tests due to error. Please validate and fix before continuing.${colors.NC}`, + ); + process.exit(1); + } + }); + }); + } catch (error) { + console.error( + `${colors.RED}Error running test: ${error.message}${colors.NC}`, + ); + FAILED_TESTS++; + process.exit(1); + } +} + +// Run all tests +async function runTests() { + console.log( + `\n${colors.YELLOW}=== Running General Metadata Tests ===${colors.NC}`, + ); + + // Test 1: General metadata with tools/list + await runBasicTest( + "metadata_tools_list", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/list", + "--metadata", + "client=test-client", + "version=1.0.0", + "environment=test", + ); + + // Test 2: General metadata with resources/list + await runBasicTest( + "metadata_resources_list", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "resources/list", + "--metadata", + "client=test-client", + "version=1.0.0", + "environment=test", + ); + + // Test 3: General metadata with prompts/list + await runBasicTest( + "metadata_prompts_list", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "prompts/list", + "--metadata", + "client=test-client", + "version=1.0.0", + "environment=test", + ); + + // Test 4: General metadata with resources/read + await runBasicTest( + "metadata_resources_read", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "resources/read", + "--uri", + "test://static/resource/1", + "--metadata", + "client=test-client", + "version=1.0.0", + "environment=test", + ); + + // Test 5: General metadata with prompts/get + await runBasicTest( + "metadata_prompts_get", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "prompts/get", + "--prompt-name", + "simple_prompt", + "--metadata", + "client=test-client", + "version=1.0.0", + "environment=test", + ); + + console.log( + `\n${colors.YELLOW}=== Running Tool-Specific Metadata Tests ===${colors.NC}`, + ); + + // Test 6: Tool-specific metadata with tools/call + await runBasicTest( + "metadata_tools_call_tool_meta", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + "message=hello world", + "--tool-metadata", + "tool-client=test-tool-client", + "tool-version=2.0.0", + "tool-environment=test-tool", + ); + + // Test 7: Tool-specific metadata with complex tool + await runBasicTest( + "metadata_tools_call_complex_tool_meta", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/call", + "--tool-name", + "add", + "--tool-arg", + "a=10", + "b=20", + "--tool-metadata", + "operation=addition", + "precision=integer", + "validation=strict", + ); + + console.log( + `\n${colors.YELLOW}=== Running Metadata Merging Tests ===${colors.NC}`, + ); + + // Test 8: General metadata + tool-specific metadata (tool-specific should override) + await runBasicTest( + "metadata_merging_general_and_tool", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + "message=hello world", + "--metadata", + "client=general-client", + "version=1.0.0", + "environment=general", + "--tool-metadata", + "client=tool-specific-client", + "tool-version=2.0.0", + "tool-environment=tool-specific", + ); + + console.log( + `\n${colors.YELLOW}=== Running Metadata Parsing Tests ===${colors.NC}`, + ); + + // Test 9: Metadata with string values + await runBasicTest( + "metadata_parsing_strings", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/list", + "--metadata", + "string_value=hello world", + 'quoted_string="quoted value"', + 'empty_string=""', + ); + + // Test 10: Metadata with numeric values (should be converted to strings) + await runBasicTest( + "metadata_parsing_numbers", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/list", + "--metadata", + "integer_value=42", + "decimal_value=3.14159", + "negative_value=-10", + ); + + // Test 11: Metadata with boolean values (should be converted to strings) + await runBasicTest( + "metadata_parsing_booleans", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/list", + "--metadata", + "true_value=true", + "false_value=false", + ); + + // Test 12: Metadata with JSON values (should be converted to strings) + await runBasicTest( + "metadata_parsing_json", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/list", + "--metadata", + 'json_object="{\\"key\\":\\"value\\"}"', + 'json_array="[1,2,3]"', + 'json_string="\\"quoted\\""', + ); + + // Test 13: Metadata with special characters + await runBasicTest( + "metadata_parsing_special_chars", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/list", + "--metadata", + "unicode=🚀🎉✨", + "special_chars=!@#$%^&*()", + "spaces=hello world with spaces", + ); + + console.log( + `\n${colors.YELLOW}=== Running Metadata Edge Cases ===${colors.NC}`, + ); + + // Test 14: Single metadata entry + await runBasicTest( + "metadata_single_entry", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/list", + "--metadata", + "single_key=single_value", + ); + + // Test 15: Many metadata entries + await runBasicTest( + "metadata_many_entries", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/list", + "--metadata", + "key1=value1", + "key2=value2", + "key3=value3", + "key4=value4", + "key5=value5", + ); + + console.log( + `\n${colors.YELLOW}=== Running Metadata Error Cases ===${colors.NC}`, + ); + + // Test 16: Invalid metadata format (missing equals) + await runErrorTest( + "metadata_error_invalid_format", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/list", + "--metadata", + "invalid_format_no_equals", + ); + + // Test 17: Invalid tool-meta format (missing equals) + await runErrorTest( + "metadata_error_invalid_tool_meta_format", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + "message=test", + "--tool-metadata", + "invalid_format_no_equals", + ); + + console.log( + `\n${colors.YELLOW}=== Running Metadata Integration Tests ===${colors.NC}`, + ); + + // Test 18: Metadata with all MCP methods + await runBasicTest( + "metadata_integration_all_methods", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/list", + "--metadata", + "integration_test=true", + "test_phase=all_methods", + ); + + // Test 19: Complex metadata scenario + await runBasicTest( + "metadata_complex_scenario", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + "message=complex test", + "--metadata", + "session_id=12345", + "user_id=67890", + "timestamp=2024-01-01T00:00:00Z", + "request_id=req-abc-123", + "--tool-metadata", + "tool_session=session-xyz-789", + "execution_context=test", + "priority=high", + ); + + // Print test summary + console.log(`\n${colors.YELLOW}=== Test Summary ===${colors.NC}`); + console.log(`${colors.GREEN}Passed: ${PASSED_TESTS}${colors.NC}`); + console.log(`${colors.RED}Failed: ${FAILED_TESTS}${colors.NC}`); + console.log(`${colors.ORANGE}Skipped: ${SKIPPED_TESTS}${colors.NC}`); + console.log(`Total: ${TOTAL_TESTS}`); + console.log( + `${colors.BLUE}Detailed logs saved to: ${OUTPUT_DIR}${colors.NC}`, + ); + + console.log(`\n${colors.GREEN}All metadata tests completed!${colors.NC}`); +} + +// Run all tests +runTests().catch((error) => { + console.error( + `${colors.RED}Tests failed with error: ${error.message}${colors.NC}`, + ); + process.exit(1); +}); diff --git a/cli/scripts/metadata-test-output/metadata_complex_scenario.log b/cli/scripts/metadata-test-output/metadata_complex_scenario.log new file mode 100644 index 00000000..bcfc10b1 --- /dev/null +++ b/cli/scripts/metadata-test-output/metadata_complex_scenario.log @@ -0,0 +1,8 @@ +{ + "content": [ + { + "type": "text", + "text": "Echo: complex test" + } + ] +} \ No newline at end of file diff --git a/cli/scripts/metadata-test-output/metadata_empty.log b/cli/scripts/metadata-test-output/metadata_empty.log new file mode 100644 index 00000000..476e5324 --- /dev/null +++ b/cli/scripts/metadata-test-output/metadata_empty.log @@ -0,0 +1,3 @@ +Invalid parameter format: . Use key=value format. + +Failed with exit code: 1 diff --git a/cli/scripts/metadata-test-output/metadata_error_invalid_format.log b/cli/scripts/metadata-test-output/metadata_error_invalid_format.log new file mode 100644 index 00000000..5a18c175 --- /dev/null +++ b/cli/scripts/metadata-test-output/metadata_error_invalid_format.log @@ -0,0 +1,3 @@ +Invalid parameter format: invalid_format_no_equals. Use key=value format. + +Failed with exit code: 1 diff --git a/cli/scripts/metadata-test-output/metadata_error_invalid_tool_meta_format.log b/cli/scripts/metadata-test-output/metadata_error_invalid_tool_meta_format.log new file mode 100644 index 00000000..5a18c175 --- /dev/null +++ b/cli/scripts/metadata-test-output/metadata_error_invalid_tool_meta_format.log @@ -0,0 +1,3 @@ +Invalid parameter format: invalid_format_no_equals. Use key=value format. + +Failed with exit code: 1 diff --git a/cli/scripts/metadata-test-output/metadata_integration_all_methods.log b/cli/scripts/metadata-test-output/metadata_integration_all_methods.log new file mode 100644 index 00000000..114d6da4 --- /dev/null +++ b/cli/scripts/metadata-test-output/metadata_integration_all_methods.log @@ -0,0 +1,218 @@ +{ + "tools": [ + { + "name": "echo", + "description": "Echoes back the input", + "inputSchema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Message to echo" + } + }, + "required": [ + "message" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "add", + "description": "Adds two numbers", + "inputSchema": { + "type": "object", + "properties": { + "a": { + "type": "number", + "description": "First number" + }, + "b": { + "type": "number", + "description": "Second number" + } + }, + "required": [ + "a", + "b" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "longRunningOperation", + "description": "Demonstrates a long running operation with progress updates", + "inputSchema": { + "type": "object", + "properties": { + "duration": { + "type": "number", + "default": 10, + "description": "Duration of the operation in seconds" + }, + "steps": { + "type": "number", + "default": 5, + "description": "Number of steps in the operation" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "printEnv", + "description": "Prints all environment variables, helpful for debugging MCP server configuration", + "inputSchema": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "sampleLLM", + "description": "Samples from an LLM using MCP's sampling feature", + "inputSchema": { + "type": "object", + "properties": { + "prompt": { + "type": "string", + "description": "The prompt to send to the LLM" + }, + "maxTokens": { + "type": "number", + "default": 100, + "description": "Maximum number of tokens to generate" + } + }, + "required": [ + "prompt" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getTinyImage", + "description": "Returns the MCP_TINY_IMAGE", + "inputSchema": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "annotatedMessage", + "description": "Demonstrates how annotations can be used to provide metadata about content", + "inputSchema": { + "type": "object", + "properties": { + "messageType": { + "type": "string", + "enum": [ + "error", + "success", + "debug" + ], + "description": "Type of message to demonstrate different annotation patterns" + }, + "includeImage": { + "type": "boolean", + "default": false, + "description": "Whether to include an example image" + } + }, + "required": [ + "messageType" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getResourceReference", + "description": "Returns a resource reference that can be used by MCP clients", + "inputSchema": { + "type": "object", + "properties": { + "resourceId": { + "type": "number", + "minimum": 1, + "maximum": 100, + "description": "ID of the resource to reference (1-100)" + } + }, + "required": [ + "resourceId" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getResourceLinks", + "description": "Returns multiple resource links that reference different types of resources", + "inputSchema": { + "type": "object", + "properties": { + "count": { + "type": "number", + "minimum": 1, + "maximum": 10, + "default": 3, + "description": "Number of resource links to return (1-10)" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "structuredContent", + "description": "Returns structured content along with an output schema for client data validation", + "inputSchema": { + "type": "object", + "properties": { + "location": { + "type": "string", + "minLength": 1, + "description": "City name or zip code" + } + }, + "required": [ + "location" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "outputSchema": { + "type": "object", + "properties": { + "temperature": { + "type": "number", + "description": "Temperature in celsius" + }, + "conditions": { + "type": "string", + "description": "Weather conditions description" + }, + "humidity": { + "type": "number", + "description": "Humidity percentage" + } + }, + "required": [ + "temperature", + "conditions", + "humidity" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + } + ] +} \ No newline at end of file diff --git a/cli/scripts/metadata-test-output/metadata_many_entries.log b/cli/scripts/metadata-test-output/metadata_many_entries.log new file mode 100644 index 00000000..114d6da4 --- /dev/null +++ b/cli/scripts/metadata-test-output/metadata_many_entries.log @@ -0,0 +1,218 @@ +{ + "tools": [ + { + "name": "echo", + "description": "Echoes back the input", + "inputSchema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Message to echo" + } + }, + "required": [ + "message" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "add", + "description": "Adds two numbers", + "inputSchema": { + "type": "object", + "properties": { + "a": { + "type": "number", + "description": "First number" + }, + "b": { + "type": "number", + "description": "Second number" + } + }, + "required": [ + "a", + "b" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "longRunningOperation", + "description": "Demonstrates a long running operation with progress updates", + "inputSchema": { + "type": "object", + "properties": { + "duration": { + "type": "number", + "default": 10, + "description": "Duration of the operation in seconds" + }, + "steps": { + "type": "number", + "default": 5, + "description": "Number of steps in the operation" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "printEnv", + "description": "Prints all environment variables, helpful for debugging MCP server configuration", + "inputSchema": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "sampleLLM", + "description": "Samples from an LLM using MCP's sampling feature", + "inputSchema": { + "type": "object", + "properties": { + "prompt": { + "type": "string", + "description": "The prompt to send to the LLM" + }, + "maxTokens": { + "type": "number", + "default": 100, + "description": "Maximum number of tokens to generate" + } + }, + "required": [ + "prompt" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getTinyImage", + "description": "Returns the MCP_TINY_IMAGE", + "inputSchema": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "annotatedMessage", + "description": "Demonstrates how annotations can be used to provide metadata about content", + "inputSchema": { + "type": "object", + "properties": { + "messageType": { + "type": "string", + "enum": [ + "error", + "success", + "debug" + ], + "description": "Type of message to demonstrate different annotation patterns" + }, + "includeImage": { + "type": "boolean", + "default": false, + "description": "Whether to include an example image" + } + }, + "required": [ + "messageType" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getResourceReference", + "description": "Returns a resource reference that can be used by MCP clients", + "inputSchema": { + "type": "object", + "properties": { + "resourceId": { + "type": "number", + "minimum": 1, + "maximum": 100, + "description": "ID of the resource to reference (1-100)" + } + }, + "required": [ + "resourceId" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getResourceLinks", + "description": "Returns multiple resource links that reference different types of resources", + "inputSchema": { + "type": "object", + "properties": { + "count": { + "type": "number", + "minimum": 1, + "maximum": 10, + "default": 3, + "description": "Number of resource links to return (1-10)" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "structuredContent", + "description": "Returns structured content along with an output schema for client data validation", + "inputSchema": { + "type": "object", + "properties": { + "location": { + "type": "string", + "minLength": 1, + "description": "City name or zip code" + } + }, + "required": [ + "location" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "outputSchema": { + "type": "object", + "properties": { + "temperature": { + "type": "number", + "description": "Temperature in celsius" + }, + "conditions": { + "type": "string", + "description": "Weather conditions description" + }, + "humidity": { + "type": "number", + "description": "Humidity percentage" + } + }, + "required": [ + "temperature", + "conditions", + "humidity" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + } + ] +} \ No newline at end of file diff --git a/cli/scripts/metadata-test-output/metadata_merging_general_and_tool.log b/cli/scripts/metadata-test-output/metadata_merging_general_and_tool.log new file mode 100644 index 00000000..145c3ac0 --- /dev/null +++ b/cli/scripts/metadata-test-output/metadata_merging_general_and_tool.log @@ -0,0 +1,8 @@ +{ + "content": [ + { + "type": "text", + "text": "Echo: hello world" + } + ] +} \ No newline at end of file diff --git a/cli/scripts/metadata-test-output/metadata_parsing_booleans.log b/cli/scripts/metadata-test-output/metadata_parsing_booleans.log new file mode 100644 index 00000000..114d6da4 --- /dev/null +++ b/cli/scripts/metadata-test-output/metadata_parsing_booleans.log @@ -0,0 +1,218 @@ +{ + "tools": [ + { + "name": "echo", + "description": "Echoes back the input", + "inputSchema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Message to echo" + } + }, + "required": [ + "message" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "add", + "description": "Adds two numbers", + "inputSchema": { + "type": "object", + "properties": { + "a": { + "type": "number", + "description": "First number" + }, + "b": { + "type": "number", + "description": "Second number" + } + }, + "required": [ + "a", + "b" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "longRunningOperation", + "description": "Demonstrates a long running operation with progress updates", + "inputSchema": { + "type": "object", + "properties": { + "duration": { + "type": "number", + "default": 10, + "description": "Duration of the operation in seconds" + }, + "steps": { + "type": "number", + "default": 5, + "description": "Number of steps in the operation" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "printEnv", + "description": "Prints all environment variables, helpful for debugging MCP server configuration", + "inputSchema": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "sampleLLM", + "description": "Samples from an LLM using MCP's sampling feature", + "inputSchema": { + "type": "object", + "properties": { + "prompt": { + "type": "string", + "description": "The prompt to send to the LLM" + }, + "maxTokens": { + "type": "number", + "default": 100, + "description": "Maximum number of tokens to generate" + } + }, + "required": [ + "prompt" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getTinyImage", + "description": "Returns the MCP_TINY_IMAGE", + "inputSchema": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "annotatedMessage", + "description": "Demonstrates how annotations can be used to provide metadata about content", + "inputSchema": { + "type": "object", + "properties": { + "messageType": { + "type": "string", + "enum": [ + "error", + "success", + "debug" + ], + "description": "Type of message to demonstrate different annotation patterns" + }, + "includeImage": { + "type": "boolean", + "default": false, + "description": "Whether to include an example image" + } + }, + "required": [ + "messageType" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getResourceReference", + "description": "Returns a resource reference that can be used by MCP clients", + "inputSchema": { + "type": "object", + "properties": { + "resourceId": { + "type": "number", + "minimum": 1, + "maximum": 100, + "description": "ID of the resource to reference (1-100)" + } + }, + "required": [ + "resourceId" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getResourceLinks", + "description": "Returns multiple resource links that reference different types of resources", + "inputSchema": { + "type": "object", + "properties": { + "count": { + "type": "number", + "minimum": 1, + "maximum": 10, + "default": 3, + "description": "Number of resource links to return (1-10)" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "structuredContent", + "description": "Returns structured content along with an output schema for client data validation", + "inputSchema": { + "type": "object", + "properties": { + "location": { + "type": "string", + "minLength": 1, + "description": "City name or zip code" + } + }, + "required": [ + "location" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "outputSchema": { + "type": "object", + "properties": { + "temperature": { + "type": "number", + "description": "Temperature in celsius" + }, + "conditions": { + "type": "string", + "description": "Weather conditions description" + }, + "humidity": { + "type": "number", + "description": "Humidity percentage" + } + }, + "required": [ + "temperature", + "conditions", + "humidity" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + } + ] +} \ No newline at end of file diff --git a/cli/scripts/metadata-test-output/metadata_parsing_json.log b/cli/scripts/metadata-test-output/metadata_parsing_json.log new file mode 100644 index 00000000..114d6da4 --- /dev/null +++ b/cli/scripts/metadata-test-output/metadata_parsing_json.log @@ -0,0 +1,218 @@ +{ + "tools": [ + { + "name": "echo", + "description": "Echoes back the input", + "inputSchema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Message to echo" + } + }, + "required": [ + "message" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "add", + "description": "Adds two numbers", + "inputSchema": { + "type": "object", + "properties": { + "a": { + "type": "number", + "description": "First number" + }, + "b": { + "type": "number", + "description": "Second number" + } + }, + "required": [ + "a", + "b" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "longRunningOperation", + "description": "Demonstrates a long running operation with progress updates", + "inputSchema": { + "type": "object", + "properties": { + "duration": { + "type": "number", + "default": 10, + "description": "Duration of the operation in seconds" + }, + "steps": { + "type": "number", + "default": 5, + "description": "Number of steps in the operation" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "printEnv", + "description": "Prints all environment variables, helpful for debugging MCP server configuration", + "inputSchema": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "sampleLLM", + "description": "Samples from an LLM using MCP's sampling feature", + "inputSchema": { + "type": "object", + "properties": { + "prompt": { + "type": "string", + "description": "The prompt to send to the LLM" + }, + "maxTokens": { + "type": "number", + "default": 100, + "description": "Maximum number of tokens to generate" + } + }, + "required": [ + "prompt" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getTinyImage", + "description": "Returns the MCP_TINY_IMAGE", + "inputSchema": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "annotatedMessage", + "description": "Demonstrates how annotations can be used to provide metadata about content", + "inputSchema": { + "type": "object", + "properties": { + "messageType": { + "type": "string", + "enum": [ + "error", + "success", + "debug" + ], + "description": "Type of message to demonstrate different annotation patterns" + }, + "includeImage": { + "type": "boolean", + "default": false, + "description": "Whether to include an example image" + } + }, + "required": [ + "messageType" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getResourceReference", + "description": "Returns a resource reference that can be used by MCP clients", + "inputSchema": { + "type": "object", + "properties": { + "resourceId": { + "type": "number", + "minimum": 1, + "maximum": 100, + "description": "ID of the resource to reference (1-100)" + } + }, + "required": [ + "resourceId" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getResourceLinks", + "description": "Returns multiple resource links that reference different types of resources", + "inputSchema": { + "type": "object", + "properties": { + "count": { + "type": "number", + "minimum": 1, + "maximum": 10, + "default": 3, + "description": "Number of resource links to return (1-10)" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "structuredContent", + "description": "Returns structured content along with an output schema for client data validation", + "inputSchema": { + "type": "object", + "properties": { + "location": { + "type": "string", + "minLength": 1, + "description": "City name or zip code" + } + }, + "required": [ + "location" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "outputSchema": { + "type": "object", + "properties": { + "temperature": { + "type": "number", + "description": "Temperature in celsius" + }, + "conditions": { + "type": "string", + "description": "Weather conditions description" + }, + "humidity": { + "type": "number", + "description": "Humidity percentage" + } + }, + "required": [ + "temperature", + "conditions", + "humidity" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + } + ] +} \ No newline at end of file diff --git a/cli/scripts/metadata-test-output/metadata_parsing_numbers.log b/cli/scripts/metadata-test-output/metadata_parsing_numbers.log new file mode 100644 index 00000000..114d6da4 --- /dev/null +++ b/cli/scripts/metadata-test-output/metadata_parsing_numbers.log @@ -0,0 +1,218 @@ +{ + "tools": [ + { + "name": "echo", + "description": "Echoes back the input", + "inputSchema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Message to echo" + } + }, + "required": [ + "message" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "add", + "description": "Adds two numbers", + "inputSchema": { + "type": "object", + "properties": { + "a": { + "type": "number", + "description": "First number" + }, + "b": { + "type": "number", + "description": "Second number" + } + }, + "required": [ + "a", + "b" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "longRunningOperation", + "description": "Demonstrates a long running operation with progress updates", + "inputSchema": { + "type": "object", + "properties": { + "duration": { + "type": "number", + "default": 10, + "description": "Duration of the operation in seconds" + }, + "steps": { + "type": "number", + "default": 5, + "description": "Number of steps in the operation" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "printEnv", + "description": "Prints all environment variables, helpful for debugging MCP server configuration", + "inputSchema": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "sampleLLM", + "description": "Samples from an LLM using MCP's sampling feature", + "inputSchema": { + "type": "object", + "properties": { + "prompt": { + "type": "string", + "description": "The prompt to send to the LLM" + }, + "maxTokens": { + "type": "number", + "default": 100, + "description": "Maximum number of tokens to generate" + } + }, + "required": [ + "prompt" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getTinyImage", + "description": "Returns the MCP_TINY_IMAGE", + "inputSchema": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "annotatedMessage", + "description": "Demonstrates how annotations can be used to provide metadata about content", + "inputSchema": { + "type": "object", + "properties": { + "messageType": { + "type": "string", + "enum": [ + "error", + "success", + "debug" + ], + "description": "Type of message to demonstrate different annotation patterns" + }, + "includeImage": { + "type": "boolean", + "default": false, + "description": "Whether to include an example image" + } + }, + "required": [ + "messageType" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getResourceReference", + "description": "Returns a resource reference that can be used by MCP clients", + "inputSchema": { + "type": "object", + "properties": { + "resourceId": { + "type": "number", + "minimum": 1, + "maximum": 100, + "description": "ID of the resource to reference (1-100)" + } + }, + "required": [ + "resourceId" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getResourceLinks", + "description": "Returns multiple resource links that reference different types of resources", + "inputSchema": { + "type": "object", + "properties": { + "count": { + "type": "number", + "minimum": 1, + "maximum": 10, + "default": 3, + "description": "Number of resource links to return (1-10)" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "structuredContent", + "description": "Returns structured content along with an output schema for client data validation", + "inputSchema": { + "type": "object", + "properties": { + "location": { + "type": "string", + "minLength": 1, + "description": "City name or zip code" + } + }, + "required": [ + "location" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "outputSchema": { + "type": "object", + "properties": { + "temperature": { + "type": "number", + "description": "Temperature in celsius" + }, + "conditions": { + "type": "string", + "description": "Weather conditions description" + }, + "humidity": { + "type": "number", + "description": "Humidity percentage" + } + }, + "required": [ + "temperature", + "conditions", + "humidity" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + } + ] +} \ No newline at end of file diff --git a/cli/scripts/metadata-test-output/metadata_parsing_special_chars.log b/cli/scripts/metadata-test-output/metadata_parsing_special_chars.log new file mode 100644 index 00000000..114d6da4 --- /dev/null +++ b/cli/scripts/metadata-test-output/metadata_parsing_special_chars.log @@ -0,0 +1,218 @@ +{ + "tools": [ + { + "name": "echo", + "description": "Echoes back the input", + "inputSchema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Message to echo" + } + }, + "required": [ + "message" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "add", + "description": "Adds two numbers", + "inputSchema": { + "type": "object", + "properties": { + "a": { + "type": "number", + "description": "First number" + }, + "b": { + "type": "number", + "description": "Second number" + } + }, + "required": [ + "a", + "b" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "longRunningOperation", + "description": "Demonstrates a long running operation with progress updates", + "inputSchema": { + "type": "object", + "properties": { + "duration": { + "type": "number", + "default": 10, + "description": "Duration of the operation in seconds" + }, + "steps": { + "type": "number", + "default": 5, + "description": "Number of steps in the operation" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "printEnv", + "description": "Prints all environment variables, helpful for debugging MCP server configuration", + "inputSchema": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "sampleLLM", + "description": "Samples from an LLM using MCP's sampling feature", + "inputSchema": { + "type": "object", + "properties": { + "prompt": { + "type": "string", + "description": "The prompt to send to the LLM" + }, + "maxTokens": { + "type": "number", + "default": 100, + "description": "Maximum number of tokens to generate" + } + }, + "required": [ + "prompt" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getTinyImage", + "description": "Returns the MCP_TINY_IMAGE", + "inputSchema": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "annotatedMessage", + "description": "Demonstrates how annotations can be used to provide metadata about content", + "inputSchema": { + "type": "object", + "properties": { + "messageType": { + "type": "string", + "enum": [ + "error", + "success", + "debug" + ], + "description": "Type of message to demonstrate different annotation patterns" + }, + "includeImage": { + "type": "boolean", + "default": false, + "description": "Whether to include an example image" + } + }, + "required": [ + "messageType" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getResourceReference", + "description": "Returns a resource reference that can be used by MCP clients", + "inputSchema": { + "type": "object", + "properties": { + "resourceId": { + "type": "number", + "minimum": 1, + "maximum": 100, + "description": "ID of the resource to reference (1-100)" + } + }, + "required": [ + "resourceId" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getResourceLinks", + "description": "Returns multiple resource links that reference different types of resources", + "inputSchema": { + "type": "object", + "properties": { + "count": { + "type": "number", + "minimum": 1, + "maximum": 10, + "default": 3, + "description": "Number of resource links to return (1-10)" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "structuredContent", + "description": "Returns structured content along with an output schema for client data validation", + "inputSchema": { + "type": "object", + "properties": { + "location": { + "type": "string", + "minLength": 1, + "description": "City name or zip code" + } + }, + "required": [ + "location" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "outputSchema": { + "type": "object", + "properties": { + "temperature": { + "type": "number", + "description": "Temperature in celsius" + }, + "conditions": { + "type": "string", + "description": "Weather conditions description" + }, + "humidity": { + "type": "number", + "description": "Humidity percentage" + } + }, + "required": [ + "temperature", + "conditions", + "humidity" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + } + ] +} \ No newline at end of file diff --git a/cli/scripts/metadata-test-output/metadata_parsing_strings.log b/cli/scripts/metadata-test-output/metadata_parsing_strings.log new file mode 100644 index 00000000..114d6da4 --- /dev/null +++ b/cli/scripts/metadata-test-output/metadata_parsing_strings.log @@ -0,0 +1,218 @@ +{ + "tools": [ + { + "name": "echo", + "description": "Echoes back the input", + "inputSchema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Message to echo" + } + }, + "required": [ + "message" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "add", + "description": "Adds two numbers", + "inputSchema": { + "type": "object", + "properties": { + "a": { + "type": "number", + "description": "First number" + }, + "b": { + "type": "number", + "description": "Second number" + } + }, + "required": [ + "a", + "b" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "longRunningOperation", + "description": "Demonstrates a long running operation with progress updates", + "inputSchema": { + "type": "object", + "properties": { + "duration": { + "type": "number", + "default": 10, + "description": "Duration of the operation in seconds" + }, + "steps": { + "type": "number", + "default": 5, + "description": "Number of steps in the operation" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "printEnv", + "description": "Prints all environment variables, helpful for debugging MCP server configuration", + "inputSchema": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "sampleLLM", + "description": "Samples from an LLM using MCP's sampling feature", + "inputSchema": { + "type": "object", + "properties": { + "prompt": { + "type": "string", + "description": "The prompt to send to the LLM" + }, + "maxTokens": { + "type": "number", + "default": 100, + "description": "Maximum number of tokens to generate" + } + }, + "required": [ + "prompt" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getTinyImage", + "description": "Returns the MCP_TINY_IMAGE", + "inputSchema": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "annotatedMessage", + "description": "Demonstrates how annotations can be used to provide metadata about content", + "inputSchema": { + "type": "object", + "properties": { + "messageType": { + "type": "string", + "enum": [ + "error", + "success", + "debug" + ], + "description": "Type of message to demonstrate different annotation patterns" + }, + "includeImage": { + "type": "boolean", + "default": false, + "description": "Whether to include an example image" + } + }, + "required": [ + "messageType" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getResourceReference", + "description": "Returns a resource reference that can be used by MCP clients", + "inputSchema": { + "type": "object", + "properties": { + "resourceId": { + "type": "number", + "minimum": 1, + "maximum": 100, + "description": "ID of the resource to reference (1-100)" + } + }, + "required": [ + "resourceId" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getResourceLinks", + "description": "Returns multiple resource links that reference different types of resources", + "inputSchema": { + "type": "object", + "properties": { + "count": { + "type": "number", + "minimum": 1, + "maximum": 10, + "default": 3, + "description": "Number of resource links to return (1-10)" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "structuredContent", + "description": "Returns structured content along with an output schema for client data validation", + "inputSchema": { + "type": "object", + "properties": { + "location": { + "type": "string", + "minLength": 1, + "description": "City name or zip code" + } + }, + "required": [ + "location" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "outputSchema": { + "type": "object", + "properties": { + "temperature": { + "type": "number", + "description": "Temperature in celsius" + }, + "conditions": { + "type": "string", + "description": "Weather conditions description" + }, + "humidity": { + "type": "number", + "description": "Humidity percentage" + } + }, + "required": [ + "temperature", + "conditions", + "humidity" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + } + ] +} \ No newline at end of file diff --git a/cli/scripts/metadata-test-output/metadata_prompts_get.log b/cli/scripts/metadata-test-output/metadata_prompts_get.log new file mode 100644 index 00000000..bfeb0428 --- /dev/null +++ b/cli/scripts/metadata-test-output/metadata_prompts_get.log @@ -0,0 +1,11 @@ +{ + "messages": [ + { + "role": "user", + "content": { + "type": "text", + "text": "This is a simple prompt without arguments." + } + } + ] +} \ No newline at end of file diff --git a/cli/scripts/metadata-test-output/metadata_prompts_list.log b/cli/scripts/metadata-test-output/metadata_prompts_list.log new file mode 100644 index 00000000..6bad1c3f --- /dev/null +++ b/cli/scripts/metadata-test-output/metadata_prompts_list.log @@ -0,0 +1,35 @@ +{ + "prompts": [ + { + "name": "simple_prompt", + "description": "A prompt without arguments" + }, + { + "name": "complex_prompt", + "description": "A prompt with arguments", + "arguments": [ + { + "name": "temperature", + "description": "Temperature setting", + "required": true + }, + { + "name": "style", + "description": "Output style", + "required": false + } + ] + }, + { + "name": "resource_prompt", + "description": "A prompt that includes an embedded resource reference", + "arguments": [ + { + "name": "resourceId", + "description": "Resource ID to include (1-100)", + "required": true + } + ] + } + ] +} \ No newline at end of file diff --git a/cli/scripts/metadata-test-output/metadata_resources_list.log b/cli/scripts/metadata-test-output/metadata_resources_list.log new file mode 100644 index 00000000..f0d23a35 --- /dev/null +++ b/cli/scripts/metadata-test-output/metadata_resources_list.log @@ -0,0 +1,65 @@ +{ + "nextCursor": "MTA=", + "resources": [ + { + "name": "Resource 1", + "uri": "test://static/resource/1", + "mimeType": "text/plain", + "text": "Resource 1: This is a plaintext resource" + }, + { + "name": "Resource 2", + "uri": "test://static/resource/2", + "mimeType": "application/octet-stream", + "blob": "UmVzb3VyY2UgMjogVGhpcyBpcyBhIGJhc2U2NCBibG9i" + }, + { + "name": "Resource 3", + "uri": "test://static/resource/3", + "mimeType": "text/plain", + "text": "Resource 3: This is a plaintext resource" + }, + { + "name": "Resource 4", + "uri": "test://static/resource/4", + "mimeType": "application/octet-stream", + "blob": "UmVzb3VyY2UgNDogVGhpcyBpcyBhIGJhc2U2NCBibG9i" + }, + { + "name": "Resource 5", + "uri": "test://static/resource/5", + "mimeType": "text/plain", + "text": "Resource 5: This is a plaintext resource" + }, + { + "name": "Resource 6", + "uri": "test://static/resource/6", + "mimeType": "application/octet-stream", + "blob": "UmVzb3VyY2UgNjogVGhpcyBpcyBhIGJhc2U2NCBibG9i" + }, + { + "name": "Resource 7", + "uri": "test://static/resource/7", + "mimeType": "text/plain", + "text": "Resource 7: This is a plaintext resource" + }, + { + "name": "Resource 8", + "uri": "test://static/resource/8", + "mimeType": "application/octet-stream", + "blob": "UmVzb3VyY2UgODogVGhpcyBpcyBhIGJhc2U2NCBibG9i" + }, + { + "name": "Resource 9", + "uri": "test://static/resource/9", + "mimeType": "text/plain", + "text": "Resource 9: This is a plaintext resource" + }, + { + "name": "Resource 10", + "uri": "test://static/resource/10", + "mimeType": "application/octet-stream", + "blob": "UmVzb3VyY2UgMTA6IFRoaXMgaXMgYSBiYXNlNjQgYmxvYg==" + } + ] +} \ No newline at end of file diff --git a/cli/scripts/metadata-test-output/metadata_resources_read.log b/cli/scripts/metadata-test-output/metadata_resources_read.log new file mode 100644 index 00000000..9b184e70 --- /dev/null +++ b/cli/scripts/metadata-test-output/metadata_resources_read.log @@ -0,0 +1,10 @@ +{ + "contents": [ + { + "uri": "test://static/resource/1", + "mimeType": "text/plain", + "text": "Resource 1: This is a plaintext resource", + "name": "Resource 1" + } + ] +} \ No newline at end of file diff --git a/cli/scripts/metadata-test-output/metadata_single_entry.log b/cli/scripts/metadata-test-output/metadata_single_entry.log new file mode 100644 index 00000000..114d6da4 --- /dev/null +++ b/cli/scripts/metadata-test-output/metadata_single_entry.log @@ -0,0 +1,218 @@ +{ + "tools": [ + { + "name": "echo", + "description": "Echoes back the input", + "inputSchema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Message to echo" + } + }, + "required": [ + "message" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "add", + "description": "Adds two numbers", + "inputSchema": { + "type": "object", + "properties": { + "a": { + "type": "number", + "description": "First number" + }, + "b": { + "type": "number", + "description": "Second number" + } + }, + "required": [ + "a", + "b" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "longRunningOperation", + "description": "Demonstrates a long running operation with progress updates", + "inputSchema": { + "type": "object", + "properties": { + "duration": { + "type": "number", + "default": 10, + "description": "Duration of the operation in seconds" + }, + "steps": { + "type": "number", + "default": 5, + "description": "Number of steps in the operation" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "printEnv", + "description": "Prints all environment variables, helpful for debugging MCP server configuration", + "inputSchema": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "sampleLLM", + "description": "Samples from an LLM using MCP's sampling feature", + "inputSchema": { + "type": "object", + "properties": { + "prompt": { + "type": "string", + "description": "The prompt to send to the LLM" + }, + "maxTokens": { + "type": "number", + "default": 100, + "description": "Maximum number of tokens to generate" + } + }, + "required": [ + "prompt" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getTinyImage", + "description": "Returns the MCP_TINY_IMAGE", + "inputSchema": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "annotatedMessage", + "description": "Demonstrates how annotations can be used to provide metadata about content", + "inputSchema": { + "type": "object", + "properties": { + "messageType": { + "type": "string", + "enum": [ + "error", + "success", + "debug" + ], + "description": "Type of message to demonstrate different annotation patterns" + }, + "includeImage": { + "type": "boolean", + "default": false, + "description": "Whether to include an example image" + } + }, + "required": [ + "messageType" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getResourceReference", + "description": "Returns a resource reference that can be used by MCP clients", + "inputSchema": { + "type": "object", + "properties": { + "resourceId": { + "type": "number", + "minimum": 1, + "maximum": 100, + "description": "ID of the resource to reference (1-100)" + } + }, + "required": [ + "resourceId" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getResourceLinks", + "description": "Returns multiple resource links that reference different types of resources", + "inputSchema": { + "type": "object", + "properties": { + "count": { + "type": "number", + "minimum": 1, + "maximum": 10, + "default": 3, + "description": "Number of resource links to return (1-10)" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "structuredContent", + "description": "Returns structured content along with an output schema for client data validation", + "inputSchema": { + "type": "object", + "properties": { + "location": { + "type": "string", + "minLength": 1, + "description": "City name or zip code" + } + }, + "required": [ + "location" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "outputSchema": { + "type": "object", + "properties": { + "temperature": { + "type": "number", + "description": "Temperature in celsius" + }, + "conditions": { + "type": "string", + "description": "Weather conditions description" + }, + "humidity": { + "type": "number", + "description": "Humidity percentage" + } + }, + "required": [ + "temperature", + "conditions", + "humidity" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + } + ] +} \ No newline at end of file diff --git a/cli/scripts/metadata-test-output/metadata_tools_call_complex_tool_meta.log b/cli/scripts/metadata-test-output/metadata_tools_call_complex_tool_meta.log new file mode 100644 index 00000000..2a6bf883 --- /dev/null +++ b/cli/scripts/metadata-test-output/metadata_tools_call_complex_tool_meta.log @@ -0,0 +1,8 @@ +{ + "content": [ + { + "type": "text", + "text": "The sum of 10 and 20 is 30." + } + ] +} \ No newline at end of file diff --git a/cli/scripts/metadata-test-output/metadata_tools_call_tool_meta.log b/cli/scripts/metadata-test-output/metadata_tools_call_tool_meta.log new file mode 100644 index 00000000..145c3ac0 --- /dev/null +++ b/cli/scripts/metadata-test-output/metadata_tools_call_tool_meta.log @@ -0,0 +1,8 @@ +{ + "content": [ + { + "type": "text", + "text": "Echo: hello world" + } + ] +} \ No newline at end of file diff --git a/cli/scripts/metadata-test-output/metadata_tools_list.log b/cli/scripts/metadata-test-output/metadata_tools_list.log new file mode 100644 index 00000000..114d6da4 --- /dev/null +++ b/cli/scripts/metadata-test-output/metadata_tools_list.log @@ -0,0 +1,218 @@ +{ + "tools": [ + { + "name": "echo", + "description": "Echoes back the input", + "inputSchema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Message to echo" + } + }, + "required": [ + "message" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "add", + "description": "Adds two numbers", + "inputSchema": { + "type": "object", + "properties": { + "a": { + "type": "number", + "description": "First number" + }, + "b": { + "type": "number", + "description": "Second number" + } + }, + "required": [ + "a", + "b" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "longRunningOperation", + "description": "Demonstrates a long running operation with progress updates", + "inputSchema": { + "type": "object", + "properties": { + "duration": { + "type": "number", + "default": 10, + "description": "Duration of the operation in seconds" + }, + "steps": { + "type": "number", + "default": 5, + "description": "Number of steps in the operation" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "printEnv", + "description": "Prints all environment variables, helpful for debugging MCP server configuration", + "inputSchema": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "sampleLLM", + "description": "Samples from an LLM using MCP's sampling feature", + "inputSchema": { + "type": "object", + "properties": { + "prompt": { + "type": "string", + "description": "The prompt to send to the LLM" + }, + "maxTokens": { + "type": "number", + "default": 100, + "description": "Maximum number of tokens to generate" + } + }, + "required": [ + "prompt" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getTinyImage", + "description": "Returns the MCP_TINY_IMAGE", + "inputSchema": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "annotatedMessage", + "description": "Demonstrates how annotations can be used to provide metadata about content", + "inputSchema": { + "type": "object", + "properties": { + "messageType": { + "type": "string", + "enum": [ + "error", + "success", + "debug" + ], + "description": "Type of message to demonstrate different annotation patterns" + }, + "includeImage": { + "type": "boolean", + "default": false, + "description": "Whether to include an example image" + } + }, + "required": [ + "messageType" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getResourceReference", + "description": "Returns a resource reference that can be used by MCP clients", + "inputSchema": { + "type": "object", + "properties": { + "resourceId": { + "type": "number", + "minimum": 1, + "maximum": 100, + "description": "ID of the resource to reference (1-100)" + } + }, + "required": [ + "resourceId" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "getResourceLinks", + "description": "Returns multiple resource links that reference different types of resources", + "inputSchema": { + "type": "object", + "properties": { + "count": { + "type": "number", + "minimum": 1, + "maximum": 10, + "default": 3, + "description": "Number of resource links to return (1-10)" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "structuredContent", + "description": "Returns structured content along with an output schema for client data validation", + "inputSchema": { + "type": "object", + "properties": { + "location": { + "type": "string", + "minLength": 1, + "description": "City name or zip code" + } + }, + "required": [ + "location" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "outputSchema": { + "type": "object", + "properties": { + "temperature": { + "type": "number", + "description": "Temperature in celsius" + }, + "conditions": { + "type": "string", + "description": "Weather conditions description" + }, + "humidity": { + "type": "number", + "description": "Humidity percentage" + } + }, + "required": [ + "temperature", + "conditions", + "humidity" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + } + ] +} \ No newline at end of file diff --git a/cli/src/client/prompts.ts b/cli/src/client/prompts.ts index 45e269f2..e7a1cf2f 100644 --- a/cli/src/client/prompts.ts +++ b/cli/src/client/prompts.ts @@ -14,11 +14,11 @@ type JsonValue = // List available prompts export async function listPrompts( client: Client, - metaData?: Record, + metadata?: Record, ): Promise { try { const params = - metaData && Object.keys(metaData).length > 0 ? { _meta: metaData } : {}; + metadata && Object.keys(metadata).length > 0 ? { _meta: metadata } : {}; const response = await client.listPrompts(params); return response; } catch (error) { @@ -33,7 +33,7 @@ export async function getPrompt( client: Client, name: string, args?: Record, - metaData?: Record, + metadata?: Record, ): Promise { try { // Convert all arguments to strings for prompt arguments @@ -55,8 +55,8 @@ export async function getPrompt( arguments: stringArgs, }; - if (metaData && Object.keys(metaData).length > 0) { - params._meta = metaData; + if (metadata && Object.keys(metadata).length > 0) { + params._meta = metadata; } const response = await client.getPrompt(params); diff --git a/cli/src/client/resources.ts b/cli/src/client/resources.ts index d530bbc2..3e44820c 100644 --- a/cli/src/client/resources.ts +++ b/cli/src/client/resources.ts @@ -4,11 +4,11 @@ import { McpResponse } from "./types.js"; // List available resources export async function listResources( client: Client, - metaData?: Record, + metadata?: Record, ): Promise { try { const params = - metaData && Object.keys(metaData).length > 0 ? { _meta: metaData } : {}; + metadata && Object.keys(metadata).length > 0 ? { _meta: metadata } : {}; const response = await client.listResources(params); return response; } catch (error) { @@ -22,12 +22,12 @@ export async function listResources( export async function readResource( client: Client, uri: string, - metaData?: Record, + metadata?: Record, ): Promise { try { const params: any = { uri }; - if (metaData && Object.keys(metaData).length > 0) { - params._meta = metaData; + if (metadata && Object.keys(metadata).length > 0) { + params._meta = metadata; } const response = await client.readResource(params); return response; @@ -41,11 +41,11 @@ export async function readResource( // List resource templates export async function listResourceTemplates( client: Client, - metaData?: Record, + metadata?: Record, ): Promise { try { const params = - metaData && Object.keys(metaData).length > 0 ? { _meta: metaData } : {}; + metadata && Object.keys(metadata).length > 0 ? { _meta: metadata } : {}; const response = await client.listResourceTemplates(params); return response; } catch (error) { diff --git a/cli/src/client/tools.ts b/cli/src/client/tools.ts index df2634a7..909f17af 100644 --- a/cli/src/client/tools.ts +++ b/cli/src/client/tools.ts @@ -21,11 +21,11 @@ type JsonSchemaType = { export async function listTools( client: Client, - metaData?: Record, + metadata?: Record, ): Promise { try { const params = - metaData && Object.keys(metaData).length > 0 ? { _meta: metaData } : {}; + metadata && Object.keys(metadata).length > 0 ? { _meta: metadata } : {}; const response = await client.listTools(params); return response; } catch (error) { @@ -87,11 +87,11 @@ export async function callTool( client: Client, name: string, args: Record, - generalMetaData?: Record, - toolSpecificMetaData?: Record, + generalMetadata?: Record, + toolSpecificMetadata?: Record, ): Promise { try { - const toolsResponse = await listTools(client, generalMetaData); + const toolsResponse = await listTools(client, generalMetadata); const tools = toolsResponse.tools as Tool[]; const tool = tools.find((t) => t.name === name); @@ -116,10 +116,10 @@ export async function callTool( // Merge general metadata with tool-specific metadata // Tool-specific metadata takes precedence over general metadata let mergedMeta: Record | undefined; - if (generalMetaData || toolSpecificMetaData) { + if (generalMetadata || toolSpecificMetadata) { mergedMeta = { - ...(generalMetaData || {}), - ...(toolSpecificMetaData || {}), + ...(generalMetadata || {}), + ...(toolSpecificMetadata || {}), }; } diff --git a/cli/src/index.ts b/cli/src/index.ts index e9f60ebd..ab655ff1 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -45,7 +45,7 @@ type Args = { toolMeta?: Record; transport?: "sse" | "stdio" | "http"; headers?: Record; - metaData?: Record; + metadata?: Record; }; function createTransportOptions( @@ -123,7 +123,7 @@ async function callMethod(args: Args): Promise { // Tools methods if (args.method === "tools/list") { - result = await listTools(client, args.metaData); + result = await listTools(client, args.metadata); } else if (args.method === "tools/call") { if (!args.toolName) { throw new Error( @@ -135,13 +135,13 @@ async function callMethod(args: Args): Promise { client, args.toolName, args.toolArg || {}, - args.metaData, + args.metadata, args.toolMeta, ); } // Resources methods else if (args.method === "resources/list") { - result = await listResources(client, args.metaData); + result = await listResources(client, args.metadata); } else if (args.method === "resources/read") { if (!args.uri) { throw new Error( @@ -149,13 +149,13 @@ async function callMethod(args: Args): Promise { ); } - result = await readResource(client, args.uri, args.metaData); + result = await readResource(client, args.uri, args.metadata); } else if (args.method === "resources/templates/list") { - result = await listResourceTemplates(client, args.metaData); + result = await listResourceTemplates(client, args.metadata); } // Prompts methods else if (args.method === "prompts/list") { - result = await listPrompts(client, args.metaData); + result = await listPrompts(client, args.metadata); } else if (args.method === "prompts/get") { if (!args.promptName) { throw new Error( @@ -167,7 +167,7 @@ async function callMethod(args: Args): Promise { client, args.promptName, args.promptArgs || {}, - args.metaData, + args.metadata, ); } // Logging methods @@ -333,6 +333,21 @@ function parseArgs(): Args { 'HTTP headers as "HeaderName: Value" pairs (for HTTP/SSE transports)', parseHeaderPair, {}, + ) + // + // Metadata options + // + .option( + "--metadata ", + "General metadata as key=value pairs (applied to all methods)", + parseKeyValuePair, + {}, + ) + .option( + "--tool-metadata ", + "Tool-specific metadata as key=value pairs (for tools/call method only)", + parseKeyValuePair, + {}, ); // Parse only the arguments before -- @@ -340,8 +355,8 @@ function parseArgs(): Args { const options = program.opts() as Omit & { header?: Record; - meta?: Record; - toolMeta?: Record; + metadata?: Record; + toolMetadata?: Record; }; let remainingArgs = program.args; @@ -359,17 +374,17 @@ function parseArgs(): Args { target: finalArgs, ...options, headers: options.header, // commander.js uses 'header' field, map to 'headers' - metaData: options.meta + metadata: options.metadata ? Object.fromEntries( - Object.entries(options.meta).map(([key, value]) => [ + Object.entries(options.metadata).map(([key, value]) => [ key, String(value), ]), ) : undefined, - toolMeta: options.toolMeta + toolMeta: options.toolMetadata ? Object.fromEntries( - Object.entries(options.toolMeta).map(([key, value]) => [ + Object.entries(options.toolMetadata).map(([key, value]) => [ key, String(value), ]), diff --git a/client/src/App.tsx b/client/src/App.tsx index 0f8997bd..14f4c339 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -198,11 +198,11 @@ const App = () => { useState(EMPTY_DEBUGGER_STATE); // Metadata state - persisted in localStorage - const [metaData, setMetaData] = useState>(() => { - const savedMetaData = localStorage.getItem("lastMetaData"); - if (savedMetaData) { + const [metadata, setMetadata] = useState>(() => { + const savedMetadata = localStorage.getItem("lastMetadata"); + if (savedMetadata) { try { - return JSON.parse(savedMetaData); + return JSON.parse(savedMetadata); } catch (error) { console.warn("Failed to parse saved metadata:", error); } @@ -214,9 +214,9 @@ const App = () => { setAuthState((prev) => ({ ...prev, ...updates })); }; - const handleMetaDataChange = (newMetaData: Record) => { - setMetaData(newMetaData); - localStorage.setItem("lastMetaData", JSON.stringify(newMetaData)); + const handleMetadataChange = (newMetadata: Record) => { + setMetadata(newMetadata); + localStorage.setItem("lastMetadata", JSON.stringify(newMetadata)); }; const nextRequestId = useRef(0); const rootsRef = useRef([]); @@ -319,7 +319,7 @@ const App = () => { }, getRoots: () => rootsRef.current, defaultLoggingLevel: logLevel, - metaData, + metadata, }); useEffect(() => { @@ -813,7 +813,7 @@ const App = () => { // Merge general metadata with tool-specific metadata // Tool-specific metadata takes precedence over general metadata const mergedMeta = { - ...metaData, // General metadata first + ...metadata, // General metadata first progressToken: progressTokenRef.current++, ...(meta ?? {}), // Tool-specific metadata overrides }; @@ -1185,8 +1185,8 @@ const App = () => { /> )} diff --git a/client/src/components/MetaDataTab.tsx b/client/src/components/MetaDataTab.tsx index eefedba4..477952c3 100644 --- a/client/src/components/MetaDataTab.tsx +++ b/client/src/components/MetaDataTab.tsx @@ -5,22 +5,22 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Trash2, Plus } from "lucide-react"; -interface MetaDataEntry { +interface MetadataEntry { key: string; value: string; } interface MetadataTabProps { - metaData: Record; - onMetaDataChange: (metaData: Record) => void; + metadata: Record; + onMetadataChange: (metadata: Record) => void; } const MetadataTab: React.FC = ({ - metaData, - onMetaDataChange, + metadata, + onMetadataChange, }) => { - const [entries, setEntries] = useState(() => { - return Object.entries(metaData).map(([key, value]) => ({ key, value })); + const [entries, setEntries] = useState(() => { + return Object.entries(metadata).map(([key, value]) => ({ key, value })); }); const addEntry = () => { @@ -30,7 +30,7 @@ const MetadataTab: React.FC = ({ const removeEntry = (index: number) => { const newEntries = entries.filter((_, i) => i !== index); setEntries(newEntries); - updateMetaData(newEntries); + updateMetadata(newEntries); }; const updateEntry = ( @@ -41,17 +41,17 @@ const MetadataTab: React.FC = ({ const newEntries = [...entries]; newEntries[index][field] = value; setEntries(newEntries); - updateMetaData(newEntries); + updateMetadata(newEntries); }; - const updateMetaData = (newEntries: MetaDataEntry[]) => { - const metaDataObject: Record = {}; + const updateMetadata = (newEntries: MetadataEntry[]) => { + const metadataObject: Record = {}; newEntries.forEach(({ key, value }) => { if (key.trim() && value.trim()) { - metaDataObject[key.trim()] = value.trim(); + metadataObject[key.trim()] = value.trim(); } }); - onMetaDataChange(metaDataObject); + onMetadataChange(metadataObject); }; return ( diff --git a/client/src/components/__tests__/MetaDataTab.test.tsx b/client/src/components/__tests__/MetaDataTab.test.tsx index 339e7015..9d0cbf99 100644 --- a/client/src/components/__tests__/MetaDataTab.test.tsx +++ b/client/src/components/__tests__/MetaDataTab.test.tsx @@ -5,8 +5,8 @@ import { Tabs } from "@/components/ui/tabs"; describe("MetadataTab", () => { const defaultProps = { - metaData: {}, - onMetaDataChange: jest.fn(), + metadata: {}, + onMetadataChange: jest.fn(), }; const renderMetadataTab = (props = {}) => { @@ -52,7 +52,7 @@ describe("MetadataTab", () => { it("should not show empty state message when entries exist", () => { renderMetadataTab({ - metaData: { key1: "value1" }, + metadata: { key1: "value1" }, }); expect( @@ -65,12 +65,12 @@ describe("MetadataTab", () => { describe("Initial Data Handling", () => { it("should initialize with existing metadata", () => { - const initialMetaData = { + const initialMetadata = { API_KEY: "test-key", VERSION: "1.0.0", }; - renderMetadataTab({ metaData: initialMetaData }); + renderMetadataTab({ metadata: initialMetadata }); expect(screen.getByDisplayValue("API_KEY")).toBeInTheDocument(); expect(screen.getByDisplayValue("test-key")).toBeInTheDocument(); @@ -79,13 +79,13 @@ describe("MetadataTab", () => { }); it("should render multiple entries in correct order", () => { - const initialMetaData = { + const initialMetadata = { FIRST: "first-value", SECOND: "second-value", THIRD: "third-value", }; - renderMetadataTab({ metaData: initialMetaData }); + renderMetadataTab({ metadata: initialMetadata }); const keyInputs = screen.getAllByPlaceholderText("Key"); const valueInputs = screen.getAllByPlaceholderText("Value"); @@ -94,7 +94,7 @@ describe("MetadataTab", () => { expect(valueInputs).toHaveLength(3); // Check that entries are rendered in the order they appear in the object - const entries = Object.entries(initialMetaData); + const entries = Object.entries(initialMetadata); entries.forEach(([key, value], index) => { expect(keyInputs[index]).toHaveValue(key); expect(valueInputs[index]).toHaveValue(value); @@ -157,7 +157,7 @@ describe("MetadataTab", () => { describe("Removing Entries", () => { it("should render remove button for each entry", () => { renderMetadataTab({ - metaData: { key1: "value1", key2: "value2" }, + metadata: { key1: "value1", key2: "value2" }, }); const removeButtons = screen.getAllByRole("button", { name: "" }); // Trash icon buttons have no text @@ -165,87 +165,87 @@ describe("MetadataTab", () => { }); it("should remove entry when remove button is clicked", () => { - const onMetaDataChange = jest.fn(); + const onMetadataChange = jest.fn(); renderMetadataTab({ - metaData: { key1: "value1", key2: "value2" }, - onMetaDataChange, + metadata: { key1: "value1", key2: "value2" }, + onMetadataChange, }); const removeButtons = screen.getAllByRole("button", { name: "" }); fireEvent.click(removeButtons[0]); - expect(onMetaDataChange).toHaveBeenCalledWith({ key2: "value2" }); + expect(onMetadataChange).toHaveBeenCalledWith({ key2: "value2" }); }); it("should remove correct entry when multiple entries exist", () => { - const onMetaDataChange = jest.fn(); + const onMetadataChange = jest.fn(); renderMetadataTab({ - metaData: { + metadata: { FIRST: "first-value", SECOND: "second-value", THIRD: "third-value", }, - onMetaDataChange, + onMetadataChange, }); const removeButtons = screen.getAllByRole("button", { name: "" }); fireEvent.click(removeButtons[1]); // Remove second entry - expect(onMetaDataChange).toHaveBeenCalledWith({ + expect(onMetadataChange).toHaveBeenCalledWith({ FIRST: "first-value", THIRD: "third-value", }); }); it("should show empty state message after removing all entries", () => { - const onMetaDataChange = jest.fn(); + const onMetadataChange = jest.fn(); renderMetadataTab({ - metaData: { key1: "value1" }, - onMetaDataChange, + metadata: { key1: "value1" }, + onMetadataChange, }); const removeButton = screen.getByRole("button", { name: "" }); fireEvent.click(removeButton); - expect(onMetaDataChange).toHaveBeenCalledWith({}); + expect(onMetadataChange).toHaveBeenCalledWith({}); }); }); describe("Editing Entries", () => { it("should update key when key input is changed", () => { - const onMetaDataChange = jest.fn(); + const onMetadataChange = jest.fn(); renderMetadataTab({ - metaData: { oldKey: "value1" }, - onMetaDataChange, + metadata: { oldKey: "value1" }, + onMetadataChange, }); const keyInput = screen.getByDisplayValue("oldKey"); fireEvent.change(keyInput, { target: { value: "newKey" } }); - expect(onMetaDataChange).toHaveBeenCalledWith({ newKey: "value1" }); + expect(onMetadataChange).toHaveBeenCalledWith({ newKey: "value1" }); }); it("should update value when value input is changed", () => { - const onMetaDataChange = jest.fn(); + const onMetadataChange = jest.fn(); renderMetadataTab({ - metaData: { key1: "oldValue" }, - onMetaDataChange, + metadata: { key1: "oldValue" }, + onMetadataChange, }); const valueInput = screen.getByDisplayValue("oldValue"); fireEvent.change(valueInput, { target: { value: "newValue" } }); - expect(onMetaDataChange).toHaveBeenCalledWith({ key1: "newValue" }); + expect(onMetadataChange).toHaveBeenCalledWith({ key1: "newValue" }); }); it("should handle editing multiple entries independently", () => { - const onMetaDataChange = jest.fn(); + const onMetadataChange = jest.fn(); renderMetadataTab({ - metaData: { + metadata: { key1: "value1", key2: "value2", }, - onMetaDataChange, + onMetadataChange, }); const keyInputs = screen.getAllByPlaceholderText("Key"); @@ -253,17 +253,17 @@ describe("MetadataTab", () => { // Edit first entry key fireEvent.change(keyInputs[0], { target: { value: "newKey1" } }); - expect(onMetaDataChange).toHaveBeenCalledWith({ + expect(onMetadataChange).toHaveBeenCalledWith({ newKey1: "value1", key2: "value2", }); // Clear mock to test second edit independently - onMetaDataChange.mockClear(); + onMetadataChange.mockClear(); // Edit second entry value fireEvent.change(valueInputs[1], { target: { value: "newValue2" } }); - expect(onMetaDataChange).toHaveBeenCalledWith({ + expect(onMetadataChange).toHaveBeenCalledWith({ newKey1: "value1", key2: "newValue2", }); @@ -272,8 +272,8 @@ describe("MetadataTab", () => { describe("Data Validation and Trimming", () => { it("should trim whitespace from keys and values", () => { - const onMetaDataChange = jest.fn(); - renderMetadataTab({ onMetaDataChange }); + const onMetadataChange = jest.fn(); + renderMetadataTab({ onMetadataChange }); const addButton = screen.getByRole("button", { name: /add entry/i }); fireEvent.click(addButton); @@ -284,14 +284,14 @@ describe("MetadataTab", () => { fireEvent.change(keyInput, { target: { value: " trimmedKey " } }); fireEvent.change(valueInput, { target: { value: " trimmedValue " } }); - expect(onMetaDataChange).toHaveBeenCalledWith({ + expect(onMetadataChange).toHaveBeenCalledWith({ trimmedKey: "trimmedValue", }); }); it("should exclude entries with empty keys or values after trimming", () => { - const onMetaDataChange = jest.fn(); - renderMetadataTab({ onMetaDataChange }); + const onMetadataChange = jest.fn(); + renderMetadataTab({ onMetadataChange }); const addButton = screen.getByRole("button", { name: /add entry/i }); fireEvent.click(addButton); @@ -308,14 +308,14 @@ describe("MetadataTab", () => { fireEvent.change(keyInputs[1], { target: { value: "" } }); fireEvent.change(valueInputs[1], { target: { value: "someValue" } }); - expect(onMetaDataChange).toHaveBeenCalledWith({ + expect(onMetadataChange).toHaveBeenCalledWith({ validKey: "validValue", }); }); it("should exclude entries with whitespace-only keys or values", () => { - const onMetaDataChange = jest.fn(); - renderMetadataTab({ onMetaDataChange }); + const onMetadataChange = jest.fn(); + renderMetadataTab({ onMetadataChange }); const addButton = screen.getByRole("button", { name: /add entry/i }); fireEvent.click(addButton); @@ -332,14 +332,14 @@ describe("MetadataTab", () => { fireEvent.change(keyInputs[1], { target: { value: " " } }); fireEvent.change(valueInputs[1], { target: { value: "someValue" } }); - expect(onMetaDataChange).toHaveBeenCalledWith({ + expect(onMetadataChange).toHaveBeenCalledWith({ validKey: "validValue", }); }); it("should handle mixed valid and invalid entries", () => { - const onMetaDataChange = jest.fn(); - renderMetadataTab({ onMetaDataChange }); + const onMetadataChange = jest.fn(); + renderMetadataTab({ onMetadataChange }); const addButton = screen.getByRole("button", { name: /add entry/i }); fireEvent.click(addButton); @@ -361,7 +361,7 @@ describe("MetadataTab", () => { fireEvent.change(keyInputs[2], { target: { value: "key3" } }); fireEvent.change(valueInputs[2], { target: { value: "value3" } }); - expect(onMetaDataChange).toHaveBeenCalledWith({ + expect(onMetadataChange).toHaveBeenCalledWith({ key1: "value1", key3: "value3", }); @@ -371,7 +371,7 @@ describe("MetadataTab", () => { describe("Input Accessibility", () => { it("should have proper labels for screen readers", () => { renderMetadataTab({ - metaData: { key1: "value1" }, + metadata: { key1: "value1" }, }); const keyLabel = screen.getByLabelText("Key", { selector: "input" }); @@ -383,7 +383,7 @@ describe("MetadataTab", () => { it("should have unique IDs for each input pair", () => { renderMetadataTab({ - metaData: { + metadata: { key1: "value1", key2: "value2", }, @@ -414,8 +414,8 @@ describe("MetadataTab", () => { describe("Edge Cases", () => { it("should handle special characters in keys and values", () => { - const onMetaDataChange = jest.fn(); - renderMetadataTab({ onMetaDataChange }); + const onMetadataChange = jest.fn(); + renderMetadataTab({ onMetadataChange }); const addButton = screen.getByRole("button", { name: /add entry/i }); fireEvent.click(addButton); @@ -430,14 +430,14 @@ describe("MetadataTab", () => { target: { value: "value with spaces & symbols $%^" }, }); - expect(onMetaDataChange).toHaveBeenCalledWith({ + expect(onMetadataChange).toHaveBeenCalledWith({ "key-with-special@chars!": "value with spaces & symbols $%^", }); }); it("should handle unicode characters", () => { - const onMetaDataChange = jest.fn(); - renderMetadataTab({ onMetaDataChange }); + const onMetadataChange = jest.fn(); + renderMetadataTab({ onMetadataChange }); const addButton = screen.getByRole("button", { name: /add entry/i }); fireEvent.click(addButton); @@ -448,14 +448,14 @@ describe("MetadataTab", () => { fireEvent.change(keyInput, { target: { value: "🔑_key" } }); fireEvent.change(valueInput, { target: { value: "值_value_🎯" } }); - expect(onMetaDataChange).toHaveBeenCalledWith({ + expect(onMetadataChange).toHaveBeenCalledWith({ "🔑_key": "值_value_🎯", }); }); it("should handle very long keys and values", () => { - const onMetaDataChange = jest.fn(); - renderMetadataTab({ onMetaDataChange }); + const onMetadataChange = jest.fn(); + renderMetadataTab({ onMetadataChange }); const addButton = screen.getByRole("button", { name: /add entry/i }); fireEvent.click(addButton); @@ -469,14 +469,14 @@ describe("MetadataTab", () => { fireEvent.change(keyInput, { target: { value: longKey } }); fireEvent.change(valueInput, { target: { value: longValue } }); - expect(onMetaDataChange).toHaveBeenCalledWith({ + expect(onMetadataChange).toHaveBeenCalledWith({ [longKey]: longValue, }); }); it("should handle duplicate keys by keeping the last one", () => { - const onMetaDataChange = jest.fn(); - renderMetadataTab({ onMetaDataChange }); + const onMetadataChange = jest.fn(); + renderMetadataTab({ onMetadataChange }); const addButton = screen.getByRole("button", { name: /add entry/i }); fireEvent.click(addButton); @@ -493,44 +493,44 @@ describe("MetadataTab", () => { fireEvent.change(valueInputs[1], { target: { value: "secondValue" } }); // The second value should overwrite the first - expect(onMetaDataChange).toHaveBeenCalledWith({ + expect(onMetadataChange).toHaveBeenCalledWith({ duplicateKey: "secondValue", }); }); }); describe("Integration with Parent Component", () => { - it("should not call onMetaDataChange when component mounts with existing data", () => { - const onMetaDataChange = jest.fn(); + it("should not call onMetadataChange when component mounts with existing data", () => { + const onMetadataChange = jest.fn(); renderMetadataTab({ - metaData: { key1: "value1" }, - onMetaDataChange, + metadata: { key1: "value1" }, + onMetadataChange, }); - expect(onMetaDataChange).not.toHaveBeenCalled(); + expect(onMetadataChange).not.toHaveBeenCalled(); }); - it("should call onMetaDataChange only when user makes changes", () => { - const onMetaDataChange = jest.fn(); + it("should call onMetadataChange only when user makes changes", () => { + const onMetadataChange = jest.fn(); renderMetadataTab({ - metaData: { key1: "value1" }, - onMetaDataChange, + metadata: { key1: "value1" }, + onMetadataChange, }); // Should not be called on mount - expect(onMetaDataChange).not.toHaveBeenCalled(); + expect(onMetadataChange).not.toHaveBeenCalled(); // Should be called when user changes value const valueInput = screen.getByDisplayValue("value1"); fireEvent.change(valueInput, { target: { value: "newValue" } }); - expect(onMetaDataChange).toHaveBeenCalledTimes(1); - expect(onMetaDataChange).toHaveBeenCalledWith({ key1: "newValue" }); + expect(onMetadataChange).toHaveBeenCalledTimes(1); + expect(onMetadataChange).toHaveBeenCalledWith({ key1: "newValue" }); }); it("should maintain internal state when props change (component doesn't sync with prop changes)", () => { const { rerender } = renderMetadataTab({ - metaData: { key1: "value1" }, + metadata: { key1: "value1" }, }); expect(screen.getByDisplayValue("key1")).toBeInTheDocument(); @@ -541,8 +541,8 @@ describe("MetadataTab", () => { rerender( , ); diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index 7bb3d01c..dcca209b 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -79,7 +79,7 @@ interface UseConnectionOptions { // eslint-disable-next-line @typescript-eslint/no-explicit-any getRoots?: () => any[]; defaultLoggingLevel?: LoggingLevel; - metaData?: Record; + metadata?: Record; } export function useConnection({ @@ -98,7 +98,7 @@ export function useConnection({ onElicitationRequest, getRoots, defaultLoggingLevel, - metaData = {}, + metadata = {}, }: UseConnectionOptions) { const [connectionStatus, setConnectionStatus] = useState("disconnected"); @@ -158,13 +158,13 @@ export function useConnection({ // Add metadata to the request if available, but skip for tool calls // as they handle metadata merging separately const shouldAddGeneralMeta = - request.method !== "tools/call" && Object.keys(metaData).length > 0; + request.method !== "tools/call" && Object.keys(metadata).length > 0; const requestWithMeta = shouldAddGeneralMeta ? { ...request, params: { ...request.params, - _meta: metaData, + _meta: metadata, }, } : request;