Skip to content

Commit b6c77b8

Browse files
chelojimenezclaude
andcommitted
Initial commit: MCP App IO Explainer
A minimal MCP App with one tool (create-status-card) that visually demonstrates how tool inputs and outputs drive UI rendering. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
0 parents  commit b6c77b8

12 files changed

Lines changed: 4342 additions & 0 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
dist/

main.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2+
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
3+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4+
import cors from "cors";
5+
import type { Request, Response } from "express";
6+
import { createServer } from "./server.js";
7+
8+
async function startHTTPServer(): Promise<void> {
9+
const port = parseInt(process.env.PORT ?? "3001", 10);
10+
const app = createMcpExpressApp({ host: "0.0.0.0" });
11+
app.use(cors());
12+
13+
app.all("/mcp", async (req: Request, res: Response) => {
14+
const server = createServer();
15+
const transport = new StreamableHTTPServerTransport({
16+
sessionIdGenerator: undefined,
17+
});
18+
19+
res.on("close", () => {
20+
transport.close().catch(() => {});
21+
server.close().catch(() => {});
22+
});
23+
24+
try {
25+
await server.connect(transport);
26+
await transport.handleRequest(req, res, req.body);
27+
} catch (error) {
28+
console.error("MCP error:", error);
29+
if (!res.headersSent) {
30+
res.status(500).json({
31+
jsonrpc: "2.0",
32+
error: { code: -32603, message: "Internal server error" },
33+
id: null,
34+
});
35+
}
36+
}
37+
});
38+
39+
app.listen(port, () => {
40+
console.log(`MCP server listening on http://localhost:${port}/mcp`);
41+
});
42+
}
43+
44+
async function main() {
45+
if (process.argv.includes("--stdio")) {
46+
await createServer().connect(new StdioServerTransport());
47+
} else {
48+
await startHTTPServer();
49+
}
50+
}
51+
52+
main().catch((e) => {
53+
console.error(e);
54+
process.exit(1);
55+
});

mcp-app.html

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<meta name="color-scheme" content="light dark">
7+
<title>IO Explainer</title>
8+
</head>
9+
<body>
10+
<main class="main">
11+
12+
<!-- Phase banner shown while waiting -->
13+
<div id="waiting" class="phase-banner">
14+
<div class="phase-icon">
15+
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
16+
<circle cx="12" cy="12" r="10"/>
17+
<path d="M12 6v6l4 2"/>
18+
</svg>
19+
</div>
20+
<p class="phase-text">Waiting for tool call&hellip;</p>
21+
<p class="phase-sub">Ask the model to call <code>create-status-card</code></p>
22+
</div>
23+
24+
<!-- Streaming preview -->
25+
<div id="streaming" class="phase-banner hidden">
26+
<div class="phase-icon pulse">
27+
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
28+
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
29+
</svg>
30+
</div>
31+
<p class="phase-text">Streaming inputs&hellip;</p>
32+
<pre id="streaming-json" class="streaming-json"></pre>
33+
</div>
34+
35+
<!-- Main 3-panel explainer (hidden until tool completes) -->
36+
<div id="explainer" class="explainer hidden">
37+
38+
<!-- Header -->
39+
<div class="explainer-header">
40+
<h2 class="explainer-title">How Tool I/O Drives the UI</h2>
41+
<p class="explainer-subtitle">Every visual element traces back to a tool input or output</p>
42+
</div>
43+
44+
<!-- 3-column layout -->
45+
<div class="columns">
46+
47+
<!-- LEFT: Inputs -->
48+
<section class="panel panel-inputs">
49+
<h3 class="panel-heading">
50+
<span class="panel-dot dot-input"></span>
51+
Tool Inputs
52+
</h3>
53+
<p class="panel-desc">Arguments the model sent</p>
54+
<ul class="field-list" id="input-fields"></ul>
55+
</section>
56+
57+
<!-- CENTER: Rendered Card -->
58+
<section class="panel panel-card">
59+
<h3 class="panel-heading">Rendered UI</h3>
60+
<p class="panel-desc">What the user sees</p>
61+
<div class="card" id="rendered-card">
62+
<div class="card-status-bar" id="card-status-bar"></div>
63+
<div class="card-body">
64+
<div class="card-icon" id="card-icon"></div>
65+
<h4 class="card-title" id="card-title"></h4>
66+
<p class="card-message" id="card-message"></p>
67+
<div class="progress-track">
68+
<div class="progress-fill" id="card-progress"></div>
69+
</div>
70+
<span class="progress-label" id="card-progress-label"></span>
71+
</div>
72+
<div class="card-footer" id="card-footer"></div>
73+
</div>
74+
</section>
75+
76+
<!-- RIGHT: Outputs -->
77+
<section class="panel panel-outputs">
78+
<h3 class="panel-heading">
79+
<span class="panel-dot dot-output"></span>
80+
Tool Outputs
81+
</h3>
82+
<p class="panel-desc">Structured result returned</p>
83+
<ul class="field-list" id="output-fields"></ul>
84+
</section>
85+
86+
</div>
87+
88+
<!-- Connection legend -->
89+
<div class="legend">
90+
<span class="legend-item"><span class="legend-line legend-input"></span> Input &rarr; Visual</span>
91+
<span class="legend-item"><span class="legend-line legend-output"></span> Output &rarr; Visual</span>
92+
</div>
93+
94+
</div>
95+
96+
</main>
97+
<script type="module" src="/src/mcp-app.ts"></script>
98+
</body>
99+
</html>

0 commit comments

Comments
 (0)