Skip to content

feat(sdk): ship per-tool TypeScript types codegen (#65)#66

Merged
hifi-phil merged 15 commits intodevfrom
feat/tool-types-codegen
Apr 26, 2026
Merged

feat(sdk): ship per-tool TypeScript types codegen (#65)#66
hifi-phil merged 15 commits intodevfrom
feat/tool-types-codegen

Conversation

@hifi-phil
Copy link
Copy Markdown
Contributor

Summary

Closes #65. Ships a umbraco-mcp-generate-types CLI binary and a createPermissiveCodegenUser() helper from @umbraco-cms/mcp-server-sdk so any downstream MCP can emit a typed tool registry from its built collections.

  • New bin: umbraco-mcp-generate-types — walks --collections <path>, runs Zod v4's z.toJSONSchema() + json-schema-to-typescript per tool, writes a single .d.ts registry.
  • New helper createPermissiveCodegenUser() (Proxy-based, no enumeration of section/permission strings).
  • Tolerant to ZodType, ZodRawShape, discriminated unions; per-tool fallback to Record<string, unknown> / unknown when a schema can't convert.
  • Type-name collision detection (e.g. get-document vs getDocument) with both offending tool names in the error.
  • Documented in the SDK README.

Smoke-tested against the local template — wrote a valid TemplateTools registry covering all 10 fixture tools.

What's NOT in this PR

Plan

Implementation followed docs/plans/mcp-server-sdk/tool-types-codegen.md (committed as the first commit on the branch). 7 tasks, each TDD where possible, two-stage review per task.

Test plan

  • npm run build -w packages/mcp-server-sdk — clean
  • npm run test -w packages/mcp-server-sdk — 491/491 pass (468 parallel + 23 stdio)
  • Binary smoke test against local template's built collections — emits valid registry
  • No version files touched (no release-time work in this PR)
  • CI green on this PR

Public API additions

import { createPermissiveCodegenUser } from "@umbraco-cms/mcp-server-sdk";
npx umbraco-mcp-generate-types \
  --collections ./dist/collections.js \
  --out ./dist/tool-types.d.ts \
  --registry-name CmsTools

🤖 Generated with Claude Code

hifi-phil and others added 10 commits April 25, 2026 18:39
Plan for shipping a per-tool TypeScript types codegen from the SDK,
exposing createPermissiveCodegenUser() and a umbraco-mcp-generate-types
CLI binary so downstream MCPs can emit typed tool registries.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Return type is unknown (not UserModel) — proxy doesn't satisfy the
  UserModel scalar shape; consumers must only pass it to
  collection.tools(user) and not read .id/.email directly.
- JSDoc lists supported array predicates + documents the
  filter/map/reduce/forEach gap.
- Drop the source-file-scan test — its behavioral assertion is
  covered by the existing "any property access" test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Type-name collision error now identifies both the conflicting tool
  and the previously-claimed tool, helping users find the duplicate
  in large collections.
- Test now asserts the previous tool name appears in the message.
- Add assertions on skipped[0].field and the absent-output-schema
  fallback registry line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Consolidate node:fs dynamic imports in mainFromCli into a single
  destructure at the top of the function.
- Hoist binPath + existsSync guard into a shared beforeAll for the
  binary smoke tests so both tests get a clear error when dist is
  stale.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this do?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a JSDoc file-header explaining the dual role (library + CLI binary). Pushed in dc0c80f.

Comment thread packages/mcp-server-sdk/README.md
Per PR review feedback:

- Drop the "Generating tool types" section from packages/mcp-server-sdk/README.md.
  The full docs now live at 17/umbraco-in-ai/mcp/base-mcp/sdk/tool-types.md
  in the UmbracoDocs repo (separate PR).
- Add a JSDoc file-header to generate-tool-types.ts explaining what the
  file does (it's both a library entry and the CLI binary, which isn't
  obvious from the shebang).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the CLI is invoked through an npm bin shim (e.g.
node_modules/.bin/umbraco-mcp-generate-types), process.argv[1] is the
shim's symlink path while import.meta.url is the resolved physical path.
The strict-equality check meant mainFromCli never ran, so the binary
silently no-op'd with exit 0 in every consumer using @umbraco-cms/mcp-dev's
postbuild hook.

Resolve symlinks via realpathSync before comparing.

Caught while wiring the CLI into umbraco-mcp-cms (PR #168 there): the
postbuild step ran without output and produced no tool-types.d.ts.
hifi-phil added a commit to umbraco/Umbraco-CMS-MCP-Dev that referenced this pull request Apr 26, 2026
The codegen pattern landed upstream in @umbraco-cms/mcp-server-sdk via PR
umbraco/Umbraco-MCP-Base#66 (issue #65). Replace the local 157-line script
with a postbuild call to the SDK's umbraco-mcp-generate-types CLI.

- Drop scripts/generate-tool-types.mjs
- Drop json-schema-to-typescript devDep (now provided transitively by SDK)
- Bump @umbraco-cms/mcp-server-sdk floor to ^17.0.0-beta.14
- Switch from && chaining to a postbuild hook (per review #3)
- Revert version bump to 17.3.0 (release branch handles versioning, per
  review #2)
- Update README import to CmsToolsName (SDK derives the union name as
  ${registryName}Name)

Output is byte-identical to the previous inline script except for the
banner comment and the renamed union type. 354 tools, 0 schema fallbacks.

Note: requires SDK >= beta.15 to actually run on a fresh checkout
(beta.14 ships the CLI but its _isMain check no-ops under bin-shim
symlinks; fixed in the SDK PR).
hifi-phil and others added 3 commits April 26, 2026 09:05
Every freshly scaffolded MCP now runs umbraco-mcp-generate-types as a
postbuild step and exposes the generated dist/tool-types.d.ts via a
./tool-types subpath export. This means any other MCP that wants to
chain to this server gets compile-time type safety for free, with no
manual setup.

Verified locally: template build emits a 10-tool McpTemplateTools
registry; all 24 template tests still pass.

CLAUDE.md notes that the postbuild step + ./tool-types export are
optional and can be removed for private/internal MCPs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The SDK's bin (umbraco-mcp-generate-types) points at
dist/cli/generate-tool-types.js which doesn't exist when npm ci runs,
so npm skips the symlink creation. After Build SDK the file exists but
the symlink is still missing, causing the template's postbuild step
to fail with "command not found" (exit 127).

npm rebuild recreates the bin symlinks based on current state,
fixing both Test Suite and CLI eval jobs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The template now has a postbuild step (umbraco-mcp-generate-types)
and a ./tool-types subpath export. Update the snapshot in
template-structure.test.ts to reflect this.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hifi-phil hifi-phil merged commit 3d90f0c into dev Apr 26, 2026
7 checks passed
@hifi-phil hifi-phil deleted the feat/tool-types-codegen branch April 26, 2026 18:26
@hifi-phil hifi-phil mentioned this pull request Apr 26, 2026
4 tasks
hifi-phil added a commit that referenced this pull request Apr 26, 2026
  Changes since 17.0.0-beta.14:                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                           
  - feat(sdk): ship per-tool TypeScript types codegen (#65, #66)                                                                                                                                                                                                           
    Adds a `umbraco-mcp-generate-types` postbuild step that walks       
    `dist/collections.js`, runs each tool's input/output Zod schema                                                                                                                                                                                                        
    through codegen, and writes a typed registry to                                                                                                                                                                                                                        
    `dist/tool-types.d.ts`. Exposed via the `./tool-types` subpath                                                                                                                                                                                                         
    so downstream MCPs can chain to a built server with full type                                                                                                                                                                                                          
    safety (`import type { McpTemplateTools } from "@umbraco-cms/mcp-template/tool-types"`).                                                                                                                                                                               
                                                                                                                                                                                                                                                                           
  - fix: normalize trailing slashes in UMBRACO_BASE_URL (#63, #64)                                                                                                                                                                                                         
    Strips a trailing `/` from `UMBRACO_BASE_URL` before joining                                                                                                                                                                                                           
    paths, so values like `https://example.com/` no longer produce                                                                                                                                                                                                         
    `//umbraco/management/api/...` request URLs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant