Skip to content

Conversation

@jerome3o-anthropic
Copy link
Member

Summary

Make example MCP App servers publishable to npm under the @modelcontextprotocol scope.

New packages:

  • @modelcontextprotocol/server-basic-react
  • @modelcontextprotocol/server-basic-vanillajs
  • @modelcontextprotocol/server-budget-allocator
  • @modelcontextprotocol/server-cohort-heatmap
  • @modelcontextprotocol/server-customer-segmentation
  • @modelcontextprotocol/server-scenario-modeler
  • @modelcontextprotocol/server-system-monitor
  • @modelcontextprotocol/server-threejs
  • @modelcontextprotocol/server-wiki-explorer

Changes:

  • Remove private: true from example package.json files
  • Add proper npm metadata (description, repository, license, files)
  • Update ext-apps dependency from relative path to ^0.2.2
  • Copy server-utils.ts into each package for standalone use
  • Add publish-examples job to npm-publish.yml workflow (matrix-based, runs in parallel after main SDK is published)

The examples will be published automatically when a GitHub release is created.

Test plan

  • All examples build successfully with npm run examples:build
  • Verify dry-run publish works: npm publish --workspace examples/basic-server-react --dry-run
  • After merge and release, verify packages appear on npm

🤖 Generated with Claude Code

Make example MCP App servers publishable to npm under the
@modelcontextprotocol scope:

- server-basic-react
- server-basic-vanillajs
- server-budget-allocator
- server-cohort-heatmap
- server-customer-segmentation
- server-scenario-modeler
- server-system-monitor
- server-threejs
- server-wiki-explorer

Changes:
- Remove `private: true` from example package.json files
- Add proper npm metadata (description, repository, license, files)
- Update ext-apps dependency from relative to ^0.2.2
- Copy server-utils.ts into each package for standalone use
- Add publish-examples job to npm-publish.yml workflow

The examples will be published automatically when a GitHub release
is created, after the main SDK package is published.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 19, 2025

Open in StackBlitz

@modelcontextprotocol/ext-apps

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/ext-apps@184

@modelcontextprotocol/server-basic-react

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-basic-react@184

@modelcontextprotocol/server-basic-vanillajs

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-basic-vanillajs@184

@modelcontextprotocol/server-budget-allocator

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-budget-allocator@184

@modelcontextprotocol/server-cohort-heatmap

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-cohort-heatmap@184

@modelcontextprotocol/server-customer-segmentation

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-customer-segmentation@184

@modelcontextprotocol/server-scenario-modeler

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-scenario-modeler@184

@modelcontextprotocol/server-system-monitor

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-system-monitor@184

@modelcontextprotocol/server-threejs

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-threejs@184

@modelcontextprotocol/server-wiki-explorer

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-wiki-explorer@184

commit: 68cddec

ochafik and others added 2 commits December 19, 2025 12:12
Resolve conflicts by:
- Keep server-utils.ts next to server.ts (not in src/)
- Simplify server-utils.ts to stateless HTTP only (no SSE)
- Keep main() with explicit --stdio handling in server.ts
- Add integration-server from main for E2E testing
- Add npm start alias from main
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
ochafik
ochafik previously approved these changes Dec 19, 2025
Copy link
Collaborator

@ochafik ochafik left a comment

Choose a reason for hiding this comment

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

Thanks @jerome3o-anthropic !
Merged main and resolved conflicts. Key changes after #182:

  • Reverted the server-utils.ts location back to next to server.ts (not in src/)
  • Simplified server-utils.ts to stateless HTTP only (removed SSE legacy endpoints)
  • Kept main() with explicit --stdio handling in each server.ts

@jonathanhefner I reverted the inline stdio handling in server-utils.ts from #182 — IMO argv checking doesn't belong in a shared helper. Each server's main()
should own that decision, keeping server-utils.ts focused on just the HTTP transport setup.

Update golden screenshots for basic-react and basic-vanillajs after
the tool output was simplified from JSON to plain text in #182.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@jonathanhefner
Copy link
Member

IMO argv checking doesn't belong in a shared helper. Each server's main() should own that decision, keeping server-utils.ts focused on just the HTTP transport setup.

In that case, why not make it boolean parameter? e.g.:

startServer(createServer, process.argv.includes("--stdio"));
// or
startServer(createServer, { stdio: process.argv.includes("--stdio") });

Comment on lines -19 to -39
// Two-part registration: tool + resource, tied together by the resource URI.
const resourceUri = "ui://get-time/mcp-app.html";

// Register a tool with UI metadata. When the host calls this tool, it reads
// `_meta[RESOURCE_URI_META_KEY]` to know which resource to fetch and render
// as an interactive UI.
registerAppTool(server,
"get-time",
{
title: "Get Time",
description: "Returns the current server time as an ISO 8601 string.",
description: "Returns the current server time.",
inputSchema: {},
_meta: { [RESOURCE_URI_META_KEY]: resourceUri },
},
async (): Promise<CallToolResult> => {
const time = new Date().toISOString();
return { content: [{ type: "text", text: time }] };
return { content: [{ type: "text", text: new Date().toISOString() }] };
},
);

// Register the resource, which returns the bundled HTML/JavaScript for the UI.
Copy link
Member

Choose a reason for hiding this comment

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

Since these are educational examples, I would like to preserve these comments.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hopefully restored

Copy link
Member

Choose a reason for hiding this comment

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

Did you mean restored in this PR or elsewhere? They still seem to be removed in the current diff for this PR.

Comment on lines -19 to +52
// Two-part registration: tool + resource, tied together by the resource URI.
const resourceUri = "ui://get-time/mcp-app.html";

// Register a tool with UI metadata. When the host calls this tool, it reads
// `_meta[RESOURCE_URI_META_KEY]` to know which resource to fetch and render
// as an interactive UI.
// MCP Apps require two-part registration: a tool (what the LLM calls) and a
// resource (the UI it renders). The `_meta` field on the tool links to the
// resource URI, telling hosts which UI to display when the tool executes.
registerAppTool(server,
"get-time",
{
title: "Get Time",
description: "Returns the current server time as an ISO 8601 string.",
inputSchema: {},
_meta: { [RESOURCE_URI_META_KEY]: resourceUri },
_meta: { [RESOURCE_URI_META_KEY]: RESOURCE_URI },
},
async (): Promise<CallToolResult> => {
const time = new Date().toISOString();
return { content: [{ type: "text", text: time }] };
return {
content: [{ type: "text", text: JSON.stringify({ time }) }],
};
},
);

// Register the resource, which returns the bundled HTML/JavaScript for the UI.
registerAppResource(server,
resourceUri,
resourceUri,
RESOURCE_URI,
RESOURCE_URI,
{ mimeType: RESOURCE_MIME_TYPE },
async (): Promise<ReadResourceResult> => {
const html = await fs.readFile(path.join(DIST_DIR, "mcp-app.html"), "utf-8");

return {
contents: [
{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html },
// Per the MCP App specification, "text/html;profile=mcp-app" signals
// to the Host that this resource is indeed for an MCP App UI.
{ uri: RESOURCE_URI, mimeType: RESOURCE_MIME_TYPE, text: html },
Copy link
Member

Choose a reason for hiding this comment

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

It looks like there was a bad rebase. This is reverting some of the changes I made in #182.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hopefully restored

@ochafik
Copy link
Collaborator

ochafik commented Dec 19, 2025

In that case, why not make it boolean parameter? e.g.:
startServer(createServer, process.argv.includes("--stdio"));
// or
startServer(createServer, { stdio: process.argv.includes("--stdio") });

@jonathanhefner The signature would be awkard tbh... If we want to make it real clean, should probably look like:

export type ServerOptions = {
  name?: string;
} & (
  | { type: 'streamable-http', port: number, path?: string } // default path is /mcp
  | { type: 'stdio' }
);

But then it's more syntax to instantiate it...
I think the simplest would be to just name it startHttpServer({name, port}) and keep the stdio branch separate (there's little value in shoving it inside createServer - only saves 2 lines per examples and makes signature weird)

Wdyt?

@jonathanhefner
Copy link
Member

If we want to make it real clean, should probably look like:

export type ServerOptions = {
  name?: string;
} & (
  | { type: 'streamable-http', port: number, path?: string } // default path is /mcp
  | { type: 'stdio' }
);

But then it's more syntax to instantiate it... I think the simplest would be to just name it startHttpServer({name, port}) and keep the stdio branch separate (there's little value in shoving it inside createServer - only saves 2 lines per examples and makes signature weird)

I'm not sure I see the value in providing name. When using concurrently (as in run-all.ts), then the example name (which is more exact than the server name) is already logged. When only running one server (without concurrently), the name is obvious.

I also think overriding the path isn't necessary for these examples. (Though I could see us supporting it if we wanted to offer this as a utility function in the SDK itself.)

Anyway, if you prefer to have separate functions, that's fine. I would suggest something like:

process.argv.includes("--stdio") ?
  startStdioServer(createServer) :
  startHttpServer(createServer); 

You could add a port argument to startHttpServer(), but I feel like implicitly parsing from the PORT env var is fairly typical.

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.

4 participants