Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions examples/debug-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Debug Server

A comprehensive testing/debugging tool for the MCP Apps SDK that exercises every capability, callback, and result format combination.

## Tools

### debug-tool

Configurable tool for testing all result variations:

| Parameter | Type | Default | Description |
| -------------------------- | ----------------------------------------------------------------------------------- | -------- | ------------------------------------------- |
| `contentType` | `"text"` \| `"image"` \| `"audio"` \| `"resource"` \| `"resourceLink"` \| `"mixed"` | `"text"` | Content block type to return |
| `multipleBlocks` | boolean | `false` | Return 3 content blocks |
| `includeStructuredContent` | boolean | `true` | Include structuredContent in result |
| `includeMeta` | boolean | `false` | Include \_meta in result |
| `largeInput` | string | - | Large text input (tests tool-input-partial) |
| `simulateError` | boolean | `false` | Return isError: true |
| `delayMs` | number | - | Delay before response (ms) |

### debug-refresh

App-only tool (hidden from model) for polling server state. Returns current timestamp and call counter.

## App UI

The debug app provides a dashboard with:

- **Event Log**: Real-time log of all SDK events with filtering
- **Host Info**: Context, capabilities, container dimensions, styles
- **Callback Status**: Table of all callbacks with call counts
- **Actions**: Buttons to test every SDK method:
- Send messages (text/image)
- Logging (debug/info/warning/error)
- Model context updates
- Display mode requests
- Link opening
- Resize controls
- Server tool calls
- File operations

## Usage

```bash
# Build
npm run --workspace examples/debug-server build

# Run standalone
npm run --workspace examples/debug-server serve

# Run with all examples
npm start
```

Then open `http://localhost:8080/basic-host/` and select "Debug MCP App Server" from the dropdown.
225 changes: 225 additions & 0 deletions examples/debug-server/mcp-app.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<title>Debug App</title>
</head>
<body>
<main class="main">
<!-- Section 1: Event Log -->
<section class="section">
<h2 class="section-header">
<span>Event Log</span>
<div class="header-actions">
<select id="log-filter">
<option value="all">All Events</option>
<option value="tool-input">tool-input</option>
<option value="tool-input-partial">tool-input-partial</option>
<option value="tool-result">tool-result</option>
<option value="tool-cancelled">tool-cancelled</option>
<option value="widget-state">widget-state</option>
<option value="host-context-changed">host-context-changed</option>
<option value="teardown">teardown</option>
<option value="call-tool">call-tool</option>
<option value="list-tools">list-tools</option>
<option value="error">error</option>
</select>
<button id="clear-log-btn" class="btn-small">Clear</button>
</div>
</h2>
<div id="event-log" class="event-log"></div>
</section>

<!-- Section 2: Host Info -->
<section class="section collapsible">
<h2 class="section-header" data-toggle="host-info-content">
<span>Host Info</span>
<span class="toggle-icon">▼</span>
</h2>
<div id="host-info-content" class="section-content">
<div class="info-grid">
<div class="info-group">
<h3>Context</h3>
<dl id="host-context-info"></dl>
</div>
<div class="info-group">
<h3>Capabilities</h3>
<dl id="host-capabilities-info"></dl>
</div>
<div class="info-group">
<h3>Container</h3>
<dl id="host-container-info"></dl>
</div>
<div class="info-group">
<h3>Styles Sample</h3>
<div id="host-styles-sample" class="styles-sample"></div>
</div>
</div>
</div>
</section>

<!-- Section 3: Callback Status -->
<section class="section collapsible">
<h2 class="section-header" data-toggle="callback-status-content">
<span>Callback Status</span>
<span class="toggle-icon">▼</span>
</h2>
<div id="callback-status-content" class="section-content">
<table class="callback-table">
<thead>
<tr>
<th>Callback</th>
<th>Registered</th>
<th>Count</th>
<th>Last Payload</th>
</tr>
</thead>
<tbody id="callback-table-body"></tbody>
</table>
</div>
</section>

<!-- Section 4: Action Buttons -->
<section class="section collapsible">
<h2 class="section-header" data-toggle="actions-content">
<span>Actions</span>
<span class="toggle-icon">▼</span>
</h2>
<div id="actions-content" class="section-content">
<!-- Messages -->
<div class="action-group">
<h3>Messages</h3>
<div class="action-row">
<input type="text" id="message-text" value="Hello from debug app!" placeholder="Message text">
<button id="send-message-text-btn">Send Text</button>
</div>
<div class="action-row">
<button id="send-message-image-btn">Send Test Image</button>
</div>
</div>

<!-- Logging -->
<div class="action-group">
<h3>Logging</h3>
<div class="action-row">
<input type="text" id="log-data" value="Debug log data" placeholder="Log data">
</div>
<div class="action-row btn-row">
<button id="log-debug-btn" class="btn-small">debug</button>
<button id="log-info-btn" class="btn-small">info</button>
<button id="log-warning-btn" class="btn-small">warning</button>
<button id="log-error-btn" class="btn-small">error</button>
</div>
</div>

<!-- Model Context -->
<div class="action-group">
<h3>Model Context</h3>
<div class="action-row">
<input type="text" id="context-text" value="Current app state info" placeholder="Context text">
<button id="update-context-text-btn">Update (Text)</button>
</div>
<div class="action-row">
<button id="update-context-structured-btn">Update (Structured)</button>
</div>
</div>

<!-- Display Mode -->
<div class="action-group">
<h3>Display Mode</h3>
<div class="action-row btn-row">
<button id="display-inline-btn" class="btn-small">Inline</button>
<button id="display-fullscreen-btn" class="btn-small">Fullscreen</button>
<button id="display-pip-btn" class="btn-small">PiP</button>
</div>
</div>

<!-- Links -->
<div class="action-group">
<h3>Links</h3>
<div class="action-row">
<input type="url" id="link-url" value="https://modelcontextprotocol.io/" placeholder="URL">
<button id="open-link-btn">Open Link</button>
</div>
</div>

<!-- Size Controls -->
<div class="action-group">
<h3>Size</h3>
<div class="action-row">
<label><input type="checkbox" id="auto-resize-toggle" checked> Auto-resize</label>
</div>
<div class="action-row btn-row">
<button id="resize-200x100-btn" class="btn-small">200x100</button>
<button id="resize-400x300-btn" class="btn-small">400x300</button>
<button id="resize-800x600-btn" class="btn-small">800x600</button>
</div>
<div class="action-row">
<span>Current: <code id="current-size">measuring...</code></span>
</div>
</div>

<!-- Server Tools -->
<div class="action-group">
<h3>Server Tools</h3>
<div class="tool-config">
<div class="config-row">
<label>Content Type:</label>
<select id="tool-content-type">
<option value="text">text</option>
<option value="image">image</option>
<option value="audio">audio</option>
<option value="resource">resource</option>
<option value="resourceLink">resourceLink</option>
<option value="mixed">mixed</option>
</select>
</div>
<div class="config-row">
<label><input type="checkbox" id="tool-multiple-blocks"> Multiple blocks</label>
</div>
<div class="config-row">
<label><input type="checkbox" id="tool-structured-content" checked> Include structuredContent</label>
</div>
<div class="config-row">
<label><input type="checkbox" id="tool-include-meta"> Include _meta</label>
</div>
<div class="config-row">
<label><input type="checkbox" id="tool-simulate-error"> Simulate error</label>
</div>
<div class="config-row">
<label>Delay (ms):</label>
<input type="number" id="tool-delay-ms" value="0" min="0" max="10000" step="100">
</div>
</div>
<div class="action-row">
<button id="call-debug-tool-btn">Call debug-tool</button>
</div>
<div class="action-row">
<button id="call-debug-refresh-btn">Call debug-refresh</button>
</div>
</div>

<!-- File Operations -->
<div class="action-group">
<h3>Files</h3>
<div class="action-row">
<input type="file" id="file-input">
</div>
<div class="action-row">
<button id="upload-file-btn">Upload File</button>
</div>
<div class="action-row">
<span>Last fileId: <code id="last-file-id">none</code></span>
</div>
<div class="action-row">
<button id="get-file-url-btn">Get File URL</button>
</div>
</div>
</div>
</section>
</main>
<script type="module" src="/src/mcp-app.ts"></script>
</body>
</html>
43 changes: 43 additions & 0 deletions examples/debug-server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "@modelcontextprotocol/server-debug",
"version": "0.4.1",
"type": "module",
"description": "Debug MCP App Server for testing all SDK capabilities",
"repository": {
"type": "git",
"url": "https://github.com/modelcontextprotocol/ext-apps",
"directory": "examples/debug-server"
},
"license": "MIT",
"main": "server.ts",
"files": [
"server.ts",
"server-utils.ts",
"dist"
],
"scripts": {
"build": "tsc --noEmit && cross-env INPUT=mcp-app.html vite build",
"watch": "cross-env INPUT=mcp-app.html vite build --watch",
"serve": "bun --watch server.ts",
"start": "cross-env NODE_ENV=development npm run build && npm run serve",
"dev": "cross-env NODE_ENV=development concurrently 'npm run watch' 'npm run serve'",
"prepublishOnly": "npm run build"
},
"dependencies": {
"@modelcontextprotocol/ext-apps": "^0.4.1",
"@modelcontextprotocol/sdk": "^1.24.0",
"zod": "^4.1.13"
},
"devDependencies": {
"@types/cors": "^2.8.19",
"@types/express": "^5.0.0",
"@types/node": "^22.0.0",
"concurrently": "^9.2.1",
"cors": "^2.8.5",
"cross-env": "^10.1.0",
"express": "^5.1.0",
"typescript": "^5.9.3",
"vite": "^6.0.0",
"vite-plugin-singlefile": "^2.3.0"
}
}
72 changes: 72 additions & 0 deletions examples/debug-server/server-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Shared utilities for running MCP servers with Streamable HTTP transport.
*/

import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import cors from "cors";
import type { Request, Response } from "express";

export interface ServerOptions {
port: number;
name?: string;
}

/**
* Starts an MCP server with Streamable HTTP transport in stateless mode.
*
* @param createServer - Factory function that creates a new McpServer instance per request.
* @param options - Server configuration options.
*/
export async function startServer(
createServer: () => McpServer,
options: ServerOptions,
): Promise<void> {
const { port, name = "MCP Server" } = options;

const app = createMcpExpressApp({ host: "0.0.0.0" });
app.use(cors());

app.all("/mcp", async (req: Request, res: Response) => {
const server = createServer();
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});

res.on("close", () => {
transport.close().catch(() => {});
server.close().catch(() => {});
});

try {
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error("MCP error:", error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: "2.0",
error: { code: -32603, message: "Internal server error" },
id: null,
});
}
}
});

const httpServer = app.listen(port, (err) => {
if (err) {
console.error("Failed to start server:", err);
process.exit(1);
}
console.log(`${name} listening on http://localhost:${port}/mcp`);
});

const shutdown = () => {
console.log("\nShutting down...");
httpServer.close(() => process.exit(0));
};

process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
}
Loading
Loading