Clean up examples (#182)
* Centralize example server transport logic in shared utils Move stdio/HTTP transport handling into `server-utils.ts` so example servers only need to call `startServer(createServer)`. Switch HTTP transport to stateless mode and remove legacy SSE endpoints. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Make example servers self-contained with local server-utils Copy `server-utils.ts` into each example's `src/` directory so examples can be used standalone without the shared directory. Updates imports from `"../shared/server-utils.js"` to `"./src/server-utils.js"`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add `integration-server` example for E2E testing Add dedicated `integration-server` example (duplicated from `basic-server-react`) to preserve E2E testing capabilities while allowing `basic-server-react` to be simplified as a minimal example. Changes: - Add `examples/integration-server` exercising SDK communication APIs - Update E2E tests to use `integration-server` instead of `basic-server-react` and `basic-server-vanillajs` - Use server name labels instead of indices for more robust test selection - Remove `basic-react.png` and `basic-vanillajs.png` golden snapshots 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Simplify `basic-server-*` examples - Clarify comments - Return plain text from `get-time` tool instead of JSON - Remove simulated cleanup delay from `onteardown` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
3222c87c56
commit
9e9f5f0210
1 changed files with 1748 additions and 475 deletions
|
|
@ -1,55 +1,52 @@
|
|||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "@modelcontextprotocol/ext-apps/server";
|
||||
import { startServer } from "../shared/server-utils.js";
|
||||
import { startServer } from "./src/server-utils.js";
|
||||
|
||||
const DIST_DIR = path.join(import.meta.dirname, "dist");
|
||||
const RESOURCE_URI = "ui://get-time/mcp-app.html";
|
||||
|
||||
/**
|
||||
* Creates a new MCP server instance with tools and resources registered.
|
||||
* Each HTTP session needs its own server instance because McpServer only supports one transport.
|
||||
*/
|
||||
function createServer(): McpServer {
|
||||
const server = new McpServer({
|
||||
name: "Basic MCP App Server (React-based)",
|
||||
name: "Basic MCP App Server (React)",
|
||||
version: "1.0.0",
|
||||
});
|
||||
|
||||
// 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.
|
||||
// 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.",
|
||||
inputSchema: {},
|
||||
_meta: { [RESOURCE_URI_META_KEY]: RESOURCE_URI },
|
||||
_meta: { [RESOURCE_URI_META_KEY]: resourceUri },
|
||||
},
|
||||
async (): Promise<CallToolResult> => {
|
||||
const time = new Date().toISOString();
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify({ time }) }],
|
||||
};
|
||||
return { content: [{ type: "text", text: time }] };
|
||||
},
|
||||
);
|
||||
|
||||
// Register the resource, which returns the bundled HTML/JavaScript for the UI.
|
||||
registerAppResource(server,
|
||||
RESOURCE_URI,
|
||||
RESOURCE_URI,
|
||||
resourceUri,
|
||||
resourceUri,
|
||||
{ mimeType: RESOURCE_MIME_TYPE },
|
||||
async (): Promise<ReadResourceResult> => {
|
||||
const html = await fs.readFile(path.join(DIST_DIR, "mcp-app.html"), "utf-8");
|
||||
|
||||
return {
|
||||
contents: [
|
||||
// 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 },
|
||||
{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html },
|
||||
],
|
||||
};
|
||||
},
|
||||
|
|
@ -58,16 +55,4 @@ function createServer(): McpServer {
|
|||
return server;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
if (process.argv.includes("--stdio")) {
|
||||
await createServer().connect(new StdioServerTransport());
|
||||
} else {
|
||||
const port = parseInt(process.env.PORT ?? "3101", 10);
|
||||
await startServer(createServer, { port, name: "Basic MCP App Server (React-based)" });
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
startServer(createServer);
|
||||
|
|
|
|||
Loading…
Reference in a new issue