audio-mcp 0.1.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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +411 -0
  3. package/dist/audio/capture.d.ts +28 -0
  4. package/dist/audio/capture.js +2 -0
  5. package/dist/audio/capture.js.map +1 -0
  6. package/dist/audio/devices.d.ts +16 -0
  7. package/dist/audio/devices.js +65 -0
  8. package/dist/audio/devices.js.map +1 -0
  9. package/dist/audio/helper-capture.d.ts +36 -0
  10. package/dist/audio/helper-capture.js +135 -0
  11. package/dist/audio/helper-capture.js.map +1 -0
  12. package/dist/audio/helper-path.d.ts +10 -0
  13. package/dist/audio/helper-path.js +47 -0
  14. package/dist/audio/helper-path.js.map +1 -0
  15. package/dist/audio/wav.d.ts +52 -0
  16. package/dist/audio/wav.js +174 -0
  17. package/dist/audio/wav.js.map +1 -0
  18. package/dist/bin/audio-capture-helper +0 -0
  19. package/dist/config.d.ts +14 -0
  20. package/dist/config.js +27 -0
  21. package/dist/config.js.map +1 -0
  22. package/dist/errors.d.ts +18 -0
  23. package/dist/errors.js +21 -0
  24. package/dist/errors.js.map +1 -0
  25. package/dist/index.d.ts +2 -0
  26. package/dist/index.js +10 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/logger.d.ts +14 -0
  29. package/dist/logger.js +34 -0
  30. package/dist/logger.js.map +1 -0
  31. package/dist/paths.d.ts +15 -0
  32. package/dist/paths.js +36 -0
  33. package/dist/paths.js.map +1 -0
  34. package/dist/server.d.ts +1 -0
  35. package/dist/server.js +112 -0
  36. package/dist/server.js.map +1 -0
  37. package/dist/session/manager.d.ts +58 -0
  38. package/dist/session/manager.js +184 -0
  39. package/dist/session/manager.js.map +1 -0
  40. package/dist/session/store.d.ts +35 -0
  41. package/dist/session/store.js +72 -0
  42. package/dist/session/store.js.map +1 -0
  43. package/dist/tools/index.d.ts +16 -0
  44. package/dist/tools/index.js +180 -0
  45. package/dist/tools/index.js.map +1 -0
  46. package/package.json +58 -0
package/dist/paths.js ADDED
@@ -0,0 +1,36 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ import { mkdirSync, existsSync } from "node:fs";
4
+ /** Expand a leading `~` in a path to the user's home directory. */
5
+ export function expandHome(p) {
6
+ if (p.startsWith("~/") || p === "~") {
7
+ return join(homedir(), p.slice(1));
8
+ }
9
+ return p;
10
+ }
11
+ /**
12
+ * Resolve audio-mcp storage paths. By default rooted at `~/.audio-mcp`.
13
+ * A custom root can be supplied (used by tests to isolate storage in a tmp dir).
14
+ */
15
+ export function resolvePaths(root, sessionsDirOverride) {
16
+ const rootDir = root ?? join(homedir(), ".audio-mcp");
17
+ const sessionsDir = sessionsDirOverride
18
+ ? expandHome(sessionsDirOverride)
19
+ : join(rootDir, "sessions");
20
+ return {
21
+ root: rootDir,
22
+ sessionsDir,
23
+ configFile: join(rootDir, "config.json"),
24
+ logFile: join(rootDir, "audio-mcp.log"),
25
+ };
26
+ }
27
+ /** Ensure the root and sessions directories exist. Idempotent. */
28
+ export function ensureDirs(paths) {
29
+ if (!existsSync(paths.root)) {
30
+ mkdirSync(paths.root, { recursive: true });
31
+ }
32
+ if (!existsSync(paths.sessionsDir)) {
33
+ mkdirSync(paths.sessionsDir, { recursive: true });
34
+ }
35
+ }
36
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAShD,mEAAmE;AACnE,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;QACpC,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAa,EAAE,mBAA4B;IACtE,MAAM,OAAO,GAAG,IAAI,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,CAAC;IACtD,MAAM,WAAW,GAAG,mBAAmB;QACrC,CAAC,CAAC,UAAU,CAAC,mBAAmB,CAAC;QACjC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAC9B,OAAO;QACL,IAAI,EAAE,OAAO;QACb,WAAW;QACX,UAAU,EAAE,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC;QACxC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC;KACxC,CAAC;AACJ,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,UAAU,CAAC,KAAY;IACrC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5B,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;QACnC,SAAS,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,CAAC;AACH,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function startServer(): Promise<void>;
package/dist/server.js ADDED
@@ -0,0 +1,112 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { resolvePaths, ensureDirs } from "./paths.js";
4
+ import { loadOrCreateConfig } from "./config.js";
5
+ import { createFileLogger } from "./logger.js";
6
+ import { SessionStore } from "./session/store.js";
7
+ import { SessionManager } from "./session/manager.js";
8
+ import { HelperProcessCapture } from "./audio/helper-capture.js";
9
+ import { listInputDevices, resolveMicDevice } from "./audio/devices.js";
10
+ import { buildTools } from "./tools/index.js";
11
+ import { AudioMcpError } from "./errors.js";
12
+ const PKG_VERSION = "0.1.0";
13
+ function toCallToolResult(value) {
14
+ return {
15
+ content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
16
+ structuredContent: value,
17
+ };
18
+ }
19
+ function toCallToolError(err) {
20
+ if (err instanceof AudioMcpError) {
21
+ return {
22
+ isError: true,
23
+ content: [
24
+ {
25
+ type: "text",
26
+ text: JSON.stringify({ error: { code: err.code, message: err.message } }, null, 2),
27
+ },
28
+ ],
29
+ structuredContent: { error: { code: err.code, message: err.message } },
30
+ };
31
+ }
32
+ const message = err instanceof Error ? err.message : String(err);
33
+ return {
34
+ isError: true,
35
+ content: [
36
+ {
37
+ type: "text",
38
+ text: JSON.stringify({ error: { code: "INTERNAL_ERROR", message } }, null, 2),
39
+ },
40
+ ],
41
+ structuredContent: { error: { code: "INTERNAL_ERROR", message } },
42
+ };
43
+ }
44
+ export async function startServer() {
45
+ const paths = resolvePaths();
46
+ ensureDirs(paths);
47
+ const cfg = loadOrCreateConfig(paths);
48
+ // If the user overrode sessions_dir in config, re-resolve + ensure.
49
+ const resolved = resolvePaths(undefined, cfg.sessions_dir ?? undefined);
50
+ ensureDirs(resolved);
51
+ const logger = createFileLogger(resolved.logFile);
52
+ logger.info("audio-mcp starting", { version: PKG_VERSION });
53
+ const store = new SessionStore(resolved.sessionsDir);
54
+ const manager = new SessionManager({
55
+ sampleRate: cfg.sample_rate,
56
+ sessionsDir: resolved.sessionsDir,
57
+ defaultSource: cfg.default_source,
58
+ defaultCaptureMode: cfg.default_capture_mode,
59
+ }, store, () => new HelperProcessCapture(), (src) => resolveMicDevice(src), logger);
60
+ const tools = buildTools({
61
+ manager,
62
+ store,
63
+ listInputDevices,
64
+ });
65
+ const server = new McpServer({ name: "audio-mcp", version: PKG_VERSION });
66
+ for (const tool of tools) {
67
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
+ const callback = async (args) => {
69
+ try {
70
+ const result = await tool.handler(args ?? {});
71
+ return toCallToolResult(result);
72
+ }
73
+ catch (err) {
74
+ logger.error(`tool ${tool.name} failed`, {
75
+ error: err instanceof Error ? err.message : String(err),
76
+ });
77
+ return toCallToolError(err);
78
+ }
79
+ };
80
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
81
+ server.registerTool(tool.name, { description: tool.description, inputSchema: tool.inputSchema }, callback);
82
+ }
83
+ const transport = new StdioServerTransport();
84
+ let shuttingDown = false;
85
+ const shutdown = async (signal) => {
86
+ if (shuttingDown)
87
+ return;
88
+ shuttingDown = true;
89
+ logger.info(`received ${signal}, shutting down`);
90
+ try {
91
+ await manager.gracefulStop();
92
+ }
93
+ catch (err) {
94
+ logger.error("shutdown error", { error: err.message });
95
+ }
96
+ try {
97
+ await server.close();
98
+ }
99
+ catch {
100
+ /* ignore */
101
+ }
102
+ process.exit(0);
103
+ };
104
+ process.on("SIGINT", () => void shutdown("SIGINT"));
105
+ process.on("SIGTERM", () => void shutdown("SIGTERM"));
106
+ // MCP clients signal shutdown by closing the stdio pipe.
107
+ process.stdin.on("end", () => void shutdown("stdin-closed"));
108
+ process.stdin.on("close", () => void shutdown("stdin-closed"));
109
+ await server.connect(transport);
110
+ logger.info("audio-mcp listening on stdio");
111
+ }
112
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ import type { CaptureDevice, CaptureMode } from "../audio/capture.js";
2
+ import type { Logger } from "../logger.js";
3
+ import type { SessionStore } from "./store.js";
4
+ export interface StartSessionInput {
5
+ label?: string;
6
+ source?: string;
7
+ capture?: CaptureMode;
8
+ }
9
+ export interface StartSessionResult {
10
+ session_id: string;
11
+ label: string;
12
+ source: string;
13
+ capture_mode: CaptureMode;
14
+ started_at: string;
15
+ sample_rate: number;
16
+ channels: number;
17
+ format: "wav";
18
+ }
19
+ export interface StopSessionResult {
20
+ session_id: string;
21
+ label: string;
22
+ started_at: string;
23
+ stopped_at: string;
24
+ duration_seconds: number;
25
+ file_size_bytes: number;
26
+ path: string;
27
+ }
28
+ export interface SessionManagerConfig {
29
+ sampleRate: number;
30
+ sessionsDir: string;
31
+ defaultSource: string | null;
32
+ defaultCaptureMode: CaptureMode;
33
+ }
34
+ export type DeviceResolver = (source?: string | null) => Promise<{
35
+ deviceId: string | undefined;
36
+ deviceName: string;
37
+ }>;
38
+ export type CaptureFactory = () => CaptureDevice;
39
+ /**
40
+ * Owns the single-active-session invariant. Coordinates a CaptureDevice
41
+ * with a WavWriter and the SessionStore. Per-session `capture` mode
42
+ * determines the channel layout (1 for mic/system, 2 for both → L=mic R=system).
43
+ */
44
+ export declare class SessionManager {
45
+ private readonly cfg;
46
+ private readonly store;
47
+ private readonly captureFactory;
48
+ private readonly resolveDevice;
49
+ private readonly logger;
50
+ private active;
51
+ constructor(cfg: SessionManagerConfig, store: SessionStore, captureFactory: CaptureFactory, resolveDevice: DeviceResolver, logger: Logger);
52
+ get hasActiveSession(): boolean;
53
+ get activeSessionId(): string | null;
54
+ start(input: StartSessionInput): Promise<StartSessionResult>;
55
+ stop(sessionId: string): Promise<StopSessionResult>;
56
+ /** Stop the active session on shutdown, swallowing any errors. */
57
+ gracefulStop(): Promise<void>;
58
+ }
@@ -0,0 +1,184 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { join } from "node:path";
3
+ import { statSync, existsSync } from "node:fs";
4
+ import { WavWriter } from "../audio/wav.js";
5
+ import { AudioMcpError } from "../errors.js";
6
+ const CHANNELS_BY_MODE = {
7
+ mic: 1,
8
+ system: 1,
9
+ both: 2,
10
+ };
11
+ /**
12
+ * Owns the single-active-session invariant. Coordinates a CaptureDevice
13
+ * with a WavWriter and the SessionStore. Per-session `capture` mode
14
+ * determines the channel layout (1 for mic/system, 2 for both → L=mic R=system).
15
+ */
16
+ export class SessionManager {
17
+ cfg;
18
+ store;
19
+ captureFactory;
20
+ resolveDevice;
21
+ logger;
22
+ active = null;
23
+ constructor(cfg, store, captureFactory, resolveDevice, logger) {
24
+ this.cfg = cfg;
25
+ this.store = store;
26
+ this.captureFactory = captureFactory;
27
+ this.resolveDevice = resolveDevice;
28
+ this.logger = logger;
29
+ }
30
+ get hasActiveSession() {
31
+ return this.active !== null;
32
+ }
33
+ get activeSessionId() {
34
+ return this.active?.meta.session_id ?? null;
35
+ }
36
+ async start(input) {
37
+ if (this.active) {
38
+ throw new AudioMcpError("SESSION_ALREADY_ACTIVE", `A session is already active (${this.active.meta.session_id}). Stop it before starting a new one.`);
39
+ }
40
+ const captureMode = input.capture ?? this.cfg.defaultCaptureMode;
41
+ const channels = CHANNELS_BY_MODE[captureMode];
42
+ const sourceInput = input.source ?? this.cfg.defaultSource ?? null;
43
+ // Mic device is only relevant if we're actually capturing mic.
44
+ let micDeviceId = undefined;
45
+ let deviceLabel;
46
+ if (captureMode === "system") {
47
+ deviceLabel = "system audio";
48
+ }
49
+ else {
50
+ try {
51
+ const resolved = await this.resolveDevice(sourceInput);
52
+ micDeviceId = resolved.deviceId;
53
+ deviceLabel = resolved.deviceName;
54
+ }
55
+ catch (err) {
56
+ if (err instanceof AudioMcpError)
57
+ throw err;
58
+ throw new AudioMcpError("AUDIO_DEVICE_ERROR", err.message);
59
+ }
60
+ if (captureMode === "both")
61
+ deviceLabel = `${deviceLabel} + system audio`;
62
+ }
63
+ const sessionId = randomUUID();
64
+ const startedAt = new Date().toISOString();
65
+ const label = input.label?.trim() || `session-${startedAt}`;
66
+ const wavPath = join(this.cfg.sessionsDir, `${sessionId}.wav`);
67
+ const capture = this.captureFactory();
68
+ const writer = new WavWriter(wavPath, {
69
+ sampleRate: this.cfg.sampleRate,
70
+ channels,
71
+ });
72
+ // Forward PCM chunks straight to the WAV writer. Append errors are logged
73
+ // but do not abort the session.
74
+ capture.onData((chunk) => {
75
+ writer.append(chunk).catch((err) => {
76
+ this.logger.error("wav append failed", { error: err.message });
77
+ });
78
+ });
79
+ try {
80
+ await capture.start({
81
+ sampleRate: this.cfg.sampleRate,
82
+ channels,
83
+ captureMode,
84
+ micDeviceId,
85
+ excludePid: process.ppid,
86
+ });
87
+ }
88
+ catch (err) {
89
+ try {
90
+ await writer.finalize();
91
+ }
92
+ catch {
93
+ /* ignore */
94
+ }
95
+ if (err instanceof AudioMcpError)
96
+ throw err;
97
+ throw new AudioMcpError("AUDIO_DEVICE_ERROR", err.message);
98
+ }
99
+ const meta = {
100
+ session_id: sessionId,
101
+ label,
102
+ source: deviceLabel,
103
+ capture_mode: captureMode,
104
+ started_at: startedAt,
105
+ stopped_at: null,
106
+ duration_seconds: null,
107
+ file_size_bytes: null,
108
+ sample_rate: this.cfg.sampleRate,
109
+ channels,
110
+ format: "wav",
111
+ path: wavPath,
112
+ is_active: true,
113
+ };
114
+ this.store.upsert(meta);
115
+ this.active = { meta, capture, writer };
116
+ this.logger.info("session started", {
117
+ session_id: sessionId,
118
+ capture_mode: captureMode,
119
+ source: deviceLabel,
120
+ label,
121
+ });
122
+ return {
123
+ session_id: sessionId,
124
+ label,
125
+ source: deviceLabel,
126
+ capture_mode: captureMode,
127
+ started_at: startedAt,
128
+ sample_rate: this.cfg.sampleRate,
129
+ channels,
130
+ format: "wav",
131
+ };
132
+ }
133
+ async stop(sessionId) {
134
+ if (!this.active || this.active.meta.session_id !== sessionId) {
135
+ const existing = this.store.get(sessionId);
136
+ if (!existing) {
137
+ throw new AudioMcpError("SESSION_NOT_FOUND", `Session not found: ${sessionId}`);
138
+ }
139
+ if (!existing.is_active) {
140
+ throw new AudioMcpError("SESSION_NOT_FOUND", `Session ${sessionId} is not currently active.`);
141
+ }
142
+ throw new AudioMcpError("SESSION_NOT_FOUND", `Session ${sessionId} not active in-process.`);
143
+ }
144
+ const { meta, capture, writer } = this.active;
145
+ await capture.stop();
146
+ const { bytesWritten, durationSeconds } = await writer.finalize();
147
+ const stoppedAt = new Date().toISOString();
148
+ const fileSize = existsSync(meta.path) ? statSync(meta.path).size : bytesWritten + 44;
149
+ const updated = {
150
+ ...meta,
151
+ stopped_at: stoppedAt,
152
+ duration_seconds: durationSeconds,
153
+ file_size_bytes: fileSize,
154
+ is_active: false,
155
+ };
156
+ this.store.upsert(updated);
157
+ this.active = null;
158
+ this.logger.info("session stopped", {
159
+ session_id: sessionId,
160
+ duration_seconds: durationSeconds,
161
+ });
162
+ return {
163
+ session_id: sessionId,
164
+ label: updated.label,
165
+ started_at: updated.started_at,
166
+ stopped_at: stoppedAt,
167
+ duration_seconds: durationSeconds,
168
+ file_size_bytes: fileSize,
169
+ path: updated.path,
170
+ };
171
+ }
172
+ /** Stop the active session on shutdown, swallowing any errors. */
173
+ async gracefulStop() {
174
+ if (!this.active)
175
+ return;
176
+ try {
177
+ await this.stop(this.active.meta.session_id);
178
+ }
179
+ catch (err) {
180
+ this.logger.error("graceful stop failed", { error: err.message });
181
+ }
182
+ }
183
+ }
184
+ //# sourceMappingURL=manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/session/manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { CaptureMode } from "../audio/capture.js";
2
+ export interface SessionMetadata {
3
+ session_id: string;
4
+ label: string;
5
+ source: string;
6
+ capture_mode: CaptureMode;
7
+ started_at: string;
8
+ stopped_at: string | null;
9
+ duration_seconds: number | null;
10
+ file_size_bytes: number | null;
11
+ sample_rate: number;
12
+ channels: number;
13
+ format: "wav";
14
+ path: string;
15
+ is_active: boolean;
16
+ }
17
+ /**
18
+ * Persists session metadata as `<uuid>.json` alongside the WAV audio file in
19
+ * the sessions directory. List order is newest-first by `started_at`.
20
+ */
21
+ export declare class SessionStore {
22
+ private readonly sessionsDir;
23
+ constructor(sessionsDir: string);
24
+ private metaPath;
25
+ upsert(meta: SessionMetadata): void;
26
+ get(sessionId: string): SessionMetadata | null;
27
+ list(): SessionMetadata[];
28
+ /**
29
+ * Delete a session's metadata and audio file. Refuses to delete an active
30
+ * session; throws `SESSION_NOT_FOUND` if the session does not exist.
31
+ */
32
+ delete(sessionId: string): void;
33
+ /** Read current file size from disk if the WAV exists. */
34
+ currentFileSize(meta: SessionMetadata): number | null;
35
+ }
@@ -0,0 +1,72 @@
1
+ import { readFileSync, writeFileSync, existsSync, readdirSync, unlinkSync, statSync, } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { AudioMcpError } from "../errors.js";
4
+ /**
5
+ * Persists session metadata as `<uuid>.json` alongside the WAV audio file in
6
+ * the sessions directory. List order is newest-first by `started_at`.
7
+ */
8
+ export class SessionStore {
9
+ sessionsDir;
10
+ constructor(sessionsDir) {
11
+ this.sessionsDir = sessionsDir;
12
+ }
13
+ metaPath(sessionId) {
14
+ return join(this.sessionsDir, `${sessionId}.json`);
15
+ }
16
+ upsert(meta) {
17
+ writeFileSync(this.metaPath(meta.session_id), JSON.stringify(meta, null, 2) + "\n", "utf8");
18
+ }
19
+ get(sessionId) {
20
+ const path = this.metaPath(sessionId);
21
+ if (!existsSync(path))
22
+ return null;
23
+ try {
24
+ return JSON.parse(readFileSync(path, "utf8"));
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
30
+ list() {
31
+ if (!existsSync(this.sessionsDir))
32
+ return [];
33
+ const entries = readdirSync(this.sessionsDir).filter((f) => f.endsWith(".json"));
34
+ const out = [];
35
+ for (const entry of entries) {
36
+ try {
37
+ const meta = JSON.parse(readFileSync(join(this.sessionsDir, entry), "utf8"));
38
+ out.push(meta);
39
+ }
40
+ catch {
41
+ // Skip malformed metadata silently.
42
+ }
43
+ }
44
+ out.sort((a, b) => (b.started_at < a.started_at ? -1 : b.started_at > a.started_at ? 1 : 0));
45
+ return out;
46
+ }
47
+ /**
48
+ * Delete a session's metadata and audio file. Refuses to delete an active
49
+ * session; throws `SESSION_NOT_FOUND` if the session does not exist.
50
+ */
51
+ delete(sessionId) {
52
+ const meta = this.get(sessionId);
53
+ if (!meta) {
54
+ throw new AudioMcpError("SESSION_NOT_FOUND", `Session not found: ${sessionId}`);
55
+ }
56
+ if (meta.is_active) {
57
+ throw new AudioMcpError("SESSION_STILL_ACTIVE", `Session ${sessionId} is still active — stop it before deleting.`);
58
+ }
59
+ if (existsSync(meta.path))
60
+ unlinkSync(meta.path);
61
+ const metaFile = this.metaPath(sessionId);
62
+ if (existsSync(metaFile))
63
+ unlinkSync(metaFile);
64
+ }
65
+ /** Read current file size from disk if the WAV exists. */
66
+ currentFileSize(meta) {
67
+ if (!existsSync(meta.path))
68
+ return null;
69
+ return statSync(meta.path).size;
70
+ }
71
+ }
72
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/session/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { z } from "zod";
2
+ import type { SessionManager } from "../session/manager.js";
3
+ import type { SessionStore } from "../session/store.js";
4
+ import type { DeviceSummary } from "../audio/capture.js";
5
+ export interface ToolsContext {
6
+ manager: SessionManager;
7
+ store: SessionStore;
8
+ listInputDevices: () => Promise<DeviceSummary[]>;
9
+ }
10
+ export interface ToolDefinition {
11
+ name: string;
12
+ description: string;
13
+ inputSchema: z.ZodRawShape;
14
+ handler: (input: any) => Promise<any>;
15
+ }
16
+ export declare function buildTools(ctx: ToolsContext): ToolDefinition[];