Skip to content

Commit 8a877b3

Browse files
authored
feat(debug-server): add interactive debug server example (#321)
* feat(examples): add debug-server example A comprehensive debug/development server for testing MCP Apps SDK features: - Vanilla TypeScript UI with collapsible panels - Message visualization (sent, received, errors) - Server tool calling interface - Multiple tool endpoints for testing * refactor(debug-server): convert to React - Replace vanilla TypeScript with React components - Add comprehensive Host Styles panel showing all CSS variables grouped by category - Use React hooks (useApp, useHostStyles, useAutoResize) - Cleaner state management with useReducer for event log - Collapsible sections for better organization - CSS modules for scoped styling * fix(debug-server): correct server name in main.ts * Revert "refactor(debug-server): convert to React" This reverts commit 5d22d2d. * fix(debug-server): update default values for multipleBlocks and includeMeta * fix(debug-server): correct serve script and server name - serve script now runs main.ts instead of server.ts - Updated server name to 'Debug Server' - Fixed npx command in comment * fix: increase maxHeight to 6000px and add tools capability to debug app - basic-host: Increase maxHeight from 600 to 6000px - debug-server: Add tools capability to enable oncalltool/onlisttools handlers * fix(debug-server): use consistent callback names for event tracking Event types now match callback names (ontoolinput, ontoolresult, etc.) so the Callback Status panel shows correct counts and payloads. * feat(debug-server): add Update (Image) button for model context * feat(debug-server): add expandable event log entries - Click on any event log row to expand/collapse - Arrow indicator rotates when expanded - Full JSON payload shown indented in pre block - Max height with scroll for large payloads * style(debug-server): format long line in fullPayload function * test(debug-server): add to E2E tests and screenshot specs * chore: regenerate package-lock.json for cross-platform compatibility * update dep
1 parent 023f1fd commit 8a877b3

22 files changed

Lines changed: 2253 additions & 435 deletions

examples/basic-host/src/implementation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ export function newAppBridge(
271271
updateModelContext: { text: {} },
272272
}, {
273273
hostContext: {
274-
containerDimensions: options?.containerDimensions ?? { maxHeight: 600 },
274+
containerDimensions: options?.containerDimensions ?? { maxHeight: 6000 },
275275
displayMode: options?.displayMode ?? "inline",
276276
availableDisplayModes: ["inline", "fullscreen"],
277277
},

examples/basic-host/src/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ function AppIFramePanel({ toolCallInfo, isDestroying, onTeardownComplete }: AppI
422422
onDisplayModeChange: setDisplayMode,
423423
}, {
424424
// Provide container dimensions - maxHeight for flexible sizing
425-
containerDimensions: { maxHeight: 600 },
425+
containerDimensions: { maxHeight: 6000 },
426426
displayMode: "inline",
427427
});
428428
appBridgeRef.current = appBridge;

examples/debug-server/.gitignore

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

examples/debug-server/README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Debug Server
2+
3+
A comprehensive testing/debugging tool for the MCP Apps SDK that exercises every capability, callback, and result format combination.
4+
5+
## Tools
6+
7+
### debug-tool
8+
9+
Configurable tool for testing all result variations:
10+
11+
| Parameter | Type | Default | Description |
12+
| -------------------------- | ----------------------------------------------------------------------------------- | -------- | ------------------------------------------- |
13+
| `contentType` | `"text"` \| `"image"` \| `"audio"` \| `"resource"` \| `"resourceLink"` \| `"mixed"` | `"text"` | Content block type to return |
14+
| `multipleBlocks` | boolean | `false` | Return 3 content blocks |
15+
| `includeStructuredContent` | boolean | `true` | Include structuredContent in result |
16+
| `includeMeta` | boolean | `false` | Include \_meta in result |
17+
| `largeInput` | string | - | Large text input (tests tool-input-partial) |
18+
| `simulateError` | boolean | `false` | Return isError: true |
19+
| `delayMs` | number | - | Delay before response (ms) |
20+
21+
### debug-refresh
22+
23+
App-only tool (hidden from model) for polling server state. Returns current timestamp and call counter.
24+
25+
## App UI
26+
27+
The debug app provides a dashboard with:
28+
29+
- **Event Log**: Real-time log of all SDK events with filtering
30+
- **Host Info**: Context, capabilities, container dimensions, styles
31+
- **Callback Status**: Table of all callbacks with call counts
32+
- **Actions**: Buttons to test every SDK method:
33+
- Send messages (text/image)
34+
- Logging (debug/info/warning/error)
35+
- Model context updates
36+
- Display mode requests
37+
- Link opening
38+
- Resize controls
39+
- Server tool calls
40+
- File operations
41+
42+
## Usage
43+
44+
```bash
45+
# Build
46+
npm run --workspace examples/debug-server build
47+
48+
# Run standalone
49+
npm run --workspace examples/debug-server serve
50+
51+
# Run with all examples
52+
npm start
53+
```
54+
55+
Then open `http://localhost:8080/basic-host/` and select "Debug MCP App Server" from the dropdown.
30.6 KB
Loading

examples/debug-server/main.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/**
2+
* Entry point for running the MCP server.
3+
* Run with: npx mcp-server-debug
4+
* Or: node dist/index.js [--stdio]
5+
*/
6+
7+
/**
8+
* Shared utilities for running MCP servers with Streamable HTTP transport.
9+
*/
10+
11+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
12+
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
13+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
15+
import cors from "cors";
16+
import type { Request, Response } from "express";
17+
import { createServer } from "./server.js";
18+
19+
export interface ServerOptions {
20+
port: number;
21+
name?: string;
22+
}
23+
24+
/**
25+
* Starts an MCP server with Streamable HTTP transport in stateless mode.
26+
*
27+
* @param createServer - Factory function that creates a new McpServer instance per request.
28+
* @param options - Server configuration options.
29+
*/
30+
export async function startServer(
31+
createServer: () => McpServer,
32+
options: ServerOptions,
33+
): Promise<void> {
34+
const { port, name = "MCP Server" } = options;
35+
36+
const app = createMcpExpressApp({ host: "0.0.0.0" });
37+
app.use(cors());
38+
39+
app.all("/mcp", async (req: Request, res: Response) => {
40+
const server = createServer();
41+
const transport = new StreamableHTTPServerTransport({
42+
sessionIdGenerator: undefined,
43+
});
44+
45+
res.on("close", () => {
46+
transport.close().catch(() => {});
47+
server.close().catch(() => {});
48+
});
49+
50+
try {
51+
await server.connect(transport);
52+
await transport.handleRequest(req, res, req.body);
53+
} catch (error) {
54+
console.error("MCP error:", error);
55+
if (!res.headersSent) {
56+
res.status(500).json({
57+
jsonrpc: "2.0",
58+
error: { code: -32603, message: "Internal server error" },
59+
id: null,
60+
});
61+
}
62+
}
63+
});
64+
65+
const httpServer = app.listen(port, (err) => {
66+
if (err) {
67+
console.error("Failed to start server:", err);
68+
process.exit(1);
69+
}
70+
console.log(`${name} listening on http://localhost:${port}/mcp`);
71+
});
72+
73+
const shutdown = () => {
74+
console.log("\nShutting down...");
75+
httpServer.close(() => process.exit(0));
76+
};
77+
78+
process.on("SIGINT", shutdown);
79+
process.on("SIGTERM", shutdown);
80+
}
81+
82+
async function main() {
83+
if (process.argv.includes("--stdio")) {
84+
await createServer().connect(new StdioServerTransport());
85+
} else {
86+
const port = parseInt(process.env.PORT ?? "3110", 10);
87+
await startServer(createServer, { port, name: "Debug Server" });
88+
}
89+
}
90+
91+
main().catch((e) => {
92+
console.error(e);
93+
process.exit(1);
94+
});

examples/debug-server/mcp-app.html

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
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>Debug App</title>
8+
</head>
9+
<body>
10+
<main class="main">
11+
<!-- Section 1: Event Log -->
12+
<section class="section">
13+
<h2 class="section-header">
14+
<span>Event Log</span>
15+
<div class="header-actions">
16+
<select id="log-filter">
17+
<option value="all">All Events</option>
18+
<option value="ontoolinput">ontoolinput</option>
19+
<option value="ontoolinputpartial">ontoolinputpartial</option>
20+
<option value="ontoolresult">ontoolresult</option>
21+
<option value="ontoolcancelled">ontoolcancelled</option>
22+
<option value="onhostcontextchanged">onhostcontextchanged</option>
23+
<option value="onteardown">onteardown</option>
24+
<option value="oncalltool">oncalltool</option>
25+
<option value="onlisttools">onlisttools</option>
26+
<option value="onerror">onerror</option>
27+
<option value="connected">connected</option>
28+
</select>
29+
<button id="clear-log-btn" class="btn-small">Clear</button>
30+
</div>
31+
</h2>
32+
<div id="event-log" class="event-log"></div>
33+
</section>
34+
35+
<!-- Section 2: Host Info -->
36+
<section class="section collapsible">
37+
<h2 class="section-header" data-toggle="host-info-content">
38+
<span>Host Info</span>
39+
<span class="toggle-icon"></span>
40+
</h2>
41+
<div id="host-info-content" class="section-content">
42+
<div class="info-grid">
43+
<div class="info-group">
44+
<h3>Context</h3>
45+
<dl id="host-context-info"></dl>
46+
</div>
47+
<div class="info-group">
48+
<h3>Capabilities</h3>
49+
<dl id="host-capabilities-info"></dl>
50+
</div>
51+
<div class="info-group">
52+
<h3>Container</h3>
53+
<dl id="host-container-info"></dl>
54+
</div>
55+
<div class="info-group">
56+
<h3>Styles Sample</h3>
57+
<div id="host-styles-sample" class="styles-sample"></div>
58+
</div>
59+
</div>
60+
</div>
61+
</section>
62+
63+
<!-- Section 3: Callback Status -->
64+
<section class="section collapsible">
65+
<h2 class="section-header" data-toggle="callback-status-content">
66+
<span>Callback Status</span>
67+
<span class="toggle-icon"></span>
68+
</h2>
69+
<div id="callback-status-content" class="section-content">
70+
<table class="callback-table">
71+
<thead>
72+
<tr>
73+
<th>Callback</th>
74+
<th>Registered</th>
75+
<th>Count</th>
76+
<th>Last Payload</th>
77+
</tr>
78+
</thead>
79+
<tbody id="callback-table-body"></tbody>
80+
</table>
81+
</div>
82+
</section>
83+
84+
<!-- Section 4: Action Buttons -->
85+
<section class="section collapsible">
86+
<h2 class="section-header" data-toggle="actions-content">
87+
<span>Actions</span>
88+
<span class="toggle-icon"></span>
89+
</h2>
90+
<div id="actions-content" class="section-content">
91+
<!-- Messages -->
92+
<div class="action-group">
93+
<h3>Messages</h3>
94+
<div class="action-row">
95+
<input type="text" id="message-text" value="Hello from debug app!" placeholder="Message text">
96+
<button id="send-message-text-btn">Send Text</button>
97+
</div>
98+
<div class="action-row">
99+
<button id="send-message-image-btn">Send Test Image</button>
100+
</div>
101+
</div>
102+
103+
<!-- Logging -->
104+
<div class="action-group">
105+
<h3>Logging</h3>
106+
<div class="action-row">
107+
<input type="text" id="log-data" value="Debug log data" placeholder="Log data">
108+
</div>
109+
<div class="action-row btn-row">
110+
<button id="log-debug-btn" class="btn-small">debug</button>
111+
<button id="log-info-btn" class="btn-small">info</button>
112+
<button id="log-warning-btn" class="btn-small">warning</button>
113+
<button id="log-error-btn" class="btn-small">error</button>
114+
</div>
115+
</div>
116+
117+
<!-- Model Context -->
118+
<div class="action-group">
119+
<h3>Model Context</h3>
120+
<div class="action-row">
121+
<input type="text" id="context-text" value="Current app state info" placeholder="Context text">
122+
<button id="update-context-text-btn">Update (Text)</button>
123+
</div>
124+
<div class="action-row">
125+
<button id="update-context-structured-btn">Update (Structured)</button>
126+
<button id="update-context-image-btn">Update (Image)</button>
127+
</div>
128+
</div>
129+
130+
<!-- Display Mode -->
131+
<div class="action-group">
132+
<h3>Display Mode</h3>
133+
<div class="action-row btn-row">
134+
<button id="display-inline-btn" class="btn-small">Inline</button>
135+
<button id="display-fullscreen-btn" class="btn-small">Fullscreen</button>
136+
<button id="display-pip-btn" class="btn-small">PiP</button>
137+
</div>
138+
</div>
139+
140+
<!-- Links -->
141+
<div class="action-group">
142+
<h3>Links</h3>
143+
<div class="action-row">
144+
<input type="url" id="link-url" value="https://modelcontextprotocol.io/" placeholder="URL">
145+
<button id="open-link-btn">Open Link</button>
146+
</div>
147+
</div>
148+
149+
<!-- Size Controls -->
150+
<div class="action-group">
151+
<h3>Size</h3>
152+
<div class="action-row">
153+
<label><input type="checkbox" id="auto-resize-toggle" checked> Auto-resize</label>
154+
</div>
155+
<div class="action-row btn-row">
156+
<button id="resize-200x100-btn" class="btn-small">200x100</button>
157+
<button id="resize-400x300-btn" class="btn-small">400x300</button>
158+
<button id="resize-800x600-btn" class="btn-small">800x600</button>
159+
</div>
160+
<div class="action-row">
161+
<span>Current: <code id="current-size">measuring...</code></span>
162+
</div>
163+
</div>
164+
165+
<!-- Server Tools -->
166+
<div class="action-group">
167+
<h3>Server Tools</h3>
168+
<div class="tool-config">
169+
<div class="config-row">
170+
<label>Content Type:</label>
171+
<select id="tool-content-type">
172+
<option value="text">text</option>
173+
<option value="image">image</option>
174+
<option value="audio">audio</option>
175+
<option value="resource">resource</option>
176+
<option value="resourceLink">resourceLink</option>
177+
<option value="mixed">mixed</option>
178+
</select>
179+
</div>
180+
<div class="config-row">
181+
<label><input type="checkbox" id="tool-multiple-blocks"> Multiple blocks</label>
182+
</div>
183+
<div class="config-row">
184+
<label><input type="checkbox" id="tool-structured-content" checked> Include structuredContent</label>
185+
</div>
186+
<div class="config-row">
187+
<label><input type="checkbox" id="tool-include-meta"> Include _meta</label>
188+
</div>
189+
<div class="config-row">
190+
<label><input type="checkbox" id="tool-simulate-error"> Simulate error</label>
191+
</div>
192+
<div class="config-row">
193+
<label>Delay (ms):</label>
194+
<input type="number" id="tool-delay-ms" value="0" min="0" max="10000" step="100">
195+
</div>
196+
</div>
197+
<div class="action-row">
198+
<button id="call-debug-tool-btn">Call debug-tool</button>
199+
</div>
200+
<div class="action-row">
201+
<button id="call-debug-refresh-btn">Call debug-refresh</button>
202+
</div>
203+
</div>
204+
205+
</div>
206+
</section>
207+
</main>
208+
<script type="module" src="/src/mcp-app.ts"></script>
209+
</body>
210+
</html>

0 commit comments

Comments
 (0)