Skip to content

Add example-mcp-app: Spiceflow + MCP Apps integration#46

Open
remorses wants to merge 1 commit into
mainfrom
example-mcp-app
Open

Add example-mcp-app: Spiceflow + MCP Apps integration#46
remorses wants to merge 1 commit into
mainfrom
example-mcp-app

Conversation

@remorses

Copy link
Copy Markdown
Owner

Spiceflow website + MCP server in a single app. Demonstrates how one Spiceflow process can serve RSC pages at / and speak the Model Context Protocol at POST /mcp.

  • src/main.tsx — Spiceflow app with website pages + /mcp endpoint that adapts Web Request/Response to the MCP SDK's StreamableHTTPServerTransport
  • src/mcp-server.tsMcpServer factory with geocode (OpenStreetMap Nominatim) and show-map (interactive Leaflet map) tools
  • src/app/mcp-app.tsx — client-only React app for the MCP App iframe, uses @modelcontextprotocol/ext-apps SDK, registers a navigate-to tool back to the LLM
  • Two Vite configs: one for the Spiceflow website, one for bundling the iframe UI into a single HTML file via vite-plugin-singlefile

Build: pnpm build:mcp-ui bundles the iframe HTML, then pnpm build runs the full Spiceflow production build. The MCP server reads the pre-built HTML from dist-mcp-ui/ at runtime.

…map UI

Demonstrates how a single Spiceflow app can serve both a website (RSC pages
with Tailwind) and an MCP server endpoint at POST /mcp.

Architecture:
- src/main.tsx: Spiceflow app with / and /about pages, plus a /mcp endpoint
  that adapts Web Request/Response to the MCP SDK StreamableHTTPServerTransport
- src/mcp-server.ts: McpServer factory with geocode (OpenStreetMap Nominatim)
  and show-map (interactive Leaflet map) tools
- src/app/mcp-app.tsx: client-only React app for the MCP App iframe UI, uses
  @modelcontextprotocol/ext-apps SDK for host communication and registers a
  navigate-to tool back to the LLM
- mcp-app.html: HTML shell bundled into a single file via vite-plugin-singlefile
- Two Vite configs: one for the Spiceflow website, one for the MCP UI bundle

Build pipeline: pnpm build:mcp-ui bundles the iframe HTML first, then
pnpm build runs the Spiceflow production build. The MCP server reads the
pre-built HTML from dist-mcp-ui/ at runtime and serves it as a ui:// resource.

Session: ses_2462ab72dffelMbT6WxWAkDW24
@vercel

vercel Bot commented Apr 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
integration-tests Ready Ready Preview, Comment Apr 23, 2026 10:45am

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7eed5c464e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// Resolve the dist directory for the pre-built MCP App UI HTML.
// In dev we read from dist-mcp-ui/ (built by `pnpm build:mcp-ui`).
function getMcpUiDistDir(): string {
return path.join(import.meta.dirname, '..', 'dist-mcp-ui')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Resolve dist-mcp-ui relative to runtime entry

getMcpUiDistDir() is computed from import.meta.dirname, but after vite build this module runs from dist/rsc/..., so path.join(import.meta.dirname, '..', 'dist-mcp-ui') points inside dist/ instead of the project-level dist-mcp-ui folder produced by build:mcp-ui. In production (node dist/rsc/index.js), the show-map resource read will fail with ENOENT, so the MCP app UI cannot be served.

Useful? React with 👍 / 👎.


try {
await server.connect(transport)
await transport.handleRequest(fakeReq as any, fakeRes as any, body)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Pass a real IncomingMessage to MCP transport

StreamableHTTPServerTransport.handleRequest() expects Node IncomingMessage/ServerResponse, but this code passes a plain object cast to any. The adapter layer used by the SDK relies on request stream/event APIs (for example request lifecycle listeners on POST bodies), which fakeReq does not implement, so /mcp requests can fail at runtime and return the generic 500 path instead of handling MCP messages.

Useful? React with 👍 / 👎.

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