Skip to content

MCP: tools with {memo} path param reject canonical memos/{id} resource names (404); error results violate the declared outputSchema #6107

Description

@harpchad

Pre-submission Checklist

  • I have searched existing issues and confirmed this has not already been reported
  • I have reproduced this bug on the latest Memos release, current main, or the demo site, or this report is specifically about upgrading to the latest release
  • This is a bug report, not a question or configuration request

Affected Area

API (built-in MCP server)

Reproduced On

Demo site (v0.30.0-rc.1) and self-hosted v0.30.0-rc.2

Memos Version Tested

v0.30.0-rc.1 (demo.usememos.com) and v0.30.0-rc.2 (self-hosted Docker)

Previous Version

No response

Environment Details

This is the rewritten OpenAPI-driven MCP server from #6026. Reproduced unauthenticated against https://demo.usememos.com/mcp (Streamable HTTP) with plain curl — no MCP client involved.

Related issues: distinct from #6022 / #6037 / #6049 (list tools returning bare arrays), which are fixed in this build.

Reproduction Frequency

Always

Steps to Reproduce

SID=$(curl -sS -D - -o /dev/null -X POST https://demo.usememos.com/mcp \
  -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"repro","version":"1.0"}}}' \
  | grep -i mcp-session-id | tr -d '\r' | awk '{print $2}')

mcp() { curl -sS -X POST https://demo.usememos.com/mcp \
  -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
  -H "Mcp-Session-Id: $SID" -d "$1"; }

# 1. list_memos returns canonical resource names:
mcp '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"memo_list_memos","arguments":{"pageSize":1}}}'
#    -> "name": "memos/webclipper0001"

# 2. Feed that name back to get_memo:
mcp '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"memo_get_memo","arguments":{"memo":"memos/webclipper0001"}}}'
#    -> isError: true, structuredContent: {"error":{"message":"404 Not Found: Not Found"}}

# 3. Same call with the bare ID succeeds:
mcp '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"memo_get_memo","arguments":{"memo":"webclipper0001"}}}'

The same full-name → 404 / bare-ID → success behavior applies to memo_update_memo, memo_delete_memo, memo_create_memo_comment, and memo_list_memo_comments (verified update/delete on a self-hosted v0.30.0-rc.2 instance with a PAT; for memo_update_memo the 404 means the PATCH is silently never applied).

Current Behavior

1. Resource-name inconsistency. Every tool that maps to a /api/v1/memos/{memo} path substitutes the memo argument directly into the URL, so only bare IDs work (memos/<id> becomes /api/v1/memos/memos/<id> → 404). But the API returns name: "memos/<id>" in every memo object, and the tool's input schema describes the param only as "The memo id.". An MCP client (human or LLM) that round-trips the name it received from memo_create_memo/memo_list_memos gets a 404 on every follow-up operation. For memo_update_memo this is destructive-adjacent: the edit is silently lost.

2. Error results violate the declared outputSchema. On REST failure the tool returns structuredContent: {"error": {"message": "..."}}, while e.g. memo_get_memo declares an outputSchema with required: ["state","content","visibility"]. Spec-strict clients (Claude Code, LiteLLM, ChatGPT connector) reject the response with a schema-validation error (data must have required property 'state'…), masking the underlying 404 and making the tools appear completely broken.

Expected Behavior

  • Tools accept the canonical memos/{id} resource name (ideally both forms: strip a memos/ prefix before path substitution), matching the name values the API returns.
  • On failure, tools either omit structuredContent (relying on isError + text content, per MCP spec) or declare an output schema that admits the error shape — so strict clients surface the real error message.

Workarounds: pass bare IDs (strip the memos/ prefix) for any {memo} path parameter; read via memo_list_memos when a get fails validation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions