@audiolabtools/mcp-server 0.1.0 → 0.1.1

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nathan Renting
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,107 +1,81 @@
1
- # AudioLab MCP server
1
+ # @audiolabtools/mcp-server
2
2
 
3
- Exposes the proprietary AudioLab analysis engine as [Model Context Protocol](https://modelcontextprotocol.io)
4
- tools, so an AI agent (Claude Desktop, Claude Code, or any MCP client) can analyze audio.
3
+ MCP ([Model Context Protocol](https://modelcontextprotocol.io)) server that gives any
4
+ MCP-capable AI — Claude Desktop, Claude Code, Cursor, and others — eight audio-analysis
5
+ tools, backed by the hosted **AudioLab** API. It is a thin HTTP client: **no local audio
6
+ engine, no ffmpeg, nothing to compile.**
5
7
 
6
- Audio sources accepted:
7
- - **Public URL** (`{url: "https://..."}`): fetched and decoded server-side
8
- - **Local file path** (`{path: "./song.wav"}`): read server-side from disk
8
+ ## Install
9
9
 
10
- The MCP server runs on stdio in the user's own process, so local-file access is the
11
- same threat model as a CLI the user installs themselves.
12
-
13
- Nothing is uploaded by the caller in either case. Responses are **curated** JSON
14
- (per-sample waveform/spectrum arrays are dropped so the response is useful to an
15
- LLM, not overwhelming).
16
-
17
- ## Two modes (pick by who runs it)
18
-
19
- | | `mcp/server.mjs` (local) | `mcp/hosted-server.mjs` (hosted) |
20
- |---|---|---|
21
- | Runs | the proprietary engine locally (bundled ffmpeg) | HTTP calls to the hosted API `audiolab.tools/v1/*` |
22
- | Audio source | `{url}` **or** `{path}` (on-device, never leaves the machine) | `{url}` only |
23
- | Needs | the repo + engine (private) | an `AUDIOLAB_API_KEY` |
24
- | Distribute? | **No** (it contains the proprietary engine; keep it repo-only) | **Yes** (no engine code, safe to hand to Claude & other AIs) |
25
-
26
- Give **hosted mode** to Claude Desktop / Claude Code / Cursor / any MCP client:
10
+ Point your MCP client at the package via `npx` (nothing to install globally):
27
11
 
28
12
  ```json
29
13
  {
30
14
  "mcpServers": {
31
15
  "audiolab": {
32
- "command": "node",
33
- "args": ["/abs/path/to/mcp/hosted-server.mjs"],
16
+ "command": "npx",
17
+ "args": ["-y", "@audiolabtools/mcp-server"],
34
18
  "env": { "AUDIOLAB_API_KEY": "al_live_yourkey" }
35
19
  }
36
20
  }
37
21
  }
38
22
  ```
39
23
 
40
- Get a key from partners@audiolab.tools (self-serve opens after the first partners).
41
- Both modes expose the identical eight tools below.
24
+ Get a key: sign in at **https://audiolab.tools/account** and generate one (free tier available).
42
25
 
43
- ## Tools: eight, all single-shot
26
+ ## Requirements
44
27
 
45
- | Tool | Lab | Input | Returns |
46
- |---|---|---|---|
47
- | `analyze_loudness` | MixLab | `{url}` or `{path}` | Integrated LUFS (EBU R128 / BS.1770-4), true-peak, loudness range, crest factor, stereo correlation, tonal balance, harshness/muddiness labels |
48
- | `check_target` | MixLab | `{url\|path, target, lufs?, tp?}` | Pass/fail vs Spotify -14 / Apple-Music -16 / YouTube -14 / TIDAL -14 / Amazon -14 / podcast -16 / EBU-broadcast -23 / ATSC-broadcast -24 (or `"custom"` + lufs+tp). Includes deltas, issue messages, ffmpeg loudnorm fix command |
49
- | `analyze_timeseries` | MixLab | `{url\|path, waveformPoints?}` | Short-term LUFS samples + downsampled waveform peaks over time (for graphing/UI) |
50
- | `get_spectrum` | MixLab | `{url}` or `{path}` | FFT bins (freqs+mags) + 7-band energies + centroid/rolloff/flatness |
51
- | `analyze_voice` | VoiceLab | `{url}` or `{path}` | Speech/silence ratio, speaking rate, SNR, noise floor, room echo, sibilance & clipping risk |
52
- | `get_speech_segments` | VoiceLab | `{url}` or `{path}` | Voiced regions with start/end timestamps + per-segment RMS (for auto-trim, chapters, speaker-turn detection) |
53
- | `index_signal` | SignalLab | `{url}` or `{path}` | Content-type guess, tags, clipping/silence regions, brightness & dynamics buckets, file facts |
54
- | `compare_loudness` | MixLab A/B | `{a: {url\|path}, b: {url\|path}}` | Run MixLab on both, return both results + per-metric deltas with verdict |
28
+ - **Node ≥ 18** — uses the built-in global `fetch` + `AbortSignal.timeout`.
29
+ - An `AUDIOLAB_API_KEY`. No ffmpeg, no native dependencies.
55
30
 
56
- Provide **exactly one** of `url` or `path` per source.
31
+ ## Tools
57
32
 
58
- ## Requirements
33
+ Every tool takes a **public https URL** to the audio; the API fetches and analyses it
34
+ server-side and returns numbers only (nothing is uploaded from your machine).
59
35
 
60
- - Node ≥ 22
61
- - **ffmpeg on `PATH`**: the engine's Node decoder shells out to it.
36
+ | Tool | Input | Returns |
37
+ |---|---|---|
38
+ | `analyze_loudness` | `{ url }` | Integrated LUFS (EBU R128 / BS.1770-4), true-peak (dBTP), LRA, crest factor, stereo correlation, mono compatibility, tonal balance |
39
+ | `check_target` | `{ url, target, lufs?, tp? }` | Pass/fail vs a delivery target (`spotify` / `apple-music` / `youtube` / `tidal` / `amazon-music` / `podcast` / `ebu-broadcast` / `atsc-broadcast`, or `target:"custom"` + `lufs`+`tp`), with per-metric deltas and an ffmpeg loudnorm fix command |
40
+ | `analyze_timeseries` | `{ url, waveformPoints? }` | Short-term LUFS over time + downsampled waveform peaks (for graphs/meters) |
41
+ | `get_spectrum` | `{ url }` | FFT magnitude data + 7-band energies + dominant band |
42
+ | `analyze_voice` | `{ url }` | Voice QA: speech/silence ratio, speaking rate, SNR, noise floor, room echo, sibilance & clipping risk |
43
+ | `get_speech_segments` | `{ url }` | Voiced regions with start/end + per-segment RMS (auto-trim, chapters) |
44
+ | `index_signal` | `{ url }` | Content-type guess, tags, clipping/silence regions, brightness & dynamics buckets |
45
+ | `compare_loudness` | `{ urlA, urlB }` | Runs loudness on both and returns both results for an A/B comparison |
62
46
 
63
- ## Run
47
+ Example asks to your AI:
64
48
 
65
- ```sh
66
- npm run mcp # or: node mcp/server.mjs
67
- node mcp/server.mjs --selftest # smoke test (4 tools), no network/ffmpeg needed
68
- ```
49
+ - *“Analyze the loudness of https://example.com/track.wav”* → `analyze_loudness`
50
+ - *“Does https://example.com/mix.wav pass Spotify?”* → `check_target` with `target:"spotify"`
51
+ - *“A/B-compare masterA.wav vs masterB.wav for loudness”* → `compare_loudness`
69
52
 
70
- The server speaks MCP over **stdio** (what desktop clients spawn). Streamable HTTP
71
- for a remote, keyed server is a later transport addition; deploy, auth keys,
72
- and billing are gated steps (see [`docs/PDD.md`](../docs/PDD.md) §0/§8).
53
+ ## Configuration (env)
73
54
 
74
- ## Add to Claude Desktop / Claude Code
55
+ | Var | Default | Purpose |
56
+ |---|---|---|
57
+ | `AUDIOLAB_API_KEY` | — (required) | Your API key. |
58
+ | `AUDIOLAB_API_BASE` | `https://audiolab.tools/v1` | Override the API base (must be `https://`). |
59
+ | `AUDIOLAB_TIMEOUT_MS` | `60000` | Per-request timeout in milliseconds. |
75
60
 
76
- ```json
77
- {
78
- "mcpServers": {
79
- "audiolab": {
80
- "command": "node",
81
- "args": ["E:/New audiolab.dev/mcp/server.mjs"]
82
- }
83
- }
84
- }
85
- ```
61
+ ## Privacy
86
62
 
87
- (Once the package ships on npm, this becomes
88
- `{"command": "npx", "args": ["-y", "@audiolab/mcp-server"]}`, gated on publish.)
63
+ The tools send the **audio URL** and your **API key** to the AudioLab API (`audiolab.tools`),
64
+ which fetches and analyses the audio server-side and returns numbers only — your audio is not
65
+ stored (see https://audiolab.tools/privacy). This package has no telemetry and writes nothing
66
+ to disk.
89
67
 
90
- Then ask:
68
+ ## Limits
91
69
 
92
- - *"Analyze the loudness of https://example.com/track.wav"* → calls `analyze_loudness` with `{url}`
93
- - *"Run loudness on ./mix.wav"* → calls `analyze_loudness` with `{path}`
94
- - *"A/B compare ./master.wav against ./reference.wav for loudness"* → calls `compare_loudness`
70
+ - One file per call (agents loop for many); one-shot (no streaming/realtime).
71
+ - File-size and rate limits are enforced by the API, per key.
95
72
 
96
- ## Limits
73
+ ## Smoke test
97
74
 
98
- - 200 MB per file (size cap)
99
- - Any sample rate / channel count (engine preserves rate; multi-channel mixed to stereo for analysis)
100
- - One file per call (no batch; agent loops for many)
101
- - One-shot only (no streaming / realtime)
75
+ ```sh
76
+ node hosted-server.mjs --selftest # verifies the 8 tools register + the missing-key guard; no network
77
+ ```
102
78
 
103
- ## Fastest path to first paying partner
79
+ ## License
104
80
 
105
- Deploy the hosted-API counterpart (`api/v1/[...endpoint].mjs`) + add one key +
106
- invoice them. No Stripe, no key DB, no metering platform required to start.
107
- Metering/credits/self-serve come later, behind the billing gate.
81
+ MIT © Nathan Renting
package/hosted-server.mjs CHANGED
@@ -5,15 +5,15 @@
5
5
  // calls, so it is safe to distribute publicly without exposing the proprietary engine.
6
6
  //
7
7
  // Two consumers share buildServer():
8
- // 1. This file run directly = a stdio server (the npm package @audiolab/mcp-server),
8
+ // 1. This file run directly = a stdio server (the npm package @audiolabtools/mcp-server),
9
9
  // reading the key from AUDIOLAB_API_KEY.
10
10
  // 2. api/mcp.mjs = the remote streamable-HTTP endpoint at /mcp, which passes the
11
11
  // per-request Bearer key via buildServer({ apiKey }).
12
12
  //
13
- // Requires an API key (the API is live for design partners; email partners@audiolab.tools).
13
+ // Requires an API key (self-serve: sign in at https://audiolab.tools/account and generate one).
14
14
  // Config in Claude Desktop's claude_desktop_config.json:
15
15
  // { "mcpServers": { "audiolab": {
16
- // "command": "npx", "args": ["-y", "@audiolab/mcp-server"],
16
+ // "command": "npx", "args": ["-y", "@audiolabtools/mcp-server"],
17
17
  // "env": { "AUDIOLAB_API_KEY": "al_live_yourkey" } } } }
18
18
  //
19
19
  // Env: AUDIOLAB_API_KEY (required at call time for stdio mode), AUDIOLAB_API_BASE
@@ -26,19 +26,35 @@ import { z } from 'zod';
26
26
 
27
27
  // Read env at CALL time (not module load) so the key can be injected by the MCP host
28
28
  // and so the missing-key guard is testable.
29
- const apiBase = () => (process.env.AUDIOLAB_API_BASE || 'https://audiolab.tools/v1').replace(/\/$/, '');
29
+ const apiBase = () => {
30
+ const base = (process.env.AUDIOLAB_API_BASE || 'https://audiolab.tools/v1').replace(/\/$/, '');
31
+ // Defense in depth: never send the Bearer key over a non-https (or attacker-injected) base.
32
+ if (!/^https:\/\//i.test(base)) throw new Error('AUDIOLAB_API_BASE must be an https:// URL.');
33
+ return base;
34
+ };
30
35
 
31
36
  // key defaults to the env var (stdio mode); the remote endpoint passes a per-request key.
32
37
  export async function call(route, body, key = process.env.AUDIOLAB_API_KEY) {
33
- if (!key) throw new Error('AUDIOLAB_API_KEY is not set. Get a key from partners@audiolab.tools.');
34
- const res = await fetch(`${apiBase()}/${route}`, {
35
- method: 'POST',
36
- headers: { 'content-type': 'application/json', authorization: `Bearer ${key}` },
37
- body: JSON.stringify(body),
38
- });
38
+ if (!key) throw new Error('AUDIOLAB_API_KEY is not set. Get a key at https://audiolab.tools/account.');
39
+ // Time-box the request so a hung/slow API can't stall the calling agent forever.
40
+ const timeoutMs = Number(process.env.AUDIOLAB_TIMEOUT_MS) || 60_000;
41
+ let res;
42
+ try {
43
+ res = await fetch(`${apiBase()}/${route}`, {
44
+ method: 'POST',
45
+ headers: { 'content-type': 'application/json', authorization: `Bearer ${key}` },
46
+ body: JSON.stringify(body),
47
+ signal: AbortSignal.timeout(timeoutMs),
48
+ });
49
+ } catch (e) {
50
+ if (e?.name === 'TimeoutError') throw new Error(`AudioLab API timed out after ${timeoutMs} ms.`);
51
+ throw new Error(`Could not reach the AudioLab API: ${String(e?.message || e)}`);
52
+ }
39
53
  const text = await res.text();
40
54
  let json;
41
- try { json = JSON.parse(text); } catch { json = { raw: text.slice(0, 500) }; }
55
+ // Parse JSON only; never echo an arbitrary upstream body back to the agent.
56
+ try { json = JSON.parse(text); }
57
+ catch { throw new Error(res.ok ? 'AudioLab API returned a non-JSON response.' : `AudioLab API error ${res.status}.`); }
42
58
  if (!res.ok) throw new Error(String(json?.error?.message || json?.error || `API returned ${res.status}`));
43
59
  return json;
44
60
  }
@@ -52,7 +68,7 @@ const wrap = (fn) => async (input) => {
52
68
  const URL_IN = { url: z.string().url().describe('Public https URL to the audio file.') };
53
69
 
54
70
  export function buildServer({ apiKey } = {}) {
55
- const server = new McpServer({ name: 'audiolab', version: '0.2.0' });
71
+ const server = new McpServer({ name: 'audiolab', version: '0.1.1' }); // keep in sync with package.json
56
72
  const toolNames = [];
57
73
  const tool = (name, def, handler) => { server.registerTool(name, def, handler); toolNames.push(name); };
58
74
  // Per-request API key threaded from the transport, so we never race on process.env
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@audiolabtools/mcp-server",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "MCP server for AudioLab — loudness (EBU R128 / BS.1770-4), true-peak, voice-quality, and signal analysis for AI agents (Claude, Cursor, any MCP client), via the AudioLab hosted API. No local audio engine required.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,6 +22,7 @@
22
22
  "audiolab"
23
23
  ],
24
24
  "homepage": "https://audiolab.tools/api",
25
+ "bugs": { "url": "https://audiolab.tools/api" },
25
26
  "repository": {
26
27
  "type": "git",
27
28
  "url": "git+https://github.com/Audio-Launch/audiolab-tools.git",