Repository navigation
feat(mcp): scaffold a tool, an app, or a form with pnpm mcp:new - #351
Merged
Merged
Conversation
@transcend-io/airgap.js-types
@transcend-io/cli
@transcend-io/design-tokens
@transcend-io/internationalization
@transcend-io/privacy-types
@transcend-io/sdk
@transcend-io/type-utils
@transcend-io/utils
@transcend-io/mcp
@transcend-io/mcp-server-admin
@transcend-io/mcp-server-assessment
@transcend-io/mcp-server-base
@transcend-io/mcp-server-consent
@transcend-io/mcp-server-discovery
@transcend-io/mcp-server-docs
@transcend-io/mcp-server-dsr
@transcend-io/mcp-server-inventory
@transcend-io/mcp-server-preferences
@transcend-io/mcp-server-workflows
commit: |
dsklyar
force-pushed
the
danielsklyar/zel-8113-new-view-scaffolder
branch
from
August 3, 2026 06:02
237ac7c to
a513eda
Compare
This was referenced Aug 3, 2026
dsklyar
force-pushed
the
danielsklyar/zel-8113-new-view-scaffolder
branch
from
August 3, 2026 23:47
f235867 to
dc1825d
Compare
4 tasks
dsklyar
force-pushed
the
danielsklyar/zel-8113-new-view-scaffolder
branch
from
August 4, 2026 20:24
dc1825d to
00b56b5
Compare
dsklyar
force-pushed
the
danielsklyar/zel-8113-new-view-scaffolder
branch
from
August 5, 2026 22:33
00b56b5 to
4e47b2d
Compare
dsklyar
force-pushed
the
danielsklyar/zel-8113-new-view-scaffolder
branch
from
August 6, 2026 22:22
16aba6b to
883f659
Compare
Base automatically changed from
danielsklyar/zel-8113-mcp-inspect-command
to
main
August 7, 2026 22:50
Generate the component and the ui:// resource module, and add the package-level view wiring when a package has none yet, so adding a view is two files to fill in rather than seven to remember.
The generator wrote a view that could not be reached without hand-writing a tool, and left the install that view build needs as an instruction. Skipping it surfaced as a resolver error naming a synthesized file, so the view build now checks its dependencies first and says what to run. The generated tool stays unregistered: adding it to the package's tool list is where its name becomes public API, which is a person's call.
Deleting a view leaves its directory behind, because git cannot track an empty one. Discovery then refused to run at all, which broke re-scaffolding the view it belonged to. Files present but no *View.tsx still fails: that is a renamed component, and skipping it is how a view silently stops existing.
Replaces `pnpm mcp:new-view` with `pnpm mcp:new <app|tool|elicitation> <package> <name>`. The three kinds shared nearly all their machinery — resolving the package, spelling the generated names, refusing to clobber a file, and leaving registration to a person — so they now share it, and each kind is a template module. Only `app` touches a package manifest, which is what keeps React, Vite, and Tailwind out of packages that ship no view. The `elicitation` template emits a valid form rather than a placeholder, since assertElicitFormSchema runs at construction and a stub would throw on boot.
The scaffolded doc comment promised enforcement by the styling test, which no longer exists. Keeps the reason a view should avoid arbitrary color and length values, without naming a check.
dsklyar
force-pushed
the
danielsklyar/zel-8113-new-view-scaffolder
branch
from
August 7, 2026 22:50
4aac7cf to
d07c4a6
Compare
With v1 support gone there is one spec, so `V2` in the name carried no information and the comment argued a choice nobody makes. The pin still needs justifying: `restoreSandboxProxy` reads the install's `clients/web/static` layout, which a major bump could restructure into a silent warning.
dawson-turechek-transcend
left a comment
Member
There was a problem hiding this comment.
Nice scaffolder — the “only app touches the manifest” boundary and leaving registration manual both feel right. A few things I’d adjust before this becomes the path everyone copies:
The generated `prebuild`, `build:ui`, and `tsconfig.ui.json` hardcoded three levels to the repo root, which is right for `packages/mcp/*` and resolves outside the repo from `dev/*` — the depth of the only view package there is. Derived from the package's own directory instead. Tool names now match the package they land in. The prefix is a package's own spelling rather than its directory's: `mcp-server-examples` ships `example_*` and `mcp-server-assessment` ships `assessments_*`, disagreeing in opposite directions, so both are listed rather than derived. `ui://` uris and `pnpm mcp:inspect` keep taking the directory's short name, which is what those packages already serve. The `app` kind takes an `_app` suffix, as `example_hello_app` does. It shared a wire name with the `tool` kind for the same argument, and registering both would collide somewhere `writeNew` cannot see, being two filenames. The elicitation template also parses a host's answer against the fields its form required, rather than the all-optional argument schema, so an empty acceptance reports `malformed` instead of echoing back as an answer.
dawson-turechek-transcend
approved these changes
Aug 8, 2026
4 tasks
This was referenced Sep 7, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Last of the eleven. Adds
pnpm mcp:new <kind> <package> <name>, one command for the three things a contributor actually adds to an MCP server:package.jsontooldefineTool, no variantsappsrc/ui/<name>/<Name>View.tsx,src/apps/<name>.ts,src/tools/<name>_app.tselicitationWhy one command and not three
The kinds differ only in what they write. Resolving the package, deriving the names, refusing to clobber a file, and leaving registration alone are the same problem three times, so they live in
scripts/lib/mcp-new/shared.tsand each kind is a template module under it.More usefully, it makes a boundary explicit: only
apptouches a manifest. A tool that renders as text needs none of React, Vite, or Tailwind, and three sibling scripts would leave a standing risk thatmcp:new-tooldrags a browser toolchain into a package that ships no view. Getting a first view to render still means aprebuildhook,build:ui,typecheck:uipointed at atsconfig.ui.jsonextending the right base, a gitignore entry, and sixcatalog:devDependencies — none discoverable from the view directory, since the entry point and stylesheet that boot a view are synthesized at build time and exist nowhere on disk. Theappkind knows that and then runspnpm install, because skipping it fails as an unresolvablereactinsidemcp-app-entry.tsx, a file that is not on disk and whose error never mentions installing anything.Two things the templates do rather than stub
elicitationemits a valid form — one required field, a real prompt.assertElicitFormSchemaand the empty-message check run at construction, so a placeholder schema would generate a package that throws on boot. A generator emitting code that cannot run is worse than one emitting nothing.appemits the MCP App variant only. To serve a form too, generate one and move its variant across;example_hello_appis the worked example of a single tool serving all three paths, and the generated file says so.Left to the author
Registering the factory in
src/tools/index.ts, for every kind. That line is where a name and description become public API on a published package, so it stays a person's decision — but the command prints the paste:The kebab-case check is not cosmetic: the name becomes a directory name, a
*View.tsxcomponent name, the last segment of aui://uri, and the tail of a tool name, and only one spelling satisfies all four.docs+fetch-usagebecomesdocs_fetch_usage, ordocs_usage_chart_appfor theappkind, which takes the same_appsuffix asexample_hello_app— without it a tool and the view that renders its result would share a wire name.The prefix is the package's own spelling, not its directory's:
mcp-server-examplesshipsexample_*andmcp-server-assessmentshipsassessments_*, so those two are listed rather than derived.ui://uris andpnpm mcp:inspectkeep taking the directory's short name, which is what both packages already serve. Generated scripts and thetsconfig.ui.jsonextendsderive their way to the repo root from the package, sincepackages/mcp/*is three levels up anddev/*is two.Ticket: ZEL-8113. No changeset — root-level scripts and docs only, nothing published changes.
Also here
*View.tsx, which is exactly the state a half-finishedmkdirleaves behind.Where this sits in the stack
Eleven layers, each targeting the one below it, registered as a GitHub stack — so this diff is only its own commits, and merging a lower layer rebases and retargets everything above it automatically.
tokens.cssfrom source inside the monorepoui://resources and per-capability tool variants./uisubpath withuseMcpAppdev/mcp-server-examples, the first consumer of all of itpnpm mcp:inspectwith per-request dev view HTMLpnpm mcp:newscaffolder ← this PRDepends on #350, for
viewHtmlanddiscoverMcpPackages. Everything below that is ordering, not need.Test plan
format,lint,typecheck,typecheck:ui, and the fullpnpm run test(40 tasks) are greenscripts/mcp-new.test.tscover all three kinds against a temp fixture package: files written, script order, refuse-to-overwrite, kebab-case rejection, the tool prefix per package, the repo-root path at both package depths, and thattoolandelicitationleave the manifest byte-identicalmcp-server-examplesand checked the output is real code rather than plausible text: the view built through Vite into a self-contained document, and all four files were in thetscprogram for bothtypecheckandtypecheck:uiassertElicitFormSchemarather than only compilingmcp-app-views.test.tspasses (exports the component its filename promises, no hand-written entry or stylesheet)PascalCasename, and the umbrella package each fail with a message naming the fixgit statusclean apart from this PR's filesDemo(s)
Recorded before the rename, so it shows the older
mcp:new-viewspelling and theappkind's output; the files it writes are unchanged.Screen.Recording.2026-08-02.at.23.59.01.mov