inspector icon indicating copy to clipboard operation
inspector copied to clipboard

Structured form not rendering for complex input objects in v0.10.2 (regression from v0.9.0)

Open ramonduraes opened this issue 9 months ago β€’ 12 comments

🐞 Structured form not rendering for complex input in v0.10.2 (regression)

Affected Version: MCP Inspector v0.10.2
Previous Version: v0.9.0

Image Image

Tool: order.add
Transport: SSE
Type: Regression


βœ… Expected Behavior

In version 0.9.0, selecting the order.add tool shows a structured form based on the input schema, including:

  • Customer Name (text)
  • Customer Tax ID (text)
  • Items (array of objects with fields: product name, quantity, unit price)
  • Total (number)

Each item in the list could be added dynamically using an Add Item button.


❌ Actual Behavior in 0.10.2

The form is no longer generated. Only a raw JSON editor appears:

{}

Users now have to manually write the full JSON structure, including nested arrays.


πŸ“¦ Example Input JSON

{
  "customerName": "John Doe",
  "customerTaxId": "123-45-6789",
  "items": [
    {
      "productName": "Laptop",
      "quantity": 1,
      "unitPrice": 1500.00
    },
    {
      "productName": "Mouse",
      "quantity": 2,
      "unitPrice": 25.00
    }
  ],
  "total": 1550.00
}

πŸ“Œ Impact

  • Makes the Inspector harder to use for tools with complex input
  • Increases chance of user error
  • Breaks a feature that worked in v0.9.0

πŸ”Ž Notes

This issue seems related to tools that have nested objects or complex schemas.
In v0.9.0, the structured form helped fill in fields quickly.
In v0.10.2, the feature is no longer present.


πŸ“· Please see attached screenshot from v0.9.0 showing the expected form layout.

Thanks for the awesome tool!

ramonduraes avatar Apr 18 '25 21:04 ramonduraes

@ramonduraes can you please share the server you’ve used above?

kavinkumar807 avatar Apr 19 '25 09:04 kavinkumar807

Problem Reproduction Steps

This sample project exposes a tool named order.add, which accepts a structured input parameter of type CreateOrderInput.
Inside this class, there's a nested list of OrderItemInput representing the items of the order.

Here’s the relevant implementation:

[McpServerToolType]
[Description("Creates a customer order with structured items.")]
public class CreateOrderTool 
{
    public string Name => "CreateOrder";
    public string Description => "Creates a customer order with structured items.";

    [McpServerTool(Name = "order.add"), Description("Creates a new order.")]
    public Task<object> ExecuteAsync(CreateOrderInput input, CancellationToken cancellationToken = default)
    {
        var result = new
        {
            message = $"βœ… Order from {input.CustomerName} ({input.CustomerTaxId}) received with {input.Items.Count} items. Total: ${input.Total}"
        };

        return Task.FromResult<object>(result);
    }
}

Input Models

public class CreateOrderInput
{
    [JsonPropertyName("customerName")]
    [Description("Full name of the customer placing the order.")]
    public string CustomerName { get; set; } = string.Empty;

    [JsonPropertyName("customerTaxId")]
    [Description("Tax number of the customer.")]
    public string CustomerTaxId { get; set; } = string.Empty;

    [JsonPropertyName("items")]
    [Description("List of items included in the order.")]
    public List<OrderItemInput> Items { get; set; } = new();

    [JsonPropertyName("total")]
    [Description("Total monetary value of the entire order.")]
    public decimal Total { get; set; }
}

public class OrderItemInput
{
    [JsonPropertyName("productId")]
    [Description("Unique identifier of the product.")]
    public string ProductId { get; set; } = string.Empty;

    [JsonPropertyName("quantity")]
    [Description("Quantity of the product to be ordered.")]
    public int Quantity { get; set; }

    [JsonPropertyName("price")]
    [Description("Unit price of the product.")]
    public decimal Price { get; set; }
}

How to Reproduce

  1. Install .NET 9 SDK
    https://dotnet.microsoft.com/en-us/download/dotnet/9.0

  2. Clone the project

    git clone https://github.com/ramonduraes/mcp.git
    
  3. Navigate to the appserver folder and run it

    cd mcp/appserver
    dotnet run
    
  4. In a separate terminal, run MCP Inspector v0.9.0 to list tools => (Transport Type=SSE; URL=http://localhost:5000/sse)

   npx @modelcontextprotocol/[email protected]
  1. Then run MCP Inspector v0.10.2 and list tools again => (Transport Type=SSE; URL=http://localhost:5000/sse)

    npx @modelcontextprotocol/[email protected]
    

πŸ§ͺ Compare the tool discovery results between both versions β€” especially how nested input structures like OrderItemInput are processed.

Image Image

ramonduraes avatar Apr 20 '25 14:04 ramonduraes

@ramonduraes thanks will check with this

kavinkumar807 avatar Apr 20 '25 17:04 kavinkumar807

@kavinkumar807 Did you confirm the scenario?

ramonduraes avatar Apr 23 '25 19:04 ramonduraes

@ramonduraes completely occupied this week, will check this weekend

kavinkumar807 avatar Apr 24 '25 05:04 kavinkumar807

TheuseEffect code changes in ToolsTab.tsx as part of the PR #284

Image

causes the issue where the params has empty values after getting mapped using generateDefaultValue()

I've gone through the PR conversation where it is mentioned that for simple json form view will be available and for complex one the json will be available but for the object

{
    "customerName": {
        "description": "Full name of the customer placing the order.",
        "type": "string"
    },
    "customerTaxId": {
        "description": "Tax number of the customer.",
        "type": "string"
    },
    "items": {
        "description": "List of items included in the order.",
        "type": "array",
        "items": {
            "type": "object",
            "properties": {
                "productId": {
                    "description": "Unique identifier of the product.",
                    "type": "string"
                },
                "quantity": {
                    "description": "Quantity of the product to be ordered.",
                    "type": "integer"
                },
                "price": {
                    "description": "Unit price of the product.",
                    "type": "number"
                }
            },
            "additionalProperties": false,
            "required": [
                "productId",
                "quantity",
                "price"
            ]
        }
    },
    "total": {
        "description": "Total monetary value of the entire order.",
        "type": "number"
    }
}

I've got empty object value from generateDefaultValue() function

Any thoughts on this @cliffhall @olaservo @max-stytch ?

kavinkumar807 avatar Apr 27 '25 18:04 kavinkumar807

In https://github.com/modelcontextprotocol/inspector/pull/284 we did eliminate the form for complex objects because it was not always possible to render the form correctly, nor was it possible to detect when it could so that we could display the JSON form instead. The decision was to show the JSON form for anything but the simplest objects (this is not a user facing app, it's a developer tool, remember). We can revisit this. @max-stytch from your work on that branch, is there a happy medium you can see?

cliffhall avatar Apr 28 '25 19:04 cliffhall

In the inspector I'm seeing errors about missing input even when the inputs are optional (i.e., omitted from the required array on the input schema):

Image

MCP error -32602: Invalid arguments for tool grep_record:

[
  {
    "code": "invalid_type",
    "expected": "number",
    "received": "null",
    "path": [
      "page_size"
    ],
    "message": "Expected number, received null"
  },
  {
    "code": "invalid_type",
    "expected": "number",
    "received": "null",
    "path": [
      "page"
    ],
    "message": "Expected number, received null"
  }
]

Definitions showing that page and page_size are optional:

{
  "tools": [
    {
      "name": "grep_record",
      "inputSchema": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string",
            "minLength": 1,
            "description": "The text string or JavaScript-style regular expression to search for (case-insensitive). Example: 'heart attack|myocardial infarction|mi'. Best for finding specific text/keywords or variations across *all* record parts (FHIR+notes). Use regex with `|` for related terms (e.g., `'diabetes|diabetic'`)."
          },
          "resource_types": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Optional list to filter the search scope. Supports FHIR resource type names (e.g., \"Patient\", \"Observation\") and the special keyword \"Attachment\".\n        Behavior based on the list:\n        - **If omitted or an empty list is provided:** Searches EVERYTHING - all FHIR resources and all attachment plaintext.\n        - **List contains only FHIR types (e.g., [\"Condition\", \"Procedure\"]):** Searches ONLY the specified FHIR resource types AND the plaintext of attachments belonging *only* to those specified resource types.\n        - **List contains only [\"Attachment\"]:** Searches ONLY the plaintext content of ALL attachments, regardless of which resource they belong to.\n        - **List contains FHIR types AND \"Attachment\" (e.g., [\"DocumentReference\", \"Attachment\"]):** Searches the specified FHIR resource types (e.g., DocumentReference) AND the plaintext of ALL attachments (including those not belonging to the specified FHIR types)."
          },
          "resource_format": {
            "type": "string",
            "enum": [
              "plaintext",
              "json"
            ],
            "default": "plaintext",
            "description": "Determines the output format for matching FHIR *resources*. 'plaintext' (default): Shows the rendered plaintext representation of the resource. 'json': Shows the full FHIR JSON of the resource. Matching *attachments* always show plaintext snippets."
          },
          "page_size": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "description": "Number of hits to display per page (1-50). Defaults to 50."
          },
          "page": {
            "type": "integer",
            "minimum": 1,
            "description": "Page number to display. Defaults to 1."
          }
        },
        "required": [
          "query"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
 ...

jmandel avatar Apr 29 '25 02:04 jmandel

In the inspector I'm seeing errors about missing input even when the inputs are optional (i.e., omitted from the required array on the input schema):

I'm not certain this is the right issue for this report. You may want to open a separate issue if you are still having the problem with the latest version of the inspector.

cliffhall avatar Apr 29 '25 15:04 cliffhall

Sorry for the delayed response - coming back from PTO and playing catch-up. It sounds like we are setting null for the default value sometimes when we should be setting undefined. Previously that wasn't an issue using the complex input form because the form itself would coerce those values to the right type (e.g. '' or 1).

@max-stytch from your work on that branch, is there a happy medium you can see?

This particular schema has a nested array property. We could play around with allowing arrays of simple types which would restore the complex object input. Since it worked before I am leaning towards that way.

There also seems to be another issue where generateDefaultValue is returning a default value of null for fields that do not accept null - it should be returning undefined instead? I need to tinker more, will hopefully get something out soon.

max-stytch avatar May 05 '25 23:05 max-stytch

Sorry for the delay - life has a way of getting in the way πŸ™ƒ I have opened https://github.com/modelcontextprotocol/inspector/pull/434 to bring back the nested array rendering. The generateDefaultValue bug is a bit more insidious - it turns out that the JsonSchemaType defined in the project is wrong: there is no required: boolean field on JSON schema objects. required is a string[] defined on the parent Object type - https://json-schema.org/understanding-json-schema/reference/object#required

This means that all the logic in this repo around prop.required is likely broken which will require a more comprehensive overhaul since the required information is not stored on prop at all - it is stored on the parent!

I believe this is also the root cause of issues like #406

max-stytch avatar May 24 '25 21:05 max-stytch

Thanks for digging into this @max-stytch ! I know there's been a lot of back and forth on the behavior of the form and its been something I kept meaning to get back to as well. Will take a look at the PR.

For the misplacement of required I can work on opening a PR to address that sometime over the next few days.

olaservo avatar May 24 '25 21:05 olaservo

The decision was to show the JSON form for anything but the simplest objects (this is not a user facing app, it's a developer tool, remember). We can revisit this.

Good discussion: anecdotally, as a developer, this definitely makes it abit harder to test my changes. For example, with complex input schemas to tools I want to test like:

Image

I end up having to manually enter the JSON or copy paste stuff. I agree that it's probably not worth making the UI so clean that any end user could test it but I've found this makes showing stakeholders (non-technical people) how MCP works much more difficult.

jpmcb avatar Jun 08 '25 15:06 jpmcb

Hey guys, what is the current state of this issue? As I can see, the linked PR https://github.com/modelcontextprotocol/inspector/pull/434 was closed and superseded(?) by https://github.com/modelcontextprotocol/inspector/pull/480 but the issue is still present for me in the latest version 0.16.2 of the inspector. For cases with nested structures this would greatly improve the UX.

ln-12 avatar Aug 06 '25 14:08 ln-12

Hi @ln-12! Thanks for the ping on this. I've reopened #434 (see my comment).

cliffhall avatar Aug 07 '25 21:08 cliffhall