anthropics / anthropics/claude-code

MCP tool schema loses type/validation info between server and Claude's tool-calling path

Open
#94,890 0 comments 0 reactions 0 assignees View on GitHub
area:mcp bug platform:windows
Dominant language
Python
Stars
145k
Forks
23.1k
PR merge metrics
PR metrics pending

Description

# Bug Report: MCP tool schema loses type/validation info between server and Claude's tool-calling path

## Summary

Calling the `aicontextmcp` MCP server's `project.bootstrap` tool from a Claude session in the affected integration path fails with a validation error (`expected nonoptional, received undefined`) when `projectId` and `remote` are omitted from the call — even though the server's own `tools/list` response marks both fields as optional (not present in any `required` array) with `"type": ["string", "null"], "default": null`.

A direct stdio probe against the deployed server binary (bypassing that integration path entirely) returned the correct, fully-typed schema. Comparing that raw schema against what the affected path exposes to the calling model (via its `ToolSearch` tool) shows several fields of the JSON Schema are missing in the exposed copy. This suggests information loss somewhere between the server's `tools/list` response and the schema used for validation — the exact component responsible has not been confirmed.

## Environment

- **Client identity:** Unconfirmed precisely. The server is configured in `claude_desktop_config.json`, which is read by the Claude desktop app, and this session was described to the model as running "inside the Claude desktop app (Code tab)." The `2.1.268` version below was obtained by running `claude --version` from this session's Bash tool — that reports the Claude Code CLI binary's version, which is not verified to be the same binary/build actually acting as the MCP client for this desktop session (the desktop app could bundle its own Claude Code build separately from any CLI install on PATH). Anyone filing this should confirm the exact client build from the desktop app's own version/about screen rather than relying on the CLI version below.
- **Claude Code CLI version (via `claude --version`, provenance caveat above):** 2.1.268
- **MCP server:** `aicontextmcp`
- **Server binary:** `D:\Astra\mcp\runtime\AIContextMCP\AIContextMCP.Server.exe`
- **Server FileVersion:** 1.0.0.0
- **Server ProductVersion:** `1.0.0+381f7cc04ab40633102d3f39ba27c3d9bf986706`
- **Launch config** (`claude_desktop_config.json`):
```json
"aicontextmcp": {
"command": "D:\\Astra\\mcp\\runtime\\AIContextMCP\\AIContextMCP.Server.exe",
"args": [],
"env": {
"AIContextMCP_DatabasePath": "D:\\Astra\\mcp\\data\\AIContextMCP\\AIContextMCP.db",
"AIContextMCP_ArtifactRoot": "D:\\Astra\\mcp\\data\\AIContextMCP\\artifacts",
"AIContextMCP_ApprovedRepositoryRoots__0": "D:\\"
}
}
```
- **Transport:** stdio, newline-delimited JSON-RPC (confirmed via direct probe — NOT Content-Length/LSP framing)

## Schema comparison

### Raw schema, `project.bootstrap.inputSchema`, from a direct stdio `tools/list` call against the server binary:

```json
{
"type": "object",
"properties": {
"requestId": {
"type": ["string", "null"],
"default": null,
"description": "Nonempty replay ID; required when register=true."
},
"projectId": { "type": ["string", "null"], "default": null },
"repositoryPath": {
"type": ["string", "null"],
"default": null,
"description": "Repository path to inspect or register."
},
"remote": { "type": ["string", "null"], "default": null },
"includeWorkingTree": {
"type": "boolean",
"default": true,
"description": "Include current working-tree state; defaults to true."
},
"register": {
"type": "boolean",
"default": false,
"description": "Register the path; defaults to false."
}
},
"additionalProperties": false,
"allOf": [
{
"if": {
"required": ["register"],
"properties": { "register": { "const": true } }
},
"then": {
"required": ["requestId"],
"properties": { "requestId": { "type": "string", "minLength": 1 } }
}
}
]
}
```

Note: `projectId` and `remote` are optional here specifically because they never appear in any `required` array (including the nested one inside `allOf`) — not merely because they carry a `default`. The schema's only `required` entries are conditional: `register` (to evaluate the `if`) and `requestId` (only `then`, when `register === true`).

### Schema Claude Code exposes for the same tool (via `ToolSearch`, then confirmed by the live `-32602` validation error):

```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"includeWorkingTree": { "default": true, "type": "boolean" },
"projectId": { "default": null },
"register": { "default": false, "type": "boolean" },
"remote": { "default": null },
"repositoryPath": { "default": null },
"requestId": { "default": null }
}
}
```

### Fields present in the raw schema but absent from the exposed version:
- `"type": ["string", "null"]` on `requestId`, `projectId`, `repositoryPath`, `remote` — a type constraint, not just documentation
- `"additionalProperties": false` — a structural constraint on the whole object
- The entire `allOf` conditional block (the conditional-requirement logic: `requestId` required and non-empty only when `register: true`)
- `description` text on all four fields that had one in the raw schema: `requestId`, `repositoryPath`, `includeWorkingTree`, and `register` (`projectId` and `remote` never had a `description` in the raw schema to begin with)

Together, `type`, `additionalProperties`, and `allOf` are all constraints that shape what input is valid — not just the `allOf` block alone.

## Reproduction

**Call (omitting `projectId` and `remote`, which the raw schema allows):**

`requestId` must be `:` (e.g. `1789598061:52a8e167-7cd2-4302-a29a-a7efcd80a01d`) — this stricter format is enforced by the server's storage layer and is not documented anywhere in the schema's `description` for `requestId` (which only says "Nonempty replay ID"). Generate the two parts separately (e.g. `date +%s` for epoch seconds, `[guid]::NewGuid()` for the UUID, lowercased) and join with `:`.

```
project_bootstrap({
repositoryPath: "D:\\TheCertMaster",
includeWorkingTree: true,
register: true,
requestId: ":"
})
```

**Result — rejected in the affected integration path; the responsible component is unconfirmed:**
```
MCP error -32602: Input validation error: Invalid arguments for tool project.bootstrap: [
{
"code": "invalid_type",
"expected": "nonoptional",
"path": ["projectId"],
"message": "Invalid input: expected nonoptional, received undefined"
},
{
"code": "invalid_type",
"expected": "nonoptional",
"path": ["remote"],
"message": "Invalid input: expected nonoptional, received undefined"
}
]
```

This error's shape (`"expected nonoptional, received undefined"`) does not correspond to anything in the raw JSON Schema shown above — there is no `required` entry for `projectId` or `remote` in the server's own schema. The wording is consistent with a Zod-style validator being reconstructed somewhere in the affected path from the (type-stripped) schema, which appears to treat "no `type` declared" as "key must be present" rather than as JSON Schema's actual meaning (unconstrained/any, including absence). Which component performs this reconstruction is not confirmed.

**Workaround attempted (in the affected integration path):** supplying `projectId`/`remote` with a value instead of omitting them. Empty string and the literal text `null` both pass the omission check described above, but then fail server-side (`InvalidInput`, `"project id is invalid."` / `"remote is invalid."`) — because neither is the JSON `null` the server actually wants, and no mechanism was found in this path for transmitting a literal JSON `null` for an untyped scalar parameter. No successful registration call has been made through this path as of this report — see the next section for a successful registration via a different path.

## Independent verification

A direct stdio probe was run against the same deployed server binary (`AIContextMCP.Server.exe`), using an isolated temporary database, bypassing Claude Code's tool-calling layer entirely:
- Confirmed transport: newline-delimited JSON-RPC (a prior attempt using Content-Length/LSP framing was rejected by the server with per-message `-32600 Invalid JSON-RPC request` errors)
- Captured the full raw `tools/list` response, saved at the time to `C:\Users\mez\AppData\Local\Temp\aicontext-schema-2c9dc8e6bd194db99f98b76d87f41d11\tools-list.json`
- This raw response is the source for the "raw schema" section above

No production data or configuration was changed by this probe (isolated temp database).

### Additional evidence: successful registration via direct probe, with `projectId`/`remote` omitted or null

Separately from the schema capture above, a direct-probe test (also against the isolated temporary database, not the production `AIContextMCP.db`) was reported to have successfully exercised `project.bootstrap` with `register: true` and `projectId`/`remote` either omitted or sent as JSON `null`, where the correctly-formatted-path integration attempt above could not. This is included as evidence that the server itself accepts the omission the raw schema describes, and that the failure reproduced above is specific to the affected integration path rather than to the server's actual contract.

This specific test's raw request/response transcript was not captured into this report — only the outcome was relayed. Anyone filing this bug should reproduce and attach that transcript directly (same isolated-temp-database method as the `tools/list` capture above) rather than relying on this secondhand description.

## Open questions / not yet established

- **Which component strips the schema fields?** Not confirmed. Candidates include the MCP client/transport layer Claude Code uses, Claude Code's own tool-indexing/`ToolSearch` layer, or an intermediate cache — no component has been isolated as the specific cause.
- **Whether an alternate schema shape on the AIContextMCP side would avoid the loss** (e.g., a differently structured nullable-field representation) is untested. This is a possible mitigation, not a confirmed fix, and doesn't address the underlying transformation issue if it turns out to be lossy for other schema shapes too.

## Suggested filing

File against Claude Code: https://github.com/anthropics/claude-code/issues, including this report's schema diff, the reproduction steps, and the open question about which layer owns the transformation.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by tracing the MCP tools/list response from the client or transport path into ToolSearch, using claude_desktop_config.json and the reported schema diff as entry points. Compare the raw and exposed schemas, then reproduce the -32602 validation error; done means the exposed tool retains the relevant types, constraints, conditional requirements, and descriptions.

Written by the indexing model from the issue text.

Assessment

Tech stack
json, python
Domain
api, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.