Add sheet-music-server example
Demo MCP App showcasing ABC notation rendering and audio playback with: - Server-side ABC notation validation - Built-in audio player with play/pause and loop controls - Sheet music visualization using abcjs library 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e514e6c745
commit
012fb3260c
13 changed files with 709 additions and 36 deletions
84
examples/sheet-music-server/README.md
Normal file
84
examples/sheet-music-server/README.md
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# Example: Sheet Music Server
|
||||
|
||||
A demo MCP App that renders [ABC notation](https://en.wikipedia.org/wiki/ABC_notation) as sheet music with interactive audio playback using the [abcjs](https://www.abcjs.net/) library.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><a href="https://modelcontextprotocol.github.io/ext-apps/screenshots/sheet-music-server/01-twinkle-twinkle-little-star.png"><img src="https://modelcontextprotocol.github.io/ext-apps/screenshots/sheet-music-server/01-twinkle-twinkle-little-star.png" alt="Twinkle, Twinkle Little Star" width="100%"></a></td>
|
||||
<td><a href="https://modelcontextprotocol.github.io/ext-apps/screenshots/sheet-music-server/02-playing-on-repeat.png"><img src="https://modelcontextprotocol.github.io/ext-apps/screenshots/sheet-music-server/02-playing-on-repeat.png" alt="Playing on repeat" width="100%"></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Features
|
||||
|
||||
- **Audio Playback**: Built-in audio player with play/pause and loop controls
|
||||
- **Sheet Music Rendering**: Displays ABC notation as properly formatted sheet music
|
||||
|
||||
## Running
|
||||
|
||||
1. Install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. Build and start the server:
|
||||
|
||||
```bash
|
||||
npm run start:http # for Streamable HTTP transport
|
||||
# OR
|
||||
npm run start:stdio # for stdio transport
|
||||
```
|
||||
|
||||
3. View using the [`basic-host`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-host) example or another MCP Apps-compatible host.
|
||||
|
||||
### Tool Input
|
||||
|
||||
When calling the `play-sheet-music` tool, provide ABC notation:
|
||||
|
||||
```json
|
||||
{
|
||||
"abcNotation": "X:1\nT:C Major Scale\nM:4/4\nL:1/4\nK:C\nC D E F | G A B c |"
|
||||
}
|
||||
```
|
||||
|
||||
#### ABC Notation Examples
|
||||
|
||||
**C Major Scale:**
|
||||
|
||||
```abc
|
||||
X:1
|
||||
T:C Major Scale
|
||||
M:4/4
|
||||
L:1/4
|
||||
K:C
|
||||
C D E F | G A B c |
|
||||
```
|
||||
|
||||
**Twinkle, Twinkle Little Star:**
|
||||
|
||||
```abc
|
||||
X:1
|
||||
T:Twinkle, Twinkle Little Star
|
||||
M:4/4
|
||||
L:1/4
|
||||
K:C
|
||||
C C G G | A A G2 | F F E E | D D C2 |
|
||||
G G F F | E E D2 | G G F F | E E D2 |
|
||||
C C G G | A A G2 | F F E E | D D C2 |
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Server (`server.ts`)
|
||||
|
||||
Exposes a single `play-sheet-music` tool that accepts:
|
||||
|
||||
- `abcNotation`: ABC notation string to render
|
||||
|
||||
The tool validates the ABC notation server-side using the abcjs parser and returns any parse errors. The actual rendering happens client-side when the UI receives the tool input.
|
||||
|
||||
### App (`src/mcp-app.ts`)
|
||||
|
||||
- Receives ABC notation via `ontoolinput` handler
|
||||
- Uses abcjs for audio playback controls and sheet music rendering (in `renderAbc()`)
|
||||
27
examples/sheet-music-server/mcp-app.html
Normal file
27
examples/sheet-music-server/mcp-app.html
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<!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>Sheet Music Viewer</title>
|
||||
</head>
|
||||
<body>
|
||||
<main class="main">
|
||||
<header class="header">
|
||||
<div id="audio-controls" class="audio-controls abcjs-large">
|
||||
<!-- SynthController will inject audio controls here -->
|
||||
</div>
|
||||
<span id="status" class="status">Waiting for notation...</span>
|
||||
</header>
|
||||
|
||||
<section class="sheet-section">
|
||||
<div id="sheet-music" class="sheet-music-container">
|
||||
<!-- ABC notation will be rendered here by abcjs -->
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script type="module" src="./src/mcp-app.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
34
examples/sheet-music-server/package.json
Normal file
34
examples/sheet-music-server/package.json
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "sheet-music-server",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc --noEmit && cross-env INPUT=mcp-app.html vite build",
|
||||
"watch": "cross-env INPUT=mcp-app.html vite build --watch",
|
||||
"serve:http": "bun server.ts",
|
||||
"serve:stdio": "bun server.ts --stdio",
|
||||
"start": "npm run start:http",
|
||||
"start:http": "cross-env NODE_ENV=development npm run build && npm run serve:http",
|
||||
"start:stdio": "cross-env NODE_ENV=development npm run build && npm run serve:stdio",
|
||||
"dev": "cross-env NODE_ENV=development concurrently 'npm run watch' 'npm run serve:http'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/ext-apps": "../..",
|
||||
"@modelcontextprotocol/sdk": "^1.24.0",
|
||||
"abcjs": "^6.4.4",
|
||||
"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": "^7.0.3",
|
||||
"express": "^5.1.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^6.0.0",
|
||||
"vite-plugin-singlefile": "^2.3.0"
|
||||
}
|
||||
}
|
||||
119
examples/sheet-music-server/server.ts
Normal file
119
examples/sheet-music-server/server.ts
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import type {
|
||||
CallToolResult,
|
||||
ReadResourceResult,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { z } from "zod";
|
||||
import ABCJS from "abcjs";
|
||||
import {
|
||||
RESOURCE_MIME_TYPE,
|
||||
RESOURCE_URI_META_KEY,
|
||||
registerAppResource,
|
||||
registerAppTool,
|
||||
} from "@modelcontextprotocol/ext-apps/server";
|
||||
import { startServer } from "./src/server-utils.js";
|
||||
|
||||
const DIST_DIR = path.join(import.meta.dirname, "dist");
|
||||
|
||||
const DEFAULT_ABC_NOTATION_INPUT = `X:1
|
||||
T:Twinkle, Twinkle Little Star
|
||||
M:4/4
|
||||
L:1/4
|
||||
K:C
|
||||
C C G G | A A G2 | F F E E | D D C2 |
|
||||
G G F F | E E D2 | G G F F | E E D2 |
|
||||
C C G G | A A G2 | F F E E | D D C2 |`;
|
||||
|
||||
/**
|
||||
* Creates a new MCP server instance with the sheet music tool and resource.
|
||||
*/
|
||||
function createServer(): McpServer {
|
||||
const server = new McpServer({
|
||||
name: "Sheet Music Server",
|
||||
version: "1.0.0",
|
||||
});
|
||||
|
||||
const resourceUri = "ui://sheet-music/mcp-app.html";
|
||||
|
||||
// Register the play-sheet-music tool.
|
||||
// Validates ABC notation server-side, then the client renders via ontoolinput.
|
||||
registerAppTool(
|
||||
server,
|
||||
"play-sheet-music",
|
||||
{
|
||||
title: "Play Sheet Music",
|
||||
description:
|
||||
"Plays music from ABC notation with audio playback and visual sheet music. " +
|
||||
"Use this to compose original songs (for birthdays, holidays, or any occasion) " +
|
||||
"or perform well-known tunes (folk songs, nursery rhymes, hymns, classical melodies). " +
|
||||
"For accurate renditions of well-known tunes, look up the ABC notation from " +
|
||||
"abcnotation.com or thesession.org rather than recalling from memory.",
|
||||
inputSchema: z.object({
|
||||
abcNotation: z
|
||||
.string()
|
||||
.default(DEFAULT_ABC_NOTATION_INPUT)
|
||||
.describe(
|
||||
"ABC notation string to render as sheet music with audio playback",
|
||||
),
|
||||
}),
|
||||
_meta: { [RESOURCE_URI_META_KEY]: resourceUri },
|
||||
},
|
||||
async ({ abcNotation }): Promise<CallToolResult> => {
|
||||
// Validate ABC notation using abcjs parser
|
||||
const [{ warnings }] = ABCJS.parseOnly(abcNotation);
|
||||
|
||||
// Check for parse warnings (abcjs reports errors as warnings)
|
||||
if (warnings && warnings.length > 0) {
|
||||
// Strip HTML markup from warning messages
|
||||
const messages = warnings.map((w) => w.replace(/<[^>]*>/g, ""));
|
||||
const error = `Invalid ABC notation:\n${messages.join("\n")}`;
|
||||
return {
|
||||
isError: true,
|
||||
content: [{ type: "text", text: error }],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{ type: "text", text: "Input parsed successfully." }],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// Register the UI resource that serves the bundled HTML/JS/CSS.
|
||||
registerAppResource(
|
||||
server,
|
||||
resourceUri,
|
||||
resourceUri,
|
||||
{ mimeType: RESOURCE_MIME_TYPE, description: "Sheet Music Viewer UI" },
|
||||
async (): Promise<ReadResourceResult> => {
|
||||
const html = await fs.readFile(
|
||||
path.join(DIST_DIR, "mcp-app.html"),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: resourceUri,
|
||||
mimeType: RESOURCE_MIME_TYPE,
|
||||
text: html,
|
||||
_meta: {
|
||||
ui: {
|
||||
csp: {
|
||||
// Allow loading soundfonts for audio playback
|
||||
connectDomains: ["https://paulrosen.github.io"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
startServer(createServer);
|
||||
12
examples/sheet-music-server/src/global.css
Normal file
12
examples/sheet-music-server/src/global.css
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 1em;
|
||||
}
|
||||
87
examples/sheet-music-server/src/mcp-app.css
Normal file
87
examples/sheet-music-server/src/mcp-app.css
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
:root {
|
||||
--color-bg: #ffffff;
|
||||
--color-text: #1f2937;
|
||||
--color-text-muted: #6b7280;
|
||||
--color-primary: #2563eb;
|
||||
--color-success: #10b981;
|
||||
--color-danger: #ef4444;
|
||||
--color-card-bg: #f9fafb;
|
||||
--color-border: #e5e7eb;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-bg: #111827;
|
||||
--color-text: #f9fafb;
|
||||
--color-text-muted: #9ca3af;
|
||||
--color-primary: #3b82f6;
|
||||
--color-success: #34d399;
|
||||
--color-danger: #f87171;
|
||||
--color-card-bg: #1f2937;
|
||||
--color-border: #374151;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.main {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
#audio-controls:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#audio-controls:not(:empty) ~ #status {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.audio-controls {
|
||||
width: 100%;
|
||||
|
||||
.abcjs-inline-audio {
|
||||
border-radius: 8px;
|
||||
|
||||
/* Make loop button active state more visible */
|
||||
.abcjs-midi-loop.abcjs-pushed {
|
||||
background-color: var(--color-success) !important;
|
||||
border-color: var(--color-success) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.status {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.status.error {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
|
||||
/* Sheet Music Section */
|
||||
.sheet-section {
|
||||
background: var(--color-card-bg);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
border: 1px solid var(--color-border);
|
||||
min-height: 500px;
|
||||
|
||||
.sheet-music-container {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
120
examples/sheet-music-server/src/mcp-app.ts
Normal file
120
examples/sheet-music-server/src/mcp-app.ts
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
/**
|
||||
* @file Sheet Music App - renders ABC notation with abcjs and provides audio playback
|
||||
*/
|
||||
import { App } from "@modelcontextprotocol/ext-apps";
|
||||
import ABCJS from "abcjs";
|
||||
import "abcjs/abcjs-audio.css";
|
||||
import "./global.css";
|
||||
import "./mcp-app.css";
|
||||
|
||||
const log = {
|
||||
info: console.log.bind(console, "[APP]"),
|
||||
error: console.error.bind(console, "[APP]"),
|
||||
};
|
||||
|
||||
// State management
|
||||
interface AppState {
|
||||
visualObj: ABCJS.TuneObject[] | null;
|
||||
synthControl: ABCJS.SynthObjectController | null;
|
||||
}
|
||||
|
||||
const state: AppState = {
|
||||
visualObj: null,
|
||||
synthControl: null,
|
||||
};
|
||||
|
||||
// DOM element references
|
||||
const mainEl = document.querySelector(".main") as HTMLElement;
|
||||
const statusEl = document.getElementById("status")!;
|
||||
const sheetMusicEl = document.getElementById("sheet-music")!;
|
||||
const audioControlsEl = document.getElementById("audio-controls")!;
|
||||
|
||||
/**
|
||||
* Updates the status display.
|
||||
*/
|
||||
function setStatus(text: string, isError = false): void {
|
||||
statusEl.textContent = text;
|
||||
statusEl.classList.toggle("error", isError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders ABC notation to sheet music and sets up audio playback
|
||||
*/
|
||||
async function renderAbc(abcNotation: string): Promise<void> {
|
||||
try {
|
||||
setStatus("Rendering...");
|
||||
|
||||
// Clear previous content
|
||||
sheetMusicEl.innerHTML = "";
|
||||
audioControlsEl.innerHTML = "";
|
||||
|
||||
// Render the sheet music visually
|
||||
state.visualObj = ABCJS.renderAbc(sheetMusicEl, abcNotation, {
|
||||
responsive: "resize",
|
||||
add_classes: true,
|
||||
});
|
||||
|
||||
if (!state.visualObj || state.visualObj.length === 0) {
|
||||
throw new Error("Failed to parse music notation");
|
||||
}
|
||||
|
||||
if (!ABCJS.synth.supportsAudio()) {
|
||||
throw new Error("Audio not supported in this browser");
|
||||
}
|
||||
|
||||
// Create synth controller with UI controls
|
||||
state.synthControl = new ABCJS.synth.SynthController();
|
||||
state.synthControl.load(audioControlsEl, null, {
|
||||
displayLoop: true,
|
||||
// displayRestart: true,
|
||||
displayPlay: true,
|
||||
displayProgress: true,
|
||||
// displayWarp: true,
|
||||
});
|
||||
|
||||
// Connect synth to the rendered tune
|
||||
await state.synthControl.setTune(state.visualObj[0], false, {});
|
||||
|
||||
setStatus("Ready to play!");
|
||||
} catch (error) {
|
||||
log.error("Render error:", error);
|
||||
setStatus(`Error: ${(error as Error).message}`, true);
|
||||
audioControlsEl.innerHTML = "";
|
||||
}
|
||||
}
|
||||
|
||||
// Create app instance
|
||||
const app = new App({ name: "Sheet Music App", version: "1.0.0" });
|
||||
|
||||
// Handle tool input - receives ABC notation from the host
|
||||
app.ontoolinput = (params) => {
|
||||
log.info("Received tool input:", params);
|
||||
|
||||
const abcNotation = params.arguments?.abcNotation as string | undefined;
|
||||
|
||||
if (abcNotation) {
|
||||
renderAbc(abcNotation);
|
||||
} else {
|
||||
setStatus("No ABC notation provided", true);
|
||||
}
|
||||
};
|
||||
|
||||
// Error handler
|
||||
app.onerror = log.error;
|
||||
|
||||
app.onhostcontextchanged = (params) => {
|
||||
if (params.safeAreaInsets) {
|
||||
mainEl.style.paddingTop = `${params.safeAreaInsets.top}px`;
|
||||
mainEl.style.paddingRight = `${params.safeAreaInsets.right}px`;
|
||||
mainEl.style.paddingBottom = `${params.safeAreaInsets.bottom}px`;
|
||||
mainEl.style.paddingLeft = `${params.safeAreaInsets.left}px`;
|
||||
}
|
||||
};
|
||||
|
||||
// Connect to host
|
||||
app.connect().then(() => {
|
||||
const ctx = app.getHostContext();
|
||||
if (ctx) {
|
||||
app.onhostcontextchanged(ctx);
|
||||
}
|
||||
});
|
||||
110
examples/sheet-music-server/src/server-utils.ts
Normal file
110
examples/sheet-music-server/src/server-utils.ts
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
/**
|
||||
* Shared utilities for running MCP servers with various transports.
|
||||
*/
|
||||
|
||||
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
||||
import cors from "cors";
|
||||
import type { Request, Response } from "express";
|
||||
|
||||
/**
|
||||
* Starts an MCP server using the appropriate transport based on command-line arguments.
|
||||
*
|
||||
* If `--stdio` is passed, uses stdio transport. Otherwise, uses Streamable HTTP transport.
|
||||
*
|
||||
* @param createServer - Factory function that creates a new McpServer instance.
|
||||
*/
|
||||
export async function startServer(
|
||||
createServer: () => McpServer,
|
||||
): Promise<void> {
|
||||
try {
|
||||
if (process.argv.includes("--stdio")) {
|
||||
await startStdioServer(createServer);
|
||||
} else {
|
||||
await startStreamableHttpServer(createServer);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an MCP server with stdio transport.
|
||||
*
|
||||
* @param createServer - Factory function that creates a new McpServer instance.
|
||||
*/
|
||||
export async function startStdioServer(
|
||||
createServer: () => McpServer,
|
||||
): Promise<void> {
|
||||
await createServer().connect(new StdioServerTransport());
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an MCP server with Streamable HTTP transport in stateless mode.
|
||||
*
|
||||
* Each request creates a fresh server and transport instance, which are
|
||||
* closed when the response ends (no session tracking).
|
||||
*
|
||||
* The server listens on the port specified by the PORT environment variable,
|
||||
* defaulting to 3001 if not set.
|
||||
*
|
||||
* @param createServer - Factory function that creates a new McpServer instance per request.
|
||||
*/
|
||||
export async function startStreamableHttpServer(
|
||||
createServer: () => McpServer,
|
||||
): Promise<void> {
|
||||
const port = parseInt(process.env.PORT ?? "3001", 10);
|
||||
|
||||
// Express app - bind to all interfaces for development/testing
|
||||
const expressApp = createMcpExpressApp({ host: "0.0.0.0" });
|
||||
expressApp.use(cors());
|
||||
|
||||
expressApp.all("/mcp", async (req: Request, res: Response) => {
|
||||
// Create fresh server and transport for each request (stateless mode)
|
||||
const server = createServer();
|
||||
const transport = new StreamableHTTPServerTransport({
|
||||
sessionIdGenerator: undefined,
|
||||
});
|
||||
|
||||
// Clean up when response ends
|
||||
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 { promise, resolve, reject } = Promise.withResolvers<void>();
|
||||
|
||||
const httpServer = expressApp.listen(port, (err?: Error) => {
|
||||
if (err) return reject(err);
|
||||
console.log(`Server listening on http://localhost:${port}/mcp`);
|
||||
resolve();
|
||||
});
|
||||
|
||||
const shutdown = () => {
|
||||
console.log("\nShutting down...");
|
||||
httpServer.close(() => process.exit(0));
|
||||
};
|
||||
|
||||
process.on("SIGINT", shutdown);
|
||||
process.on("SIGTERM", shutdown);
|
||||
|
||||
return promise;
|
||||
}
|
||||
19
examples/sheet-music-server/tsconfig.json
Normal file
19
examples/sheet-music-server/tsconfig.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src", "server.ts"]
|
||||
}
|
||||
24
examples/sheet-music-server/vite.config.ts
Normal file
24
examples/sheet-music-server/vite.config.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { defineConfig } from "vite";
|
||||
import { viteSingleFile } from "vite-plugin-singlefile";
|
||||
|
||||
const INPUT = process.env.INPUT;
|
||||
if (!INPUT) {
|
||||
throw new Error("INPUT environment variable is not set");
|
||||
}
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV === "development";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [viteSingleFile()],
|
||||
build: {
|
||||
sourcemap: isDevelopment ? "inline" : undefined,
|
||||
cssMinify: !isDevelopment,
|
||||
minify: !isDevelopment,
|
||||
|
||||
rollupOptions: {
|
||||
input: INPUT,
|
||||
},
|
||||
outDir: "dist",
|
||||
emptyOutDir: false,
|
||||
},
|
||||
});
|
||||
108
package-lock.json
generated
108
package-lock.json
generated
|
|
@ -306,7 +306,7 @@
|
|||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/ext-apps": "../..",
|
||||
"@modelcontextprotocol/sdk": "^1.22.0",
|
||||
"@modelcontextprotocol/sdk": "^1.24.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"zod": "^4.1.13"
|
||||
|
|
@ -318,7 +318,6 @@
|
|||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"bun": "^1.3.2",
|
||||
"concurrently": "^9.2.1",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^5.1.0",
|
||||
|
|
@ -386,6 +385,63 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"examples/sheet-music-server": {
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/ext-apps": "../..",
|
||||
"@modelcontextprotocol/sdk": "^1.24.0",
|
||||
"abcjs": "^6.4.4",
|
||||
"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": "^7.0.3",
|
||||
"express": "^5.1.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^6.0.0",
|
||||
"vite-plugin-singlefile": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"examples/sheet-music-server/node_modules/@types/node": {
|
||||
"version": "22.19.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz",
|
||||
"integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"examples/sheet-music-server/node_modules/cross-env": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
|
||||
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"cross-env": "src/bin/cross-env.js",
|
||||
"cross-env-shell": "src/bin/cross-env-shell.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.14",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1"
|
||||
}
|
||||
},
|
||||
"examples/sheet-music-server/node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"examples/system-monitor-server": {
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
|
|
@ -2394,6 +2450,16 @@
|
|||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/abcjs": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/abcjs/-/abcjs-6.5.2.tgz",
|
||||
"integrity": "sha512-XLDZPy/4TZbOqPsLwuu0Umsl79NTAcObEkboPxdYZXI8/fU6PNh59SAnkZOnEPVbyT8EXfBUjgNoe/uKd3T0xQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/paulrosen"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
||||
|
|
@ -2685,40 +2751,6 @@
|
|||
"resolved": "examples/budget-allocator-server",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/bun": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/bun/-/bun-1.3.5.tgz",
|
||||
"integrity": "sha512-c1YHIGUfgvYPJmLug5QiLzNWlX2Dg7X/67JWu1Va+AmMXNXzC/KQn2lgQ7rD+n1u1UqDpJMowVGGxTNpbPydNw==",
|
||||
"cpu": [
|
||||
"arm64",
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"os": [
|
||||
"darwin",
|
||||
"linux",
|
||||
"win32"
|
||||
],
|
||||
"bin": {
|
||||
"bun": "bin/bun.exe",
|
||||
"bunx": "bin/bunx.exe"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@oven/bun-darwin-aarch64": "1.3.5",
|
||||
"@oven/bun-darwin-x64": "1.3.5",
|
||||
"@oven/bun-darwin-x64-baseline": "1.3.5",
|
||||
"@oven/bun-linux-aarch64": "1.3.5",
|
||||
"@oven/bun-linux-aarch64-musl": "1.3.5",
|
||||
"@oven/bun-linux-x64": "1.3.5",
|
||||
"@oven/bun-linux-x64-baseline": "1.3.5",
|
||||
"@oven/bun-linux-x64-musl": "1.3.5",
|
||||
"@oven/bun-linux-x64-musl-baseline": "1.3.5",
|
||||
"@oven/bun-windows-x64": "1.3.5",
|
||||
"@oven/bun-windows-x64-baseline": "1.3.5"
|
||||
}
|
||||
},
|
||||
"node_modules/bun-types": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.3.tgz",
|
||||
|
|
@ -5656,6 +5688,10 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/sheet-music-server": {
|
||||
"resolved": "examples/sheet-music-server",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/shell-quote": {
|
||||
"version": "1.8.3",
|
||||
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ const SERVERS = [
|
|||
{ key: "cohort-heatmap", name: "Cohort Heatmap Server" },
|
||||
{ key: "customer-segmentation", name: "Customer Segmentation Server" },
|
||||
{ key: "scenario-modeler", name: "SaaS Scenario Modeler" },
|
||||
{ key: "sheet-music", name: "Sheet Music Server" },
|
||||
{ key: "system-monitor", name: "System Monitor Server" },
|
||||
{ key: "threejs", name: "Three.js Server" },
|
||||
{ key: "wiki-explorer", name: "Wiki Explorer" },
|
||||
|
|
|
|||
BIN
tests/e2e/servers.spec.ts-snapshots/sheet-music.png
Normal file
BIN
tests/e2e/servers.spec.ts-snapshots/sheet-music.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
Loading…
Reference in a new issue