fix: return MCP-compliant content[] from tool handlers (clients show "no output") - #27
fix: return MCP-compliant content[] from tool handlers (clients show "no output")#27ChrisbyChA wants to merge 1 commit into
Conversation
…put')
Tool-Handler gaben rohe Paperless-API-Objekte zurueck; MCP-Clients erwarten
{content:[{type:text,text}]}. Zentraler Shim in src/index.ts normalisiert jeden
registrierten Handler. Build (tsc) regeneriert. Lokaler Patch (kein Upstream-Push).
📝 WalkthroughWalkthroughStartup now installs a compatibility shim around ChangesMCP tool response normalization
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/index.ts`:
- Around line 75-84: The text normalization in the response builder can still
produce a non-string when `JSON.stringify` returns undefined for top-level
undefined, functions, or symbols. Update the `text` assignment in `src/index.ts`
so the `return { content: [{ type: "text", text }] }` path always receives a
string, by adding a fallback to `String(result)` whenever the stringify result
is not a string. Keep the fix localized to the `text` handling block around the
`result` serialization logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
| let text: string; | ||
| try { | ||
| text = | ||
| typeof result === "string" | ||
| ? result | ||
| : JSON.stringify(result, null, 2); | ||
| } catch { | ||
| text = String(result); | ||
| } | ||
| return { content: [{ type: "text", text }] }; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the changed area and surrounding context in src/index.ts
git diff -- src/index.ts | sed -n '1,220p'
printf '\n---\n'
sed -n '1,140p' src/index.ts
printf '\n---\n'
# Read-only probe for JSON.stringify top-level edge cases and String() fallback
node - <<'JS'
const cases = [undefined, function f(){}, Symbol('s'), 1, null, {a:1}, [1,2]];
for (const value of cases) {
let serialized;
try {
serialized = JSON.stringify(value, null, 2);
} catch (e) {
serialized = `THREW: ${e && e.message}`;
}
console.log({
type: typeof value,
value: String(value),
json: serialized,
jsonType: typeof serialized,
});
}
JSRepository: nloui/paperless-mcp
Length of output: 5337
Ensure text always resolves to a string.
JSON.stringify(result, null, 2) returns undefined for top-level undefined, functions, and symbols, so this path can still emit { content: [{ type: "text", text: undefined }] } and break the MCP content contract.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/index.ts` around lines 75 - 84, The text normalization in the response
builder can still produce a non-string when `JSON.stringify` returns undefined
for top-level undefined, functions, or symbols. Update the `text` assignment in
`src/index.ts` so the `return { content: [{ type: "text", text }] }` path always
receives a string, by adding a fallback to `String(result)` whenever the
stringify result is not a string. Keep the fix localized to the `text` handling
block around the `result` serialization logic.
Problem
When used with a strict MCP client (e.g. Claude Code), every tool (
search_documents,list_tags,list_correspondents,get_document, …) returns "completed with no output" — even though the Paperless instance, token and routing are all fine and the same requests work viacurl.Root cause
The tool handlers return the raw Paperless REST object directly, e.g. in
src/tools/tags.js:But the MCP spec requires a
tools/callresult to be aCallToolResult, i.e.{ content: [{ type: "text", text }] }. A raw object has nocontent[], so spec-compliant clients render nothing. Capturedtools/callresult forlist_tags:{ "count": 14, "next": null, "previous": null, "all": [...], "results": [...] } // no content[] -> "no output"(Some lenient clients happen to display the raw object, which is why this can go unnoticed; Claude Code does not.)
Fix
A single central shim in
src/index.ts(right afternew McpServer(...)) that wraps every registered tool handler and normalizes its return value into MCPcontent[], while passing through results that are already MCP-compliant. One place, covers all current and future tools, no change to the individual tool files:Testing
Built with
tscand exercised over a raw stdio handshake (initialize→tools/call):list_tagsandsearch_documentsnow returnresult.content[0].type === "text"with the JSON payload.Source-only change (
build/is gitignored).🤖 Generated with Claude Code
Summary by CodeRabbit