[BUG] claude.ai connector ingestion silently drops any tool with inputSchema >16,384 bytes; refresh path applies no limit
Summary
When a remote MCP connector is added in Claude Desktop (custom connector, streamable HTTP), the connect-time tool enumeration silently drops any tool whose serialized inputSchema exceeds 16,384 bytes. Measured by bisection with a controlled server (reproducer below):
- A tool with a 16,384-byte schema loads; 16,385 bytes does not (inclusive boundary, exactly 16 KiB).
- The limit applies to the schema alone: a tool whose total entry was 18,100 bytes wrapping a 15,000-byte schema loads fine.
- It is bytes, not tokens: padding is a single repeated character (tokenizes to almost nothing) and still triggers the drop.
- It is per-tool, not total: with tools deliberately named so the largest sorts first, the large first tool is dropped while smaller later tools load; surviving sets totaling 50KB+ load without issue.
The two defects
- The drop is completely silent. No warning in the connector panel, no log, no error anywhere. Our production 4-tool CRUD server showed only its one small tool for a week and was serially misdiagnosed as a server bug, an auth/scope problem, and an Anthropic API change before we bisected the cause.
- The refresh path does not enforce the limit. Clicking refresh on the same connector loads tools with 22KB+ schemas. Fresh add and refresh disagreeing makes the behavior look nondeterministic: connect shows 1 tool, refresh shows 4, next fresh add shows 1 again.
Downstream impact: the model confabulates around the silent failure
Because nothing signals that tools were dropped, Claude invents explanations. Two independent sessions in our org, uncoached:
- One session explained the missing
gettool as "a quirk of the tool search/indexing in this session." - Another invented "a known recurring quirk" plus "an established workaround: calling the MCP server directly via the Anthropic API inside a widget artifact" — not a real capability; the widget produced nothing, and the lookup only worked after a manual refresh surfaced the real tool.
The silent drop doesn't just hide capability — it manufactures false confidence downstream.
Reproduction (~10 minutes)
Minimal Go server serving tools whose schemas are padded to exact byte sizes; tools are named after their sizes so the connector panel reads itself.
<details>
<summary>go.mod + main.go (complete reproducer)</summary>
module capbisect
go 1.25.0
require github.com/modelcontextprotocol/go-sdk v1.4.1
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strings"
"github.com/google/jsonschema-go/jsonschema"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
var ladder = []struct {
name string
schemaTarget int // exact serialized size of inputSchema, bytes
}{
{"a_control_400", 400},
{"b_s4096", 4096},
{"c_s8192", 8192},
{"d_s12288", 12288},
{"e_s16300", 16300},
{"f_s16384", 16384}, // the boundary: loads
{"g_s16385", 16385}, // one byte over: silently dropped
{"h_s17000", 17000},
{"i_s20000", 20000},
{"j_s40000", 40000},
}
func makeTool(name string, schemaTarget int) *mcp.Tool {
tool := &mcp.Tool{
Name: name,
Description: fmt.Sprintf("probe %s: inputSchema padded to exactly %d bytes.", name, schemaTarget),
InputSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"note": {Type: "string", Description: "pad:"},
},
},
}
for i := 0; i < 4; i++ {
data, err := json.Marshal(tool.InputSchema)
if err != nil {
log.Fatal(err)
}
diff := schemaTarget - len(data)
if diff <= 0 {
break
}
schema := tool.InputSchema.(*jsonschema.Schema)
schema.Properties["note"].Description += strings.Repeat("x", diff)
}
return tool
}
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "18081"
}
handler := mcp.NewStreamableHTTPHandler(func(r *http.Request) *mcp.Server {
srv := mcp.NewServer(&mcp.Implementation{Name: "cap-bisect", Version: "1.0.0"}, nil)
for _, l := range ladder {
tool := makeTool(l.name, l.schemaTarget)
name := l.name
mcp.AddTool(srv, tool, func(ctx context.Context, req *mcp.CallToolRequest, in struct {
Note string `json:"note,omitempty"`
}) (*mcp.CallToolResult, any, error) {
return &mcp.CallToolResult{Content: []mcp.Content{
&mcp.TextContent{Text: fmt.Sprintf(`{"tool":%q,"ok":true}`, name)},
}}, nil, nil
})
}
return srv
}, &mcp.StreamableHTTPOptions{Stateless: true})
for _, l := range ladder {
tool := makeTool(l.name, l.schemaTarget)
data, _ := json.Marshal(tool.InputSchema)
log.Printf("%-15s schema=%6d bytes", l.name, len(data))
}
log.Printf("cap-bisect listening on :%s/mcp", port)
log.Fatal(http.ListenAndServe(":"+port, handler))
}
</details>
Steps:
go build && ./capbisect— startup log prints each tool's exact schema byte size (ground truth).- Expose via any tunnel, e.g.
cloudflared tunnel --url http://<LAN-IP>:18081(LAN IP, not localhost — the SDK's DNS-rebinding protection 403s loopback servers receiving a public Host header). Verifytools/listthrough the tunnel returns all 10 tools — the server side is provably correct. - Claude Desktop → Settings → Connectors → Add custom connector (must be a fresh add; refresh bypasses the cap) → open the connector's Tool permissions panel.
- Observed: panel shows exactly 6 tools (
a_control_400…f_s16384);g_s16385and everything larger is absent.f_s16384andg_s16385differ by one byte. - Click refresh: all 10 appear.
Environment
- Claude Desktop, Windows, current auto-updated build (July 2026)
- Custom connector, streamable HTTP, no auth
- Also reproduced through an org-managed connector against our production MCP server (only the one tool with a 3.9KB schema loaded; tools at 25–104KB dropped, across every server version we've ever shipped)
Related public reports (same silent-cap pattern, different layers)
- anthropics/claude-code#26406 — Desktop 1.1.3189 (Windows): entire
tools/listdropped above ~2,500 bytes total on the local stdio path; distinct bug (whole-list vs per-tool, IPC vs cloud ingestion), same silence. Closed as duplicate, no documented fix. - bgheneti/Amazing-Marvin-MCP#11 — Desktop shows 5 of 28 tools; server verified correct via MCP Inspector; log shows
[52653 chars truncated]; unresolved. - anthropics/claude-code#31302 — complex tool schemas silently truncated, stripped parameters silently dropped from outgoing calls; closed as duplicate.
- modelcontextprotocol discussion #537 — Desktop silently caps servers at 100 tools.
The pattern across all of these: silent size/count caps discovered only by users binary-searching in the dark. This report adds the first exact constant and a self-contained reproducer.
Requests
- Surface dropped tools in the connector UI — name, reason, measured size vs limit.
- Document the limit so MCP server authors can budget for it (JSON schemas are routinely 5–10× larger than authors estimate; three separate engineers here "pared down" schemas that were still over the invisible line).
- Make connect and refresh enforce the same rule, whichever rule is chosen.