Skip to content
Open
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"main": "dist/index.js",
"scripts": {
"dev": "tsx watch --inspect src/index.ts",
"dev:stdio": "tsx src/stdio.ts",
"dev:internal": "AUTH_MODE=internal tsx watch --inspect src/index.ts",
"dev:external": "concurrently \"AUTH_MODE=auth_server PORT=3001 BASE_URI=http://localhost:3001 tsx watch src/index.ts\" \"sleep 3 && AUTH_MODE=external AUTH_SERVER_URL=http://localhost:3001 tsx watch --inspect src/index.ts\"",
"build": "tsc && npm run copy-static && npm run build:apps",
Expand Down
77 changes: 77 additions & 0 deletions scripts/test-stdio.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/usr/bin/env npx tsx
/**
* Test script to verify the stdio server works correctly.
* Connects to the server, lists resources, and reads the MCP App resource.
*/
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

async function main() {
console.log("Starting stdio client test...\n");

const transport = new StdioClientTransport({
command: "npx",
args: ["tsx", "src/stdio.ts"],
});

const client = new Client({
name: "test-client",
version: "1.0.0",
});

await client.connect(transport);
console.log("Connected to server\n");

// Initialize
const serverInfo = client.getServerVersion();
console.log("Server info:", serverInfo);
console.log();

// List resources
console.log("Listing resources...");
const resources = await client.listResources();
console.log(`Found ${resources.resources.length} resources:`);
for (const resource of resources.resources) {
console.log(` - ${resource.name} (${resource.uri})`);
}
console.log();

// Find and read the MCP App resource
const mcpAppResource = resources.resources.find((r) =>
r.uri.startsWith("ui://")
);
if (mcpAppResource) {
console.log(`Reading MCP App resource: ${mcpAppResource.uri}`);
const content = await client.readResource({ uri: mcpAppResource.uri });
const textContent = content.contents[0];
if ("text" in textContent) {
console.log(` MIME type: ${textContent.mimeType}`);
console.log(` Content length: ${textContent.text.length} bytes`);
console.log(` First 200 chars: ${textContent.text.slice(0, 200)}...`);
}
console.log();
}

// List tools
console.log("Listing tools...");
const tools = await client.listTools();
console.log(`Found ${tools.tools.length} tools:`);
for (const tool of tools.tools) {
const meta = tool._meta as Record<string, unknown> | undefined;
const uiUri = meta?.["ui/resourceUri"];
console.log(
` - ${tool.name}${uiUri ? ` (UI: ${uiUri})` : ""}`
);
}
console.log();

console.log("All tests passed!");
await client.close();
// Give the server a moment to clean up before we exit
setTimeout(() => process.exit(0), 100);
}

main().catch((error) => {
console.error("Test failed:", error);
process.exit(1);
});
8 changes: 7 additions & 1 deletion src/modules/mcp/services/mcp.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import fs from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import {
CallToolRequestSchema,
CompleteRequestSchema,
Expand Down Expand Up @@ -293,7 +294,12 @@ export const createMcpServer = (): McpServerWrapper => {

// MCP Apps UI resources
if (uri === HELLO_WORLD_APP_URI) {
const distDir = path.join(import.meta.dirname, "../../../apps");
// Always resolve to dist/apps from project root, regardless of whether
// we're running from src/ (tsx dev) or dist/ (production)
// Path: {src,dist}/modules/mcp/services/ -> 4 levels up to project root
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const projectRoot = path.join(__dirname, "../../../..");
const distDir = path.join(projectRoot, "dist/apps");
const html = await fs.readFile(path.join(distDir, "mcp-app.html"), "utf-8");
return {
contents: [
Expand Down
36 changes: 36 additions & 0 deletions src/stdio.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env node
/**
* Stdio entrypoint for the MCP example server.
* This allows running the server locally via stdio transport.
*
* Usage:
* npx tsx src/stdio.ts
* # or after building:
* node dist/stdio.js
*/
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { createMcpServer } from "./modules/mcp/services/mcp.js";

async function main() {
const { server, cleanup } = createMcpServer();

const transport = new StdioServerTransport();

// Handle cleanup on exit
process.on("SIGINT", () => {
cleanup();
process.exit(0);
});

process.on("SIGTERM", () => {
cleanup();
process.exit(0);
});

await server.connect(transport);
}

main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});