What
The MemOS MCP server (src/memos/api/mcp_serve.py, MOSMCPServer) registers tools with @self.mcp.tool() and no authentication on any of them. Several are admin-grade or destructive, and one reads arbitrary server files. The server runs over stdio/http/sse (_run_m :570-580; documented as python src/memos/api/mcp_serve.py --transport http --host ... --port ...), so when an operator runs it for an MCP client and it is reachable beyond loopback, all of the following are unauthenticated and remote:
create_user(user_id, user_name, role) (mcp_serve.py:168-187) — role is caller-supplied; user_role = UserRole.ADMIN if role.upper() == "ADMIN" (:186). A caller mints an ADMIN user.
delete_all_memories(cube_id, user_id) (:421) — wipes a cube's memories; user_id caller-supplied (cross-user).
register_cube / unregister_cube (:220 / :251) — caller-supplied cube_id/user_id.
add_memory(..., doc_path, user_id) (:308) — doc_path flows to mos_core.add(doc_path=…) → _get_all_documents (mem_os/core.py:233) which Path(doc_path).rglob("*")s an arbitrary path and reads every .txt/.pdf/.json/.md/.ppt(x) under it; user_id caller-supplied.
There is no verify_api_key / auth dependency on any MCP tool (contrast the REST admin router, which uses Depends(verify_api_key)).
Checked at 93e4082 (main HEAD).
How to reproduce
python src/memos/api/mcp_serve.py --transport http --host 127.0.0.1 --port 8000 &
# MCP tool calls with no credentials:
# create_user(user_id="attacker", user_name="a", role="ADMIN") -> ADMIN user created
# delete_all_memories(cube_id="victim_cube", user_id="victim") -> cross-user wipe
# add_memory(doc_path="/etc", cube_id="default", user_id="x") -> server reads files under /etc
The tool handler signatures in mcp_serve.py show no auth gate; role, user_id, cube_id, and doc_path are plain caller-supplied parameters passed straight to mos_core.
Impact / scope
Same root issue as the /product REST surface (unauth + client-supplied identity), on the MCP entry point, plus the ability to create an ADMIN user and delete all memories of any cube. Severity scales with how the MCP server is exposed (loopback vs a published http/sse port).
Suggested change
- Gate MCP tools behind the same auth as the REST surface (or document/bind the MCP server as loopback-only by default).
- Do not accept
role=ADMIN from an unauthenticated tool call; derive roles from the authenticated principal.
- Derive
user_id/cube_id from auth, not caller-supplied fields.
- Clamp
doc_path to an allowlisted documents root (same fix as the REST surface) rather than rglob-ing an arbitrary path.
Happy to open a PR aligning the MCP tools with the REST auth.
What
The MemOS MCP server (
src/memos/api/mcp_serve.py,MOSMCPServer) registers tools with@self.mcp.tool()and no authentication on any of them. Several are admin-grade or destructive, and one reads arbitrary server files. The server runs overstdio/http/sse(_run_m:570-580; documented aspython src/memos/api/mcp_serve.py --transport http --host ... --port ...), so when an operator runs it for an MCP client and it is reachable beyond loopback, all of the following are unauthenticated and remote:create_user(user_id, user_name, role)(mcp_serve.py:168-187) —roleis caller-supplied;user_role = UserRole.ADMIN if role.upper() == "ADMIN"(:186). A caller mints an ADMIN user.delete_all_memories(cube_id, user_id)(:421) — wipes a cube's memories;user_idcaller-supplied (cross-user).register_cube/unregister_cube(:220/:251) — caller-suppliedcube_id/user_id.add_memory(..., doc_path, user_id)(:308) —doc_pathflows tomos_core.add(doc_path=…)→_get_all_documents(mem_os/core.py:233) whichPath(doc_path).rglob("*")s an arbitrary path and reads every.txt/.pdf/.json/.md/.ppt(x)under it;user_idcaller-supplied.There is no
verify_api_key/ auth dependency on any MCP tool (contrast the REST admin router, which usesDepends(verify_api_key)).Checked at
93e4082(mainHEAD).How to reproduce
The tool handler signatures in
mcp_serve.pyshow no auth gate;role,user_id,cube_id, anddoc_pathare plain caller-supplied parameters passed straight tomos_core.Impact / scope
Same root issue as the
/productREST surface (unauth + client-supplied identity), on the MCP entry point, plus the ability to create an ADMIN user and delete all memories of any cube. Severity scales with how the MCP server is exposed (loopback vs a publishedhttp/sseport).Suggested change
role=ADMINfrom an unauthenticated tool call; derive roles from the authenticated principal.user_id/cube_idfrom auth, not caller-supplied fields.doc_pathto an allowlisted documents root (same fix as the REST surface) rather thanrglob-ing an arbitrary path.Happy to open a PR aligning the MCP tools with the REST auth.