fix(mcp): publish satisfiable inputSchemas for free-form JSON fields - #375
fix(mcp): publish satisfiable inputSchemas for free-form JSON fields#375syf2211 wants to merge 1 commit into
Conversation
Replace z.record() with z.json()-based helpers so fastmcp's strictJsonSchema does not emit unsatisfiable object schemas (propertyNames + additionalProperties: false with no properties). Fixes jsonOptions.schema rejecting real JSON Schema documents in schema-validating MCP clients. Fixes firecrawl#373
There was a problem hiding this comment.
2 issues found across 4 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="tests/mcp-input-schema.test.mjs">
<violation number="1" location="tests/mcp-input-schema.test.mjs:16">
P2: `child.kill()` is called without awaiting the child's exit (and stderr is never drained), so on failure paths the spawned Node server can be left running and startup errors are hidden behind the 2.5 s timeout. The sibling smoke test appends `t.after(() => stopChild(child))`, which awaits exit and escalates to SIGKILL after a timeout. Mirror that to guarantee the spawned `dist/index.js` is reaped and to surface startup errors.</violation>
<violation number="2" location="tests/mcp-input-schema.test.mjs:46">
P2: This test relies on a fixed 2500 ms wall-clock sleep and then immediately calls `child.kill()`, rather than waiting for the actual `tools/list` response. On a slow or loaded CI runner, server startup plus schema generation for all ~15 tools can exceed 2.5 s, making the test flaky (it throws 'tools/list response not found' and fails a healthy build). Send requests event-driven: await the initialize response, send `tools/list`, and resolve/kill only when the matching id===2 response arrives. This also matches the established pattern in `tests/mcp-smoke.test.mjs` (`StdioMcpClient.request` awaits each response and `stopChild` escalates SIGTERM→SIGKILL), instead of hand-rolling framing and timing.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
| } | ||
|
|
||
| async function listToolsViaStdio() { | ||
| const child = spawn('node', ['dist/index.js'], { |
There was a problem hiding this comment.
P2: child.kill() is called without awaiting the child's exit (and stderr is never drained), so on failure paths the spawned Node server can be left running and startup errors are hidden behind the 2.5 s timeout. The sibling smoke test appends t.after(() => stopChild(child)), which awaits exit and escalates to SIGKILL after a timeout. Mirror that to guarantee the spawned dist/index.js is reaped and to surface startup errors.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/mcp-input-schema.test.mjs, line 16:
<comment>`child.kill()` is called without awaiting the child's exit (and stderr is never drained), so on failure paths the spawned Node server can be left running and startup errors are hidden behind the 2.5 s timeout. The sibling smoke test appends `t.after(() => stopChild(child))`, which awaits exit and escalates to SIGKILL after a timeout. Mirror that to guarantee the spawned `dist/index.js` is reaped and to surface startup errors.</comment>
<file context>
@@ -0,0 +1,80 @@
+}
+
+async function listToolsViaStdio() {
+ const child = spawn('node', ['dist/index.js'], {
+ env: { ...process.env, FIRECRAWL_API_KEY: 'fc-test' },
+ stdio: ['pipe', 'pipe', 'pipe'],
</file context>
| child.stdin.write(`${JSON.stringify(message)}\n`); | ||
| } | ||
|
|
||
| await delay(2500); |
There was a problem hiding this comment.
P2: This test relies on a fixed 2500 ms wall-clock sleep and then immediately calls child.kill(), rather than waiting for the actual tools/list response. On a slow or loaded CI runner, server startup plus schema generation for all ~15 tools can exceed 2.5 s, making the test flaky (it throws 'tools/list response not found' and fails a healthy build). Send requests event-driven: await the initialize response, send tools/list, and resolve/kill only when the matching id===2 response arrives. This also matches the established pattern in tests/mcp-smoke.test.mjs (StdioMcpClient.request awaits each response and stopChild escalates SIGTERM→SIGKILL), instead of hand-rolling framing and timing.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/mcp-input-schema.test.mjs, line 46:
<comment>This test relies on a fixed 2500 ms wall-clock sleep and then immediately calls `child.kill()`, rather than waiting for the actual `tools/list` response. On a slow or loaded CI runner, server startup plus schema generation for all ~15 tools can exceed 2.5 s, making the test flaky (it throws 'tools/list response not found' and fails a healthy build). Send requests event-driven: await the initialize response, send `tools/list`, and resolve/kill only when the matching id===2 response arrives. This also matches the established pattern in `tests/mcp-smoke.test.mjs` (`StdioMcpClient.request` awaits each response and `stopChild` escalates SIGTERM→SIGKILL), instead of hand-rolling framing and timing.</comment>
<file context>
@@ -0,0 +1,80 @@
+ child.stdin.write(`${JSON.stringify(message)}\n`);
+ }
+
+ await delay(2500);
+ child.kill();
+
</file context>
Summary
Replace
z.record()tool-parameter schemas withz.json()-based helpers so fastmcp'sstrictJsonSchemano longer publishes unsatisfiable object schemas (propertyNames+additionalProperties: falsewith noproperties).Motivation
firecrawl_scrape's publishedjsonOptions.schemarejected every legitimate JSON Schema document in MCP clients that validate tool arguments againsttools/listinputSchemas before forwarding calls. The server accepted these calls at runtime, but schema-validating gateways saw 100% failure for JSON extraction with a schema.Fixes #373
Changes
src/mcp-json-schemas.tswith shared helpers (mcpJsonObject,mcpStringMapOptional, etc.)jsonOptions.schema, agentschema, feedbackmetadata, crawlwebhookHeaders, and monitorbodyfieldstools/listTests
npm run build— passnpm run lint— passnpm test— 69/72 pass; 3 pre-existing flaky smoke/OAuth tests fail in this environment (unrelated to this change)node --test tests/mcp-input-schema.test.mjs— passNotes
Runtime validation behavior is preserved: object-only refinements still reject arrays/null for body/metadata/schema fields, and
webhookHeadersstill requires string values at execution time.Summary by cubic
Publishes satisfiable MCP inputSchemas for free‑form JSON parameters so schema‑validating clients accept real payloads. Previously
z.record()generated unsatisfiable object schemas underfastmcp’sstrictJsonSchema; nowz.json()-based helpers describe permissive JSON objects.z.record()with helpers fromsrc/mcp-json-schemas.ts(mcpJsonObject,mcpJsonObjectOptional,mcpStringMapOptional,mcpJsonSchemaDocumentOptional).firecrawl_scrapejsonOptions.schema, agentschema, feedbackmetadata, crawlwebhookHeaders, and monitorbody.tests/mcp-input-schema.test.mjsto asserttools/listpublishes satisfiable schemas via stdio.webhookHeadersstill requires string values).Written for commit a46eab0. Summary will update on new commits.