最新资讯
dev.to 我们在 DolphinBench 上测试记忆检索:纯关键词搜索暴露了自身回归 dev.to 别再把 Agent 写成 while 循环,把它建模为状态机 bloomberg.com Rightmove 与 Scout24 借 AI 产品应对低迷房市并上调展望 stackoverflow.blog 欢迎来到芯片时代:半导体制造、AI 数据中心与芯片设计的未来 www.youtube.com AI Agent 总犯同样的错:CentralAgent AI 将记忆重构为经验系统 pub.towardsai.net Netflix 科学家建议重采样 LLM 评判:5 次仅将误差棒缩小 13% pub.towardsai.net Jev 作为 LLM 前的静默层:构建提示感知的模型路由器 reddit.com OpenAI 称 Astra 仅满足 10% 安全任务标准 dev.to FreeToken:在自有 GPU 上运行前沿 MoE 模型及其内存需求 dev.to 当 AI Agent 说“完成”时,究竟完成了什么? dev.to 部署 Underdog Bark 3.0 Pro:逐字转写、自定义后端与分块设置 dev.to 为艰难对话构建 AI:五个 Agent、一条安全正则与一位盲评监督者
多元动态
登录 注册
大模型 dev.to · @Edward Izgorodin

你的 Memory Server 没写 destructiveHint,按规范即视为 true

原文 我的收藏

文章讨论 MCP 规范中注解的默认语义:未声明注解的工具在规范下被视为 destructiveHint 为 true。文中引用 Eugeniya Ivanova 2026-09-07 通过 ChatGPT 应用目录审核的经历,扫描器要求显式给出 readOnlyHint、openWorldHint 和 destructiveHint,她为只读工具补上 destructiveHint: false 并撰写说明;另引用 Himanshu Kumar 2026-08-30 对已部署服务器的审计,六个工具均未声明任何注解。

AI推荐理由 聚焦 MCP 注解语义这一具体工程细节:未声明 destructiveHint 在规范下等价于 true,并引用 Eugeniya Ivanova 与 Himanshu Kumar 的实测案例,对 MCP 服务开发者有直接参考价值,但属个人博客转述,非官方规范解读。

62

正文

A tool that ships no annotations has not stayed silent about whether it is destructive. Under the MCP schema it has answered true. The scanner asked for something the spec calls optional Eugeniya Ivanova published a walkthrough on 2026-09-07 of getting an MCP server through the ChatGPT app directory review, and one section of it is about annotations. The scanner went through every tool and required explicit readOnlyHint, openWorldHint and destructiveHint values. Four of her read-only tools carried no destructiveHint, which the specification permits, and the scanner wanted it anyway. She added destructiveHint: false to those tools and wrote justifications for forty-odd annotation values. By her account, the tools themselves did not change. Himanshu Kumar had measured the other end of the same field a week earlier, on 2026-08-30, auditing a deployed server: six tools, zero tools declaring any annotation. His sentence for it is the one worth keeping: "The server did not lie. It said nothing." The arithmetic of the defaults is theirs, not mine. What follows is a count of how often those four names appear in the published source of seven source trees, and what the counting turned out to be unable to tell me. Four defaults, and a note telling you not to act on them ToolAnnotations is declared at line 1912 of schema/2026-07-28/schema.ts in the specification repository (commit 271ecc9, fetched 2026-09-07, 98,426 bytes). Four boolean fields, each with a documented default, and each default with a consequence when the field is missing. Field Default in the schema What a client honoring defaults must assume when the field is absent readOnlyHint false (line 1921) the tool modifies its environment destructiveHint true (line 1931) the tool may perform destructive updates idempotentHint false (line 1941) repeating the call has further effect openWorldHint true (line 1951) the tool reaches an open world of external entities Directly above that interface, at lines 1903 to 1908, sits a NOTE that has to travel with any argument built on those defaults: "all properties in ToolAnnotations are hints. They are not guaranteed to provide a faithful description of tool behavior", and "Clients should never make tool use decisions based on ToolAnnotations received from untrusted servers." So the schema supplies defaults for a field it also instructs clients not to decide on. Both halves are in force, and the gap between them is the subject. One silence, two lawful readings Take a memory server whose save_memory tool ships no annotations. A client implementing the documented defaults must treat that tool as not read-only, destructive, non-idempotent and open-world. A client reading the absent object as "the server did not say, so no annotation gate applies" routes the same call straight through. Neither client is misbehaving. The schema backs the first, the NOTE backs the second, and the server said the identical nothing to both. A second rule at line 1929 makes the reading depend on a different field: destructiveHint "is meaningful only when readOnlyHint == false". On a tool declaring readOnlyHint: true, omitting destructiveHint is exactly right, and reading true into it is an error. On a tool declaring nothing at all, readOnlyHint falls to its default of false, which makes destructiveHint meaningful, which makes its default of true apply. The same omission is clean in one place and loud in the other, and what separates them is a second field the tool also did not fill in. An empty annotation is not missing information. It is a value, and different clients will supply different ones. Counting the four names in seven source trees Method before numbers. For each project I downloaded the branch archive at a named commit, walked every regular file in it, and counted case-insensitive byte occurrences of the four field names. Not a search restricted to paths containing mcp: that filter would have missed the assertions in mem0's tests/test_memory_core.py. Case-insensitive because the Go SDK capitalizes the same fields, and a case-sensitive grep returns a false zero on any Go server. Source tree Commit Files walked readOnlyHint destructiveHint idempotentHint openWorldHint modelcontextprotocol/servers d73f99e 156 61 51 51 60 supermemoryai/supermemory 4d8a4eb 1,192 6 6 6 6 mem0ai/mem0 dae67f7 1,777 2 0 2 1 getzep/zep 54f63ee 936 0 0 0 0 getzep/graphiti b943c9e 360 0 0 0 0 topoteretes/cognee e93a4f0 3,594 0 0 0 0 MemoriLabs/Memori 10d6501 673 0 0 0 0 All rows probed 2026-09-07. GibsonAI/memori now redirects to MemoriLabs/Memori. The package I publish is not in the count, because measuring my own tree beside other people's, on a method I picked myself, is not a comparison a reader should have to trust. Four rows read zero, and a zero from a weak probe proves nothing, so the walker needs a control. In the same pass it counted the word annotations in those four trees: 115 in zep, 30 in graphiti, 210 in cognee, 36 in Memori. The files were read. Three of the four also ship an MCP server in the same tree: zep at mcp/zep-mcp-server with 13 tools registered through mcp.AddTool, graphiti at mcp_server/src/graphiti_mcp_server.py with 13 @mcp.tool() decorators, and cognee at cognee-mcp/src. Memori is the exception: the integrations/openclaw/src/tools path with its 6 registerTool calls is an OpenClaw plugin, and the MCP server sits in a separate public repository, MemoriLabs/memori-mcp (commit e02957d, 12 files), documented as a hosted endpoint. That repository reads zero on all four fields as well, walked the same day. The rows describe servers that exist, and for Memori the server is one repository over. The most useful result is a limit on the method itself. Supermemory reads 6 in every column, which sounds like six annotated tools. It is not. apps/mcp/src/server/tools/annotations.ts defines four named presets, all four fields set in each, and fifteen tool files import one of them. Counting literals undercounts the annotated tools by a factor of two and a half in the one project that factored them into a constant. This census answers whether a tree declares these fields at all. It does not count tools. A zero in the destructiveHint column is not automatically a gap either. The mem0 plugin exposes one tool, a memory search, declaring readOnlyHint: True, idempotentHint: True and no destructiveHint. By line 1929 that omission is precisely the case the spec calls meaningless, so leaving it out is correct. Graphiti shows most plainly what the defaults do when nothing is declared. Thirteen tools, every decorator a bare @mcp.tool(), and the names run from clear_graph, delete_entity_edge and delete_episode through to search_nodes, get_episodes and get_status. A client honoring the defaults treats all thirteen alike. On the first three that lands right. On the last three it does not. Read your own tools/list Source is not the wire. Three JSON-RPC lines on stdin get the answer from a running server, and this prints, per tool, which of the four it omits. printf '%s\n' \ '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}' \ '{"jsonrpc":"2.0","method":"notifications/initialized"}' \ '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \ | npx -y @modelcontextprotocol/server-memory 2>/dev/null \ | python3 -c 'import sys, json FIELDS = ("readOnlyHint", "destructiveHint", "idempotentHint", "openWorldHint") for line in sys.stdin: for t in (json.loads(line).get("result") or {}).get("tools") or []: a = t.get("annotations") or {} print(t["name"], "omits:", ", ".join(f for f in FIELDS if f not in a) or "nothing")' Enter fullscreen mode Exit fullscreen mode Swap the npx line for your own server command. Against the reference server it prints omits: nothing nine times, matching its source: @modelcontextprotocol/server-memory fills all four fields on all nine tools (src/memory/index.ts, commit d73f99e). That package took 405,883 npm installs between 2026-08-08 and 2026-09-06, with the window set in the api.npmjs.org request. The example most servers learn their shape from fills everything. openWorldHint, where the spec names memory as its example Of the four fields, openWorldHint is the one where the specification picks a side by illustration. Lines 1948 and 1949: "For example, the world of a web search tool is open, whereas that of a memory tool is not." The default is true, and the worked example of false is a memory tool. The reference server agrees with its own spec: false on all nine tools, backed by a local memory.jsonl file. Supermemory agrees too, false in all four presets. The mem0 plugin tool declares true. The split is worth naming rather than resolving. The example in the spec fits memory that lives where the tool runs, a file or a local process, which is exactly what the reference server is, and the sentence does not say what to read when the tool hands the work somewhere further out. Servers taking the same line differently is what an optional hint permits, and the field cannot tell a client which reading a given server took. That is a limit of the hint, not a fault in anybody's server, and it is the limit the NOTE was warning about. What this does not prove This is a census of published source on one date, not a census of running servers. A live deployment can answer differently from the tree it was built from, and no hosted deployment was checked here, so nothing above transfers from a repository to a service. A framework can attach annotations at registration or serialization time without the field name ever appearing in a project's own source. Every zero above means the four literals are absent from that tree at that commit. It does not mean the server answers tools/list without annotations. The counts move under you. Two of the seven trees had head commits dated 2026-09-06, one day before this probe, so a run tomorrow counts a different tree. That is why every row names a commit rather than a branch, and why the table was rebuilt from scratch on the day of writing rather than carried over from an earlier pass. None of this ranks memory engines, and none of it is a benchmark. Filling four hint fields is cheap and says nothing about retrieval quality. A project that leaves them empty may be right to, since the specification calls them optional and tells clients not to trust them when present. Both readings of an absent field pass review. If your client documents which one it takes, I would like to read it. Disclosure: I work on Mnemoverse, a memory engine for AI agents connected over MCP, so weigh the argument accordingly.

来源报道 · 1 篇 · 以下链接均前往原始媒体

← 返回精选动态 原文

更多内容