DEV Community

Discussion on: Your MCP server changed. Its version didn't. Here's how to catch it.

Collapse
 
yahhi profile image
Valentina Koniukhova •

Tried it today. worklore 0.5.1 now publishes the sha256 of every part of the surface a stranger can reproduce without a token (initialize without serverInfo.version, tools/list, and which methods and tools answer anonymously) in server.json under _meta → io.modelcontextprotocol.registry/publisher-provided. The deploy check recomputes them from the live server and compares them with the registry entry, the way a client would, and the README has a 20-line script so anyone can do the same: github.com/worklore/worklore-mcp#v...
One thing I left out on purpose: resources/list. Mine lists stories, so it changes every day, and its hash would break the promise daily without anything actually breaking. Thanks for the idea; it was the missing half.

Collapse
 
sidneybissoli profile image
Sidney da S. P. Bissoli •

Thank you — your cut is the one I used. What goes to the registry is only what a stranger can reproduce without a token: the declared surface, and which methods answer anonymously in the production configuration. The keyed variants stay in the lock, where only I can measure them.

One difference from yours: I kept resources/list in. I measured the seven servers and none of them lists anything that changes, so here it holds still. Your reason for leaving it out is now written into the spec as the rule: a list that changes on its own does not belong in a promise. Your 20-line script became verify.mjs, and it is the second implementation the tests compare against.

Thread Thread
 
yahhi profile image
Valentina Koniukhova •

That's the best possible outcome for a 20-line script: becoming the second implementation your tests check against. And your reason for keeping resources/list is the right one: the rule should be "only what holds still", not "never resources".
One thought now that there are two of us: is your spec public? If our _meta blocks used the same key and section names, a client could verify any server that publishes them, not just ours. I'm happy to change worklore's format to match yours, since yours has the tests.

Thread Thread
 
sidneybissoli profile image
Sidney da S. P. Bissoli •

Yes, it's public: SPEC.md, and every registry entry links to it.

I'd love worklore to use the same format. The key stays io.github.sidneybissoli/mcp-surface, with form: "mcp-surface/1" and the declared / anonymous sections. Renaming it would make it a new form and invalidate the hashes already published, so it's the one thing I'd rather not move. §7 has a test vector to check an implementation against, and verify.mjs should then accept worklore's entry as-is.

Two things landed in the spec today that you'll recognise: your "can't say no" probe is step 2 of §6, and §6.2 now says the check holds per response, not per connection.

Thread Thread
 
yahhi profile image
Valentina Koniukhova •

Done: worklore 0.6.0 publishes mcp-surface/1 under your key, and your verify.mjs accepts it against production:
io.github.worklore/worklore@0.6.0 at worklore.dev/mcp
declared registry d5e4e0a80b32 live d5e4e0a80b32 ok
anonymous 7 methods ok

To follow §2, the stories moved behind a resource template (worklore://story/{slug}), and resources/list now holds three fixed documents: a guide, the story format, and the access rules. I reproduced the §7 vector byte for byte before publishing.

One thing I found along the way: verify.mjs builds the sorted object with Object.fromEntries, and JavaScript then puts integer-like keys first, in numeric order ("9" before "10"), while §3 and RFC 8785 order them as strings ("10" before "9"). Nothing in either of our surfaces has such keys, but a third server's could make the two implementations disagree.

Thread Thread
 
sidneybissoli profile image
Sidney da S. P. Bissoli •

Confirmed from here: verify.mjs accepts io.github.worklore/worklore@0.6.0 against production, d5e4e0a80b32 on both sides and the 7 anonymous methods as declared. Thank you for publishing it, and for reproducing the §7 vector before you did.

You were right about Object.fromEntries, and the bug was not only in verify.mjs: the package built its hash the same way, so the two agreed with each other and the tests had nothing to catch. Both now write the JSON key by key, so "10" comes before "9" as §3 says. Before changing anything I checked the seven servers: none has such a key in its locked surface, and all seven live endpoints still give their published hashes, so no fingerprint moves. The fix is @sbissoli/mcp-surface 0.5.3.

The SPEC now has a second vector, §7.2, built around exactly this case: a tool whose inputSchema has the properties "10", "9" and "a". Its sha256 is c7255a44a9d70ea6417ae2757c0530895b628551c3ae9fbfc440878a375539b1, and a third implementation (Python, json.dumps with sort_keys) agrees. Running worklore's implementation against it would close the loop on your side too. The note in §3 credits you.