@counterpoint-studio/audio-file-mcp-app 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server";
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+ import * as z from "zod";
8
+ import { normalizeIncomingPath } from "./path-utils.js";
9
+ import { asScalar, parseNonNegInt } from "./range-params.js";
10
+ const server = new McpServer({
11
+ name: "Audio File MCP App",
12
+ version: "1.0.0",
13
+ });
14
+ const resourceUri = "ui://ctpt.co/audio-file/mcp-app.html";
15
+ const regionSchema = z.object({
16
+ startSeconds: z.number().min(0).finite(),
17
+ endSeconds: z.number().min(0).finite(),
18
+ });
19
+ let callSeq = 0;
20
+ registerAppTool(server, "display_audio_file", {
21
+ title: "Display audio file",
22
+ description: "Display a UI for an audio file, providing the user with playback, metadata, and statistics. Use when the user specifically asks to hear or see an audio file, or when otherwise clear from context that one would be helpful.",
23
+ inputSchema: z.object({
24
+ path: z
25
+ .string()
26
+ .describe("Absolute path to an audio file on the user's machine"),
27
+ playheadSeconds: z
28
+ .number()
29
+ .min(0)
30
+ .finite()
31
+ .optional()
32
+ .describe("Optional initial playhead position in seconds (decimal)"),
33
+ region: regionSchema
34
+ .optional()
35
+ .describe("Optional initial selected/highlighted region in seconds (decimal)"),
36
+ }),
37
+ _meta: { ui: { resourceUri } },
38
+ }, async ({ path, playheadSeconds, region }) => {
39
+ const normalized = normalizeIncomingPath(path);
40
+ if (!normalized) {
41
+ throw new Error("Path parameter is required");
42
+ }
43
+ const stat = await fs.stat(normalized);
44
+ const seq = ++callSeq;
45
+ const createdAt = Date.now();
46
+ const structuredContent = {
47
+ path: normalized,
48
+ createdAt,
49
+ seq,
50
+ sizeBytes: stat.size,
51
+ mtimeMs: stat.mtimeMs,
52
+ };
53
+ if (playheadSeconds !== undefined) {
54
+ structuredContent.playheadSeconds = playheadSeconds;
55
+ }
56
+ if (region !== undefined && region.endSeconds > region.startSeconds) {
57
+ structuredContent.region = region;
58
+ }
59
+ return {
60
+ content: [{ type: "text", text: normalized }],
61
+ structuredContent,
62
+ };
63
+ });
64
+ registerAppResource(server, resourceUri, resourceUri, {
65
+ mimeType: RESOURCE_MIME_TYPE,
66
+ _meta: { ui: { prefersBorder: false } },
67
+ }, async () => {
68
+ const html = await fs.readFile(path.join(import.meta.dirname, "..", "..", "dist", "mcp-app.html"), "utf-8");
69
+ return {
70
+ contents: [
71
+ { uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html },
72
+ ],
73
+ };
74
+ });
75
+ const MAX_CHUNK_BYTES = 8 * 1024 * 1024;
76
+ // Base64 is returned in the `text` field rather than `blob` because Goose's
77
+ // MCP-Apps host only forwards `text` resource content to the iframe.
78
+ server.registerResource("audiofile-range", new ResourceTemplate("audiofile-range://{path}/{start}/{length}", {
79
+ list: undefined,
80
+ }), {
81
+ description: "Byte range of a local audio file as base64 in `text`; path/start/length are URL-encoded.",
82
+ mimeType: "application/octet-stream;encoding=base64",
83
+ }, async (uri, { path, start, length }) => {
84
+ const rawPath = asScalar(path);
85
+ if (!rawPath)
86
+ throw new Error("Path parameter is required");
87
+ const pathStr = normalizeIncomingPath(decodeURIComponent(rawPath));
88
+ if (!pathStr)
89
+ throw new Error("Path parameter is required");
90
+ const startNum = parseNonNegInt(asScalar(start));
91
+ const lengthNum = parseNonNegInt(asScalar(length));
92
+ if (startNum === null || lengthNum === null) {
93
+ throw new Error("start and length must be non-negative integers");
94
+ }
95
+ if (lengthNum === 0 || lengthNum > MAX_CHUNK_BYTES) {
96
+ throw new Error(`length must be in (0, ${MAX_CHUNK_BYTES}]`);
97
+ }
98
+ const fh = await fs.open(pathStr, "r");
99
+ try {
100
+ const buf = Buffer.allocUnsafe(lengthNum);
101
+ const { bytesRead } = await fh.read(buf, 0, lengthNum, startNum);
102
+ const slice = bytesRead === lengthNum ? buf : buf.subarray(0, bytesRead);
103
+ return {
104
+ contents: [
105
+ {
106
+ uri: uri.href,
107
+ mimeType: "application/octet-stream;encoding=base64",
108
+ text: slice.toString("base64"),
109
+ },
110
+ ],
111
+ };
112
+ }
113
+ finally {
114
+ await fh.close();
115
+ }
116
+ });
117
+ async function main() {
118
+ const transport = new StdioServerTransport();
119
+ await server.connect(transport);
120
+ }
121
+ main();
122
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ const QUOTE_PAIRS = [
2
+ ["'", "'"],
3
+ ['"', '"'],
4
+ ["`", "`"],
5
+ ["‘", "’"],
6
+ ["“", "”"],
7
+ ["‚", "’"],
8
+ ["„", "”"],
9
+ ];
10
+ function stripSurroundingQuotes(s) {
11
+ if (s.length < 2)
12
+ return s;
13
+ const first = s[0];
14
+ const last = s[s.length - 1];
15
+ for (const [open, close] of QUOTE_PAIRS) {
16
+ if (first === open && last === close) {
17
+ return s.slice(1, -1);
18
+ }
19
+ }
20
+ return s;
21
+ }
22
+ function stripFileScheme(s) {
23
+ const match = /^file:\/\/(.*)$/i.exec(s);
24
+ if (!match)
25
+ return s;
26
+ let rest = match[1];
27
+ try {
28
+ rest = decodeURIComponent(rest);
29
+ }
30
+ catch {
31
+ // Leave undecodable input as-is rather than throwing.
32
+ }
33
+ // file:///C:/... → C:/... (Windows-style absolute path)
34
+ const winMatch = /^\/([A-Za-z]:[\/\\].*)$/.exec(rest);
35
+ if (winMatch)
36
+ return winMatch[1];
37
+ return rest;
38
+ }
39
+ export function normalizeIncomingPath(raw) {
40
+ let current = raw;
41
+ // Loop until a pass changes nothing — handles nested wrappers
42
+ // like ` ' "/path" ' ` and `file://` wrapped in quotes.
43
+ for (let i = 0; i < 8; i++) {
44
+ const before = current;
45
+ current = current.trim();
46
+ current = stripSurroundingQuotes(current);
47
+ current = stripFileScheme(current);
48
+ if (current === before)
49
+ break;
50
+ }
51
+ return current;
52
+ }
53
+ //# sourceMappingURL=path-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-utils.js","sourceRoot":"","sources":["../../src/server/path-utils.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,GAA6C;IAC1D,CAAC,GAAG,EAAE,GAAG,CAAC;IACV,CAAC,GAAG,EAAE,GAAG,CAAC;IACV,CAAC,GAAG,EAAE,GAAG,CAAC;IACV,CAAC,GAAG,EAAE,GAAG,CAAC;IACV,CAAC,GAAG,EAAE,GAAG,CAAC;IACV,CAAC,GAAG,EAAE,GAAG,CAAC;IACV,CAAC,GAAG,EAAE,GAAG,CAAC;CACb,CAAC;AAEF,SAAS,sBAAsB,CAAC,CAAS;IACrC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IAC3B,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACnB,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC7B,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC;QACtC,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;YACnC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC1B,CAAC;IACL,CAAC;IACD,OAAO,CAAC,CAAC;AACb,CAAC;AAED,SAAS,eAAe,CAAC,CAAS;IAC9B,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACzC,IAAI,CAAC,KAAK;QAAE,OAAO,CAAC,CAAC;IACrB,IAAI,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACpB,IAAI,CAAC;QACD,IAAI,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACL,sDAAsD;IAC1D,CAAC;IACD,yDAAyD;IACzD,MAAM,QAAQ,GAAG,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtD,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC;IACjC,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,GAAW;IAC7C,IAAI,OAAO,GAAG,GAAG,CAAC;IAClB,8DAA8D;IAC9D,0DAA0D;IAC1D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,OAAO,CAAC;QACvB,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QACzB,OAAO,GAAG,sBAAsB,CAAC,OAAO,CAAC,CAAC;QAC1C,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;QACnC,IAAI,OAAO,KAAK,MAAM;YAAE,MAAM;IAClC,CAAC;IACD,OAAO,OAAO,CAAC;AACnB,CAAC"}
@@ -0,0 +1,18 @@
1
+ export function asScalar(v) {
2
+ if (Array.isArray(v))
3
+ return v.length > 0 ? v[0] : null;
4
+ return v ?? null;
5
+ }
6
+ const INT_RE = /^\d+$/;
7
+ const MAX_SAFE = Number.MAX_SAFE_INTEGER;
8
+ export function parseNonNegInt(s) {
9
+ if (s === null || s.length === 0)
10
+ return null;
11
+ if (!INT_RE.test(s))
12
+ return null;
13
+ const n = Number(s);
14
+ if (!Number.isFinite(n) || n < 0 || n > MAX_SAFE)
15
+ return null;
16
+ return n;
17
+ }
18
+ //# sourceMappingURL=range-params.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"range-params.js","sourceRoot":"","sources":["../../src/server/range-params.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,QAAQ,CAAC,CAAgC;IACrD,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACxD,OAAO,CAAC,IAAI,IAAI,CAAC;AACrB,CAAC;AAED,MAAM,MAAM,GAAG,OAAO,CAAC;AACvB,MAAM,QAAQ,GAAG,MAAM,CAAC,gBAAgB,CAAC;AAEzC,MAAM,UAAU,cAAc,CAAC,CAAgB;IAC3C,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACjC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACpB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC9D,OAAO,CAAC,CAAC;AACb,CAAC"}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@counterpoint-studio/audio-file-mcp-app",
3
+ "version": "1.0.0",
4
+ "description": "An MCP app for inspecting audio files in audio workflows. Playback, metadata, statistics",
5
+ "keywords": [
6
+ "audio",
7
+ "mcp",
8
+ "mcp-app",
9
+ "mcp-server",
10
+ "model-context-protocol"
11
+ ],
12
+ "license": "ISC",
13
+ "author": "Tero Parviainen <tero@teropa.info>",
14
+ "homepage": "https://github.com/counterpoint-studio/audio-file-mcp-app#readme",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/counterpoint-studio/audio-file-mcp-app.git"
18
+ },
19
+ "bugs": "https://github.com/counterpoint-studio/audio-file-mcp-app/issues",
20
+ "type": "module",
21
+ "bin": {
22
+ "audio-file-mcp-app": "dist/server/app.js"
23
+ },
24
+ "files": [
25
+ "dist/server",
26
+ "dist/mcp-app.html",
27
+ "README.md",
28
+ "LICENSE"
29
+ ],
30
+ "engines": {
31
+ "node": ">=20"
32
+ },
33
+ "dependencies": {
34
+ "@modelcontextprotocol/ext-apps": "^1.7.1",
35
+ "@modelcontextprotocol/sdk": "^1.29.0",
36
+ "audio-type": "^2.4.1",
37
+ "mediabunny": "^1.45.2",
38
+ "zod": "^4.4.3"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^25.6.2",
42
+ "tsx": "^4.21.0",
43
+ "typescript": "^6.0.3",
44
+ "vite": "^8.0.11",
45
+ "vite-plugin-singlefile": "^2.3.3",
46
+ "vitest": "^3"
47
+ },
48
+ "scripts": {
49
+ "build": "INPUT=mcp-app.html vite build",
50
+ "build:wasm": "scripts/build-wasm.sh",
51
+ "build:js-dsp": "scripts/build-js-dsp.sh",
52
+ "build:dsp": "scripts/build-wasm.sh && scripts/build-js-dsp.sh",
53
+ "build:server": "tsc -p tsconfig.server-build.json && node scripts/add-shebang.mjs",
54
+ "build:dist": "pnpm run build && pnpm run build:server",
55
+ "serve": "npx tsx src/server/app.ts",
56
+ "test": "vitest run",
57
+ "typecheck": "tsc -b"
58
+ }
59
+ }