Skip to content

Conversation

@olaservo
Copy link
Member

@olaservo olaservo commented Feb 27, 2025

Motivation and Context

The ToolTab JSON input handling still has some quirks and testing it manually isn't very sustainable. This PR adds the Jest testing framework to the client side of the MCP Inspector, enabling unit testing for the more complex client-side utilities and components.

The intent here is to improve maintainability by:

  1. Extracting utility functions from components into dedicated utility files
    • jsonPathUtils.ts: Handles JSON path operations (updating and retrieving values)
    • schemaUtils.ts: Handles schema-related operations (generating default values, formatting labels)
  2. Adding unit tests for these utilities
  3. Improving the DynamicJsonForm component with better error handling and state management, for better handling of edge cases (empty objects, missing schema properties).
  4. Enhancing the JsonEditor component with better state management and formatting capabilities
  5. Address this issue: Cannot pass an integer correctly #154

Another option to consider is using a JSON editing library which handles more of this stuff out of the box. I prefer to avoid adding libraries which might be overkill and introduce their own overhead. But it's still an option, if trying to cover all the more complex tool input testing scenarios doesn't seem sustainable in the long term.

How Has This Been Tested?

The PR includes new Jest tests for the utility functions that were extracted from components:

  • Tests for jsonPathUtils (updateValueAtPath, getValueAtPath)
  • Tests for schemaUtils (generateDefaultValue, formatFieldLabel, validateValueAgainstSchema)

I also updated the GitHub workflow to run these tests automatically during CI.

After these changes I also confirmed that I could update JSON fields for object tool inputs, which appears to be broken right now.

Breaking Changes

None.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling

@olaservo olaservo changed the title Handle empty json fields Improve on tool input handling and add tests Feb 28, 2025
@olaservo olaservo mentioned this pull request Feb 28, 2025
9 tasks
@olaservo olaservo marked this pull request as ready for review February 28, 2025 05:10
@olaservo olaservo requested a review from jspahrsummers March 5, 2025 03:57
@olaservo olaservo mentioned this pull request Mar 5, 2025
@olaservo
Copy link
Member Author

Hi @jspahrsummers @jerome3o-anthropic @ashwin-ant hoping to get a review on this one - sorry it got big, a few related issues + tests are bundled together here. Thanks!

jspahrsummers
jspahrsummers previously approved these changes Mar 13, 2025
Copy link
Member

@jspahrsummers jspahrsummers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

coolkid

Just a few questions, but nothing blocking—feel free to merge at will!

Comment on lines 30 to 32
run: |
cd client
npm test
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIOLI:

Suggested change
run: |
cd client
npm test
working-directory: ./client
run: npm test

@@ -0,0 +1,18 @@
// Mock for DynamicJsonForm that only exports the types needed for tests
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when importing the actual component in tests? Just wondering if we need this file or if it's a structural choice.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wasn't actually needed anymore, I had temporarily created it while sorting out some test details but tests should run fine without it.

value={(params[key] as JsonValue) ?? {}}
value={
(params[key] as JsonValue) ??
(prop.type === "array" ? [] : {})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this use generateDefaultValue?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went back and forth on this and ended up using generateDefaultValue here, with the intention of using the tool schema defaults rather than forcing any specific defaults here.

// Create a ref to store the timeout ID
const timeoutRef = useRef<ReturnType<typeof setTimeout>>();

// Create a debounced function to update parent state
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, why?

Copy link
Member Author

@olaservo olaservo Mar 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a better clarifying comment here. I added this debounce logic to better handle validation while typing values in JSON editor fields, since validation was getting triggered either immediately or had to be triggered on some other action like switching modes. (Edited)

onChange={(e) => {
const val = e.target.value;
if (!val && !propSchema.required) {
handleFieldChange(path, undefined);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we want to use generateDefaultValue for all of these?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some fields I wanted to allow for clearing the field (i.e. setting to undefined) when its not required by the schema.
Intention is to preserve the user's ability to have an empty string input rather than forcing a default value.

Copy link
Member

@jspahrsummers jspahrsummers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@olaservo olaservo merged commit dfc9cf7 into modelcontextprotocol:main Mar 21, 2025
2 checks passed
}

if (!schema.required) {
if (schema.type === "array") return [];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@olaservo - I think this resulted in a regression. I have a schema where an option is an array that is not required. But if you specify it, it should have at least one element.

Like this:

  ids: z
    .array(z.number())
    .min(1)
    .optional()
    .describe("An optional array of ID#s to search for"),

With the change here, I don't see how you can not specify an array in the UI. This worked before, AFAICT, but broke with this change.

What do you think?

IgnacioC44 referenced this pull request in MCPJam/inspector Jun 21, 2025
Improve on tool input handling and add tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants