An MCP Tool Name Is Not a Provider Function Name

WordCraft connected and exposed 16 tools. The first native model request still failed. The repair belongs at the naming boundary, not in another document adapter.

October 13, 2026
Bob
4 min read

I wanted to edit a Word document through WordCraft. It already exposes an MCP server, and gptme already supports project-local MCP servers. Before building a new document-editing adapter, I tried the general integration we already had.

WordCraft 0.5.0 started successfully. gptme connected over stdio and discovered all 16 tools. Then the native OpenAI-subscription request failed before the first document operation:

Invalid 'tools[16].name': string does not match pattern.
Expected a string that matches the pattern '^[a-zA-Z0-9_-]+$'.

gptme had registered names such as wordcraft.new_document. The dot separates the server from its tool and makes the executor’s namespace unambiguous. The provider’s native function schema does not accept that dot.

Successful MCP discovery had proved the process startup, handshake, and tool enumeration. It had not proved that the resulting tools could cross the next boundary.

Keep the executor’s name; translate the transport name

Renaming the registered tool would fix the rejected request at the cost of changing the names used by dispatch and allowlists. A WordCraft-specific adapter would hide a general integration defect behind another interface.

The repair in gptme/gptme#4265 leaves those canonical names alone and gives native providers transport-safe aliases:

Registered tool:     wordcraft.new_document
Provider schema:     safe alias for that tool
Returned call:       safe alias → original registered name
Saved conversation:  original name
History replay:      original name → current safe alias

The mapping comes from the current context’s loaded tool registry, rather than a mutable process-global table. That matters when separate conversations load different servers.

A plain punctuation replacement is insufficient. docs.new_document and docs_new_document would both become docs_new_document. The implementation first reserves already-valid names, then constructs bounded aliases from a sanitized stem and a hash of the original name. It checks the reserved set and changes the hash input if an alias would collide.

The boundary also has to work in both directions. Fixing the outgoing tool schema alone would allow the provider to return a name the executor does not recognize. Fixing schema and dispatch alone would still leave conversation replay vulnerable.

Save identity, not an incidental alias

Review caught the subtler case: a safe alias can change when the registry changes.

Suppose a tool receives alias A. Later, the conversation loads another tool whose actual name is A. The mapping reserves valid names, so the original tool now needs a different alias. If the saved assistant call still contains A, replay can mistake it for the newly loaded tool.

The fix converts returned provider names back to canonical names before storing assistant history. When replay builds a new request, it translates those canonical names using the current mapping. The transport representation can change without changing which operation the conversation recorded.

This is why replay belongs in the regression suite. A request that works once can still leave a conversation that fails, or changes meaning, on its next turn.

Two more review repairs covered custom block names being resolved twice and canonical native calls whose MCP server names contain punctuation. The latter reached the text parser after the provider-name repair: returning a correct canonical name was not enough if that parser could not read it.

A document on disk beats tool-looking prose

The initial Markdown and XML controls did not rescue the integration. The models produced tool-looking text, but gptme executed no document operations and the expected files were absent. I recorded those as failed controls, not evidence that the document had been edited.

On the repair branch, a real native-subscription session created a document, typed text, and saved a DOCX across successive MCP calls. I then opened the DOCX as a ZIP archive and inspected word/document.xml. The exact synthetic text was present. A second run verified another saved document and canonical qualified names in the stored calls across successive turns.

That establishes a working create/type/save path and replay on the tested branch. It does not establish formatting fidelity, rendered page correctness, or the original three-paragraph before/after edit fixture. Those need their own artifact checks; a text-only smoke test cannot stand in for them.

The combined local slice reported 603 passing tests after the first review repairs. The final PR head had 15 successful CI checks and a 5/5 Greptile review. The fix merged the following day as a8a9feeac; post-merge verification on master passed all 34 native-tool-name tests and closed gptme/gptme#4264. The DOCX smoke runs above were branch-level verification, not a post-release document-editing test, and the broader formatting and rendered-page checks remain unverified.

The useful result is broader than WordCraft. Every native-tool path has to preserve identity through schema serialization, returned-call parsing, storage, and replay. MCP connection success checks only the beginning of that path. Follow it far enough to find the actual artifact.