Implements Route B from planning/AI_DRIVEN_IMPORT_ROUTES.md. Design and field notes are on #106; this issue is the build.
Why now
Three things became true after the design was written, and all three shrink the work:
| Design assumed |
Actually available |
| Write the server from scratch |
@umbraco-cms/create-umbraco-mcp-server scaffolds it |
| Reimplement Management API calls in TypeScript |
Chain to @umbraco-cms/mcp-dev and delegate |
| Roughly 1-2 weeks |
Smaller — most of that estimate was plumbing now provided |
So UpDoc's server only has to own extract, transform, apply mapping. Everything Umbraco-shaped — scaffold, create, save — is delegated to the developer server via chaining.
Decisions
npm package, not an RCL. An MCP server is a Node process the host spawns, not something loaded into the ASP.NET pipeline. Confirmed by the reference implementation.
In this repo, at mcp/. Umbraco split their editor and developer servers because they are separate products with separate release cycles. UpDoc's server is part of UpDoc, and the tool contract should version with the code it drives. mcp/ rather than src/UpDoc.Mcp/ because src/ holds .NET projects, and MCP_DOCS_SERVER.md already proposed mcp/.
One package for now. The docs server in MCP_DOCS_SERVER.md was never built, so there is nothing to merge or keep apart. Splitting later is easy; maintaining two from the start is not.
Reference implementation
umbraco/Umbraco-CMS-MCP-Editor — Phil's recommendation, and the pattern to follow.
Package umbraco-mcp-editor-cms: ESM, bin entry, ships dist only, Node >= 22. Versioned 18.0.0-beta.3, tracking the Umbraco major — the same idea as UpDoc's NuGet scheme.
Its dependencies show the chaining:
"@umbraco-cms/mcp-dev": "^18.0.1"
"@umbraco-cms/mcp-server-sdk": "^1.0.0-beta.31"
"@umbraco-cms/mcp-hosted": "^1.0.0-beta.31"
It builds for two targets from one codebase: stdio locally, and a Cloudflare Worker with OAuth. Testing runs in three layers — unit, LLM evals, and hosted E2E.
Chaining
Three patterns, per the SDK docs. Two are useful here:
- Delegation — call an Umbraco tool from inside a handler, invisible to the AI. This is how the import gets its scaffold and creates the document.
- Composite — orchestrate several chained calls in one tool, so an import is a single invocation rather than several LLM round-trips.
Proxying is not wanted. Re-exposing Umbraco's tools duplicates what is already there when both servers run side by side.
Increments
Each is independently useful. Stopping after any of them leaves something that works.
Carry this rule into every write
update-document and raw PUT /document/{id} do full replacement — every top-level field omitted is set to null. This has broken the site three times (115 group tours in July 2026, TTM5086 in June, Sights media in April).
Delegating to a chained tool does not exempt anything. A composite tool that calls the destructive endpoint is exactly as destructive as calling it directly. Use update-document-properties and update-block-property.
Worth raising with Umbraco HQ: this is a general hazard of the MCP surface, not specific to UpDoc.
Not in scope
The end-of-import tasks on a tour — Skybrud redirect, member custom property, page permissions — are outside UpDoc entirely. Public access is reachable through existing Umbraco MCP tools; the other two are not, and no UpDoc server would help with them.
Refs #106, #41.
Implements Route B from
planning/AI_DRIVEN_IMPORT_ROUTES.md. Design and field notes are on #106; this issue is the build.Why now
Three things became true after the design was written, and all three shrink the work:
@umbraco-cms/create-umbraco-mcp-serverscaffolds it@umbraco-cms/mcp-devand delegateSo UpDoc's server only has to own extract, transform, apply mapping. Everything Umbraco-shaped — scaffold, create, save — is delegated to the developer server via chaining.
Decisions
npm package, not an RCL. An MCP server is a Node process the host spawns, not something loaded into the ASP.NET pipeline. Confirmed by the reference implementation.
In this repo, at
mcp/. Umbraco split their editor and developer servers because they are separate products with separate release cycles. UpDoc's server is part of UpDoc, and the tool contract should version with the code it drives.mcp/rather thansrc/UpDoc.Mcp/becausesrc/holds .NET projects, andMCP_DOCS_SERVER.mdalready proposedmcp/.One package for now. The docs server in
MCP_DOCS_SERVER.mdwas never built, so there is nothing to merge or keep apart. Splitting later is easy; maintaining two from the start is not.Reference implementation
umbraco/Umbraco-CMS-MCP-Editor— Phil's recommendation, and the pattern to follow.Package
umbraco-mcp-editor-cms: ESM,binentry, shipsdistonly, Node >= 22. Versioned18.0.0-beta.3, tracking the Umbraco major — the same idea as UpDoc's NuGet scheme.Its dependencies show the chaining:
It builds for two targets from one codebase: stdio locally, and a Cloudflare Worker with OAuth. Testing runs in three layers — unit, LLM evals, and hosted E2E.
Chaining
Three patterns, per the SDK docs. Two are useful here:
Proxying is not wanted. Re-exposing Umbraco's tools duplicates what is already there when both servers run side by side.
Increments
Each is independently useful. Stopping after any of them leaves something that works.
updoc-list-workflows. Proves the server connects and is discoverable.updoc-importwithdryRun: true— returns the extraction, creates nothing. No write risk, and per the field notes on AI-driven import: let AI sessions run Create from Source (endpoint / UpDoc MCP server) #106 this is the highest-value single flag.dryRun: false, creating the document through delegation.warnings[]naming fields that fell back to blueprint defaults. Comes from a real failure: Organiser Name silently took the blueprint placeholder while every other organiser field extracted fine.Carry this rule into every write
update-documentand rawPUT /document/{id}do full replacement — every top-level field omitted is set to null. This has broken the site three times (115 group tours in July 2026, TTM5086 in June, Sights media in April).Delegating to a chained tool does not exempt anything. A composite tool that calls the destructive endpoint is exactly as destructive as calling it directly. Use
update-document-propertiesandupdate-block-property.Worth raising with Umbraco HQ: this is a general hazard of the MCP surface, not specific to UpDoc.
Not in scope
The end-of-import tasks on a tour — Skybrud redirect, member custom property, page permissions — are outside UpDoc entirely. Public access is reachable through existing Umbraco MCP tools; the other two are not, and no UpDoc server would help with them.
Refs #106, #41.