@zakotoys/notify-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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ZakoToys
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.
@@ -0,0 +1,154 @@
1
+ # @zakotoys/notify-mcp
2
+
3
+ [![npm](https://img.shields.io/npm/v/%40zakotoys%2Fnotify-mcp?logo=npm&logoColor=white)](https://www.npmjs.com/package/@zakotoys/notify-mcp)
4
+ [![CI](https://github.com/zakotoys/notify-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/zakotoys/notify-mcp/actions/workflows/ci.yml)
5
+ [![codecov](https://codecov.io/gh/zakotoys/notify-mcp/branch/main/graph/badge.svg)](https://codecov.io/gh/zakotoys/notify-mcp)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
7
+
8
+ [English](README.md) | [简体中文](README.zh-CN.md) | **日本語**
9
+
10
+ `notify-mcp` は、固定された内蔵サウンドの再生とネイティブなデスクトップ通知を提供する、標準入出力で動作する [Model Context Protocol](https://modelcontextprotocol.io) サーバーです。Claude Desktop、Cursor、OpenCode、Codex などの MCP クライアントから、ローカルの Windows または macOS 通知システムを利用できます。
11
+
12
+ ## 機能
13
+
14
+ - オーディオ一覧、オーディオ再生、デスクトップ通知、両方を行う統合処理の 4 つの MCP ツール。
15
+ - 1 種類の内蔵サウンド:`zako`。
16
+ - `node-notifier` による Windows 通知センターと macOS Notification Center。
17
+ - オーディオは固定カタログのみ。ファイルパス、URL、アップロード、実行時の追加は受け付けません。
18
+ - ビジネスロジックと OS アダプターを分離し、スピーカーや GUI のない CI でもテストできます。
19
+
20
+ ## 必要条件
21
+
22
+ - Node.js 20 以降。
23
+ - Claude Desktop、Cursor、OpenCode、Codex などの MCP クライアント。
24
+ - 主なデスクトップ通知環境は Windows 10 以降または macOS。
25
+
26
+ `aplay` とデスクトップ通知バックエンドがあれば Linux でも利用できます。
27
+
28
+ ## クイックスタート
29
+
30
+ ### Claude Desktop、Cursor、OpenCode、Codex
31
+
32
+ ```bash
33
+ npm install --global @zakotoys/notify-mcp
34
+ ```
35
+
36
+ クライアントの MCP 設定に追加します。
37
+
38
+ ```json
39
+ {
40
+ "mcpServers": {
41
+ "notify": { "command": "notify-mcp" }
42
+ }
43
+ }
44
+ ```
45
+
46
+ グローバルインストールをせず `npx` で実行することもできます。
47
+
48
+ ```json
49
+ {
50
+ "mcpServers": {
51
+ "notify": {
52
+ "command": "npx",
53
+ "args": ["-y", "@zakotoys/notify-mcp@latest"]
54
+ }
55
+ }
56
+ }
57
+ ```
58
+
59
+ Windows クライアントで `npx` を直接解決できない場合は `cmd /c npx -y @zakotoys/notify-mcp@latest` を使用します。
60
+
61
+ ### MCP Inspector
62
+
63
+ ```bash
64
+ npx @modelcontextprotocol/inspector npx -y @zakotoys/notify-mcp@latest
65
+ ```
66
+
67
+ まず `notify_list_audio` を呼び出し、次に `notify_desktop` または `notify` を試してください。
68
+
69
+ ## MCP ツール
70
+
71
+ | ツール | 用途 |
72
+ | --- | --- |
73
+ | `notify_list_audio` | 固定された内蔵オーディオカタログを一覧表示します。 |
74
+ | `notify_play_audio` | 内蔵サウンドを 1 つ再生します。 |
75
+ | `notify_desktop` | ネイティブなデスクトップ通知を表示します。 |
76
+ | `notify` | 任意の内蔵サウンドを再生してから通知を表示します。 |
77
+
78
+ ### `notify_list_audio`
79
+
80
+ 引数はありません。各項目には `id`、ラベル、説明、周波数、再生時間が含まれます。
81
+
82
+ ### `notify_play_audio`
83
+
84
+ ```json
85
+ { "audio": "zako" }
86
+ ```
87
+
88
+ `audio` は `zako` でなければなりません。サーバーにはボーカル分離済みの WAV が内蔵されており、外部パスや URL は受け付けません。
89
+
90
+ このクリップは [`assets/audio/SOURCE.md`](assets/audio/SOURCE.md) に記載した動画からボーカルを抽出したものです。このアセットを含むパッケージを公開する前に、再配布の権利を確認してください。
91
+
92
+ ### `notify_desktop`
93
+
94
+ ```json
95
+ {
96
+ "title": "ビルド完了",
97
+ "message": "notify-mcp のビルドが成功しました",
98
+ "subtitle": "任意の補足テキスト"
99
+ }
100
+ ```
101
+
102
+ `title` は必須で 200 文字以内、`message` は必須で 2,000 文字以内、`subtitle` は任意で 200 文字以内です。
103
+
104
+ ### `notify`
105
+
106
+ ```json
107
+ {
108
+ "audio": "zako",
109
+ "title": "リマインダー",
110
+ "message": "会議は 5 分後に始まります"
111
+ }
112
+ ```
113
+
114
+ `audio` は省略できます。指定した場合は、サウンドの再生が完了してから通知を送信します。
115
+
116
+ ## プラットフォームの動作
117
+
118
+ - **macOS:** サウンドに `afplay`、通知に Notification Center を使用します。
119
+ - **Windows:** サウンドに PowerShell の `Media.SoundPlayer`、通知に Windows 通知センターを使用します。
120
+ - **Linux:** サウンドに ALSA の `aplay` を使用し、通知バックエンドは `node-notifier` が選択します。
121
+
122
+ OS の設定で、ターミナルアプリケーションからの通知を許可する必要がある場合があります。サーバーが通知本文やオーディオをアップロードすることはありません。
123
+
124
+ ## 開発
125
+
126
+ ```bash
127
+ npm install
128
+ npm run typecheck # TypeScript の型チェック
129
+ npm run lint # Biome の lint
130
+ npm test # Vitest と V8 カバレッジ
131
+ npm run build # dist/ にランタイムをコンパイル
132
+ npm run ci # すべてのチェック、ビルド、パッケージ検証
133
+ ```
134
+
135
+ テストは注入した fake アダプターとインメモリ MCP transport を使用するため、スピーカー、GUI、実際の通知センターは必要ありません。
136
+
137
+ ## リリース
138
+
139
+ `vX.Y.Z` タグを push すると、GitHub Actions が完全な CI と package バージョンの一致を確認します。その後 npm Trusted Publishing と provenance を使用して公開し、自動生成された変更履歴付きの GitHub Release を作成します。
140
+
141
+ ```bash
142
+ npm version patch # minor または major も使用できます
143
+ git push --follow-tags origin main
144
+ ```
145
+
146
+ npm の Trusted Publisher には、リポジトリ `zakotoys/notify-mcp`、workflow `publish.yml`、GitHub Actions プロバイダーを設定してください。
147
+
148
+ ## プロジェクトの範囲
149
+
150
+ このプロジェクトはローカル通知の基本機能だけを提供します。通知履歴、リモートオーディオ、ユーザーが追加するメディア、バックグラウンド daemon は管理しません。ツールを呼び出すタイミングは MCP クライアントが決定します。
151
+
152
+ ## ライセンス
153
+
154
+ MIT、詳しくは [LICENSE](LICENSE) を参照してください。
package/README.md ADDED
@@ -0,0 +1,191 @@
1
+ # @zakotoys/notify-mcp
2
+
3
+ [![npm](https://img.shields.io/npm/v/%40zakotoys%2Fnotify-mcp?logo=npm&logoColor=white)](https://www.npmjs.com/package/@zakotoys/notify-mcp)
4
+ [![CI](https://github.com/zakotoys/notify-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/zakotoys/notify-mcp/actions/workflows/ci.yml)
5
+ [![codecov](https://codecov.io/gh/zakotoys/notify-mcp/branch/main/graph/badge.svg)](https://codecov.io/gh/zakotoys/notify-mcp)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
7
+
8
+ **English** | [简体中文](README.zh-CN.md) | [日本語](README.ja-JP.md)
9
+
10
+ `notify-mcp` is a small [Model Context Protocol](https://modelcontextprotocol.io)
11
+ server for playing fixed built-in sounds and showing native desktop
12
+ notifications. It connects Claude Desktop, Cursor, OpenCode, Codex, or another
13
+ MCP client to the local Windows or macOS notification system through stdio.
14
+
15
+ ## Features
16
+
17
+ - Four standard MCP tools for listing the audio, playing it, sending a desktop
18
+ notification, and doing both in one call.
19
+ - One built-in audio track: `zako`.
20
+ - Native notifications through `node-notifier` (Windows notification center and
21
+ macOS Notification Center).
22
+ - Audio is intentionally closed: callers can select a built-in id, but cannot
23
+ provide a file path, URL, upload, or runtime-added track.
24
+ - Business logic is separated from operating-system adapters and is fully
25
+ testable without a speaker or desktop session.
26
+
27
+ ## Requirements
28
+
29
+ - Node.js 20 or newer.
30
+ - A supported MCP client such as Claude Desktop, Cursor, OpenCode, or Codex.
31
+ - Windows 10+ or macOS for the primary desktop notification experience.
32
+
33
+ Linux is also supported when `aplay` and a desktop notification backend are
34
+ available.
35
+
36
+ ## Quick start
37
+
38
+ ### Claude Desktop, Cursor, OpenCode, or Codex
39
+
40
+ Install the package globally:
41
+
42
+ ```bash
43
+ npm install --global @zakotoys/notify-mcp
44
+ ```
45
+
46
+ Then add this server to the client's MCP configuration:
47
+
48
+ ```json
49
+ {
50
+ "mcpServers": {
51
+ "notify": {
52
+ "command": "notify-mcp"
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
58
+ You can also run the package without a global install:
59
+
60
+ ```json
61
+ {
62
+ "mcpServers": {
63
+ "notify": {
64
+ "command": "npx",
65
+ "args": ["-y", "@zakotoys/notify-mcp@latest"]
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
71
+ For Windows clients that do not resolve `npx` directly, use `cmd`:
72
+
73
+ ```json
74
+ {
75
+ "command": "cmd",
76
+ "args": ["/c", "npx", "-y", "@zakotoys/notify-mcp@latest"]
77
+ }
78
+ ```
79
+
80
+ ### MCP Inspector
81
+
82
+ ```bash
83
+ npx @modelcontextprotocol/inspector npx -y @zakotoys/notify-mcp@latest
84
+ ```
85
+
86
+ Call `notify_list_audio` first, then try `notify_desktop` or `notify`.
87
+
88
+ ## MCP tools
89
+
90
+ | Tool | Purpose |
91
+ | --- | --- |
92
+ | `notify_list_audio` | List the fixed built-in audio catalog. |
93
+ | `notify_play_audio` | Play one built-in audio track. |
94
+ | `notify_desktop` | Show a native desktop notification. |
95
+ | `notify` | Play optional built-in audio, then show a notification. |
96
+
97
+ ### `notify_list_audio`
98
+
99
+ Takes no arguments. Each item includes an `id`, label, description, frequency,
100
+ and duration.
101
+
102
+ ### `notify_play_audio`
103
+
104
+ ```json
105
+ { "audio": "zako" }
106
+ ```
107
+
108
+ `audio` must be `zako`. The server ships a short voice-only WAV asset, delegates
109
+ to the native player, and never accepts external audio paths or URLs.
110
+
111
+ The clip is a short vocal extraction from the source documented in
112
+ [`assets/audio/SOURCE.md`](assets/audio/SOURCE.md). Verify redistribution rights
113
+ before publishing a package containing this asset.
114
+
115
+ ### `notify_desktop`
116
+
117
+ ```json
118
+ {
119
+ "title": "Build complete",
120
+ "message": "notify-mcp finished successfully",
121
+ "subtitle": "Optional supplementary text"
122
+ }
123
+ ```
124
+
125
+ `title` is required and limited to 200 characters. `message` is required and
126
+ limited to 2,000 characters. `subtitle` is optional and limited to 200
127
+ characters.
128
+
129
+ ### `notify`
130
+
131
+ ```json
132
+ {
133
+ "audio": "zako",
134
+ "title": "Reminder",
135
+ "message": "The meeting starts in five minutes"
136
+ }
137
+ ```
138
+
139
+ `audio` is optional. When provided, playback completes before the desktop
140
+ notification is submitted.
141
+
142
+ ## Platform behavior
143
+
144
+ - **macOS:** `afplay` for sound and Notification Center through
145
+ `node-notifier`.
146
+ - **Windows:** PowerShell `Media.SoundPlayer` for sound and Windows
147
+ notification center through `node-notifier`.
148
+ - **Linux:** ALSA `aplay` for sound, with the notification backend selected by
149
+ `node-notifier`.
150
+
151
+ The operating system may require permission for the terminal application to
152
+ send notifications. The server does not upload notification text or audio.
153
+
154
+ ## Development
155
+
156
+ ```bash
157
+ npm install
158
+ npm run typecheck # TypeScript checks (including tests)
159
+ npm test # Vitest + V8 coverage
160
+ npm run build # Compile runtime files into dist/
161
+ npm run ci # All checks, coverage, build, and package dry-run
162
+ ```
163
+
164
+ The test suite uses injected fake adapters and an in-memory MCP transport, so
165
+ CI does not need a speaker, GUI, or real notification center.
166
+
167
+ ## Releases
168
+
169
+ Every `vX.Y.Z` tag is published by GitHub Actions after the full CI suite and a
170
+ package-version check pass. The publish workflow uses npm Trusted Publishing
171
+ with provenance and creates a GitHub Release with generated notes.
172
+
173
+ To release a new version:
174
+
175
+ ```bash
176
+ npm version patch # or minor / major
177
+ git push --follow-tags origin main
178
+ ```
179
+
180
+ Configure the npm package's Trusted Publisher for the repository
181
+ `zakotoys/notify-mcp`, workflow `publish.yml`, and the GitHub Actions provider.
182
+
183
+ ## Project scope
184
+
185
+ This project intentionally provides local notification primitives only. It
186
+ does not manage notification history, remote audio, user-uploaded media, or a
187
+ background daemon. MCP clients decide when a tool should be called.
188
+
189
+ ## License
190
+
191
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,148 @@
1
+ # @zakotoys/notify-mcp
2
+
3
+ [![npm](https://img.shields.io/npm/v/%40zakotoys%2Fnotify-mcp?logo=npm&logoColor=white)](https://www.npmjs.com/package/@zakotoys/notify-mcp)
4
+ [![CI](https://github.com/zakotoys/notify-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/zakotoys/notify-mcp/actions/workflows/ci.yml)
5
+ [![codecov](https://codecov.io/gh/zakotoys/notify-mcp/branch/main/graph/badge.svg)](https://codecov.io/gh/zakotoys/notify-mcp)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
7
+
8
+ [English](README.md) | **简体中文** | [日本語](README.ja-JP.md)
9
+
10
+ `notify-mcp` 是一个通过标准输入输出运行的 [Model Context Protocol](https://modelcontextprotocol.io/) 服务,为 MCP 客户端提供固定内置音效和原生桌面通知。支持 Claude Desktop、Cursor、OpenCode、Codex 及其他 MCP 客户端。
11
+
12
+ ## 功能
13
+
14
+ - 四个 MCP tools:列出音效、播放音效、发送通知,以及两者组合调用。
15
+ - 一个固定内置音效:`zako`。
16
+ - Windows 通知中心和 macOS 通知中心均通过 `node-notifier` 调用。
17
+ - 音频不接受文件路径、URL、上传或运行时新增,只能选择内置 id。
18
+ - 业务流程与操作系统适配器分离,可在没有扬声器或桌面会话的 CI 中测试。
19
+
20
+ ## 环境要求
21
+
22
+ - Node.js 20 或更高版本。
23
+ - Claude Desktop、Cursor、OpenCode、Codex 等 MCP 客户端。
24
+ - Windows 10+ 或 macOS。
25
+
26
+ Linux 在安装 `aplay` 和桌面通知后端时也可使用。
27
+
28
+ ## 快速开始
29
+
30
+ ### Claude Desktop、Cursor、OpenCode 或 Codex
31
+
32
+ ```bash
33
+ npm install --global @zakotoys/notify-mcp
34
+ ```
35
+
36
+ 在客户端 MCP 配置中加入:
37
+
38
+ ```json
39
+ {
40
+ "mcpServers": {
41
+ "notify": { "command": "notify-mcp" }
42
+ }
43
+ }
44
+ ```
45
+
46
+ 也可以使用 `npx`:
47
+
48
+ ```json
49
+ {
50
+ "mcpServers": {
51
+ "notify": {
52
+ "command": "npx",
53
+ "args": ["-y", "@zakotoys/notify-mcp@latest"]
54
+ }
55
+ }
56
+ }
57
+ ```
58
+
59
+ Windows 客户端如无法直接解析 `npx`,请使用 `cmd /c npx -y @zakotoys/notify-mcp@latest`。
60
+
61
+ ### MCP Inspector
62
+
63
+ ```bash
64
+ npx @modelcontextprotocol/inspector npx -y @zakotoys/notify-mcp@latest
65
+ ```
66
+
67
+ 先调用 `notify_list_audio`,再尝试 `notify_desktop` 或 `notify`。
68
+
69
+ ## MCP tools
70
+
71
+ | Tool | 用途 |
72
+ | --- | --- |
73
+ | `notify_list_audio` | 列出固定内置音效。 |
74
+ | `notify_play_audio` | 播放一条内置音效。 |
75
+ | `notify_desktop` | 显示原生桌面通知。 |
76
+ | `notify` | 播放可选音效后显示通知。 |
77
+
78
+ ### `notify_play_audio`
79
+
80
+ ```json
81
+ { "audio": "zako" }
82
+ ```
83
+
84
+ `audio` 只能是 `zako`。服务内置经过人声分离的 WAV 资源,调用系统播放器,不接受外部路径或 URL。
85
+
86
+ 该片段来自 [assets/audio/SOURCE.md](assets/audio/SOURCE.md) 中记录的视频并经过人声分离。发布包含此资源的软件包前,请确认具有再分发权限。
87
+
88
+ ### `notify_desktop`
89
+
90
+ ```json
91
+ {
92
+ "title": "构建完成",
93
+ "message": "notify-mcp 已成功构建",
94
+ "subtitle": "可选补充文本"
95
+ }
96
+ ```
97
+
98
+ 标题必填且最多 200 个字符,正文必填且最多 2000 个字符,副标题最多 200 个字符。
99
+
100
+ ### `notify`
101
+
102
+ ```json
103
+ {
104
+ "audio": "zako",
105
+ "title": "提醒",
106
+ "message": "会议将在五分钟后开始"
107
+ }
108
+ ```
109
+
110
+ `audio` 可省略;提供时保证先播放音效,再提交桌面通知。
111
+
112
+ ## 平台行为
113
+
114
+ - **macOS:** 使用 `afplay` 和 Notification Center。
115
+ - **Windows:** 使用 PowerShell `Media.SoundPlayer` 和 Windows 通知中心。
116
+ - **Linux:** 使用 ALSA `aplay`,通知后端由 `node-notifier` 选择。
117
+
118
+ 操作系统可能需要允许终端应用发送通知。服务不会上传通知正文或音频。
119
+
120
+ ## 开发
121
+
122
+ ```bash
123
+ npm install
124
+ npm run typecheck
125
+ npm run lint
126
+ npm test
127
+ npm run build
128
+ npm run ci
129
+ ```
130
+
131
+ 测试使用注入的 fake 适配器和内存 MCP transport,不需要真实扬声器或 GUI。
132
+
133
+ ## 发布版本
134
+
135
+ 推送 `vX.Y.Z` 标签后,GitHub Actions 会运行完整检查,校验标签与 package 版本一致,使用 npm Trusted Publishing 和 provenance 发布,并创建带自动生成变更说明的 GitHub Release。
136
+
137
+ ```bash
138
+ npm version patch # 或 minor / major
139
+ git push --follow-tags origin main
140
+ ```
141
+
142
+ ## 项目范围
143
+
144
+ 本项目只提供本地通知基础能力,不管理通知历史、远程音频、用户媒体或后台 daemon。
145
+
146
+ ## 许可证
147
+
148
+ MIT,详见 [LICENSE](LICENSE)。
@@ -0,0 +1,11 @@
1
+ # zako audio source
2
+
3
+ - Source video: https://www.youtube.com/watch?v=kqj7b59D85Y
4
+ - Source title: `雑魚 / 亞北ネル`
5
+ - Selected source interval: `01:39.85` to `01:40.75`
6
+ - Processing: Demucs vocal separation, mono downmix, 44.1 kHz PCM WAV, short fade-in/out
7
+ - Output: `zako.wav`
8
+
9
+ This file is included as a project asset at the request of the project owner.
10
+ Confirm that you have permission to redistribute the derived clip before
11
+ publishing the package or any release artifact.
Binary file
@@ -0,0 +1,9 @@
1
+ import type { AudioPlayer, AudioTrack } from "./types.js";
2
+ export type CommandRunner = (command: string, args: string[]) => Promise<void>;
3
+ /** Creates a tiny PCM WAV in a temporary directory and delegates playback to the OS. */
4
+ export declare class SystemAudioPlayer implements AudioPlayer {
5
+ private readonly platform;
6
+ private readonly run;
7
+ constructor(platform?: NodeJS.Platform, run?: CommandRunner);
8
+ play(track: AudioTrack): Promise<void>;
9
+ }
package/dist/audio.js ADDED
@@ -0,0 +1,64 @@
1
+ import { execFile } from "node:child_process";
2
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { promisify } from "node:util";
7
+ const execFileAsync = promisify(execFile);
8
+ /** Creates a tiny PCM WAV in a temporary directory and delegates playback to the OS. */
9
+ export class SystemAudioPlayer {
10
+ platform;
11
+ run;
12
+ constructor(platform = process.platform, run) {
13
+ this.platform = platform;
14
+ this.run = run ?? (async (command, args) => { await execFileAsync(command, args); });
15
+ }
16
+ async play(track) {
17
+ const directory = await mkdtemp(join(tmpdir(), "notify-mcp-"));
18
+ const file = track.assetFile
19
+ ? join(dirname(fileURLToPath(import.meta.url)), "..", "assets", "audio", track.assetFile)
20
+ : join(directory, `${track.id}.wav`);
21
+ try {
22
+ if (!track.assetFile)
23
+ await writeFile(file, makeWav(track));
24
+ if (this.platform === "darwin")
25
+ await this.run("afplay", [file]);
26
+ else if (this.platform === "win32") {
27
+ const script = `(New-Object Media.SoundPlayer '${file.replaceAll("'", "''")}').PlaySync()`;
28
+ await this.run("powershell", ["-NoProfile", "-NonInteractive", "-Command", script]);
29
+ }
30
+ else if (this.platform === "linux")
31
+ await this.run("aplay", ["-q", file]);
32
+ else
33
+ throw new Error(`Audio playback is unsupported on ${this.platform}`);
34
+ }
35
+ finally {
36
+ await rm(directory, { recursive: true, force: true });
37
+ }
38
+ }
39
+ }
40
+ function makeWav(track) {
41
+ const sampleRate = 44100;
42
+ const samples = Math.max(1, Math.round(sampleRate * track.durationMs / 1000));
43
+ const dataSize = samples * 2;
44
+ const wav = Buffer.alloc(44 + dataSize);
45
+ wav.write("RIFF", 0);
46
+ wav.writeUInt32LE(36 + dataSize, 4);
47
+ wav.write("WAVE", 8);
48
+ wav.write("fmt ", 12);
49
+ wav.writeUInt32LE(16, 16);
50
+ wav.writeUInt16LE(1, 20);
51
+ wav.writeUInt16LE(1, 22);
52
+ wav.writeUInt32LE(sampleRate, 24);
53
+ wav.writeUInt32LE(sampleRate * 2, 28);
54
+ wav.writeUInt16LE(2, 32);
55
+ wav.writeUInt16LE(16, 34);
56
+ wav.write("data", 36);
57
+ wav.writeUInt32LE(dataSize, 40);
58
+ for (let i = 0; i < samples; i += 1) {
59
+ const envelope = Math.min(1, i / (sampleRate * 0.01), (samples - i) / (sampleRate * 0.02));
60
+ wav.writeInt16LE(Math.round(Math.sin(2 * Math.PI * track.frequencyHz * i / sampleRate) * 0.22 * envelope * 32767), 44 + i * 2);
61
+ }
62
+ return wav;
63
+ }
64
+ //# sourceMappingURL=audio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audio.js","sourceRoot":"","sources":["../src/audio.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { AudioTrack } from "./types.js";
2
+ export declare function listBuiltInAudio(): readonly AudioTrack[];
3
+ export declare function getBuiltInAudio(id: string): AudioTrack;
@@ -0,0 +1,14 @@
1
+ const TRACKS = [
2
+ { id: "zako", label: "Zako", description: "A short vocal clip", frequencyHz: 0, durationMs: 900, assetFile: "zako.wav" }
3
+ ];
4
+ const BY_ID = new Map(TRACKS.map((track) => [track.id, track]));
5
+ export function listBuiltInAudio() {
6
+ return TRACKS;
7
+ }
8
+ export function getBuiltInAudio(id) {
9
+ const track = BY_ID.get(id);
10
+ if (!track)
11
+ throw new Error(`Unknown built-in audio: ${id}`);
12
+ return track;
13
+ }
14
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,GAA0B;IACpC,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,oBAAoB,EAAE,WAAW,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,SAAS,EAAE,UAAU,EAAE;CACzH,CAAC;AAEF,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;AAEhE,MAAM,UAAU,gBAAgB;IAC9B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,EAAU;IACxC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,EAAa,CAAC,CAAC;IACvC,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,EAAE,EAAE,CAAC,CAAC;IAC7D,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { SystemAudioPlayer } from "./audio.js";
4
+ import { SystemDesktopNotifier } from "./notifier.js";
5
+ import { NotificationService } from "./service.js";
6
+ import { createMcpServer } from "./server.js";
7
+ const service = new NotificationService(new SystemAudioPlayer(), new SystemDesktopNotifier());
8
+ const server = createMcpServer(service);
9
+ await server.connect(new StdioServerTransport());
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,OAAO,GAAG,IAAI,mBAAmB,CAAC,IAAI,iBAAiB,EAAE,EAAE,IAAI,qBAAqB,EAAE,CAAC,CAAC;AAC9F,MAAM,MAAM,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;AACxC,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { DesktopNotifier, NotificationInput } from "./types.js";
2
+ export declare class SystemDesktopNotifier implements DesktopNotifier {
3
+ notify(input: NotificationInput): Promise<void>;
4
+ }
@@ -0,0 +1,9 @@
1
+ import notifier from "node-notifier";
2
+ export class SystemDesktopNotifier {
3
+ async notify(input) {
4
+ await new Promise((resolve, reject) => {
5
+ notifier.notify({ title: input.title, message: input.message, subtitle: input.subtitle }, (error) => error ? reject(error) : resolve());
6
+ });
7
+ }
8
+ }
9
+ //# sourceMappingURL=notifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifier.js","sourceRoot":"","sources":["../src/notifier.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,eAAe,CAAC;AAGrC,MAAM,OAAO,qBAAqB;IAChC,KAAK,CAAC,MAAM,CAAC,KAAwB;QACnC,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC1C,QAAQ,CAAC,MAAM,CACb,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,EACxE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAC7C,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { NotificationService } from "./service.js";
3
+ export declare function createMcpServer(service: NotificationService): McpServer;
package/dist/server.js ADDED
@@ -0,0 +1,45 @@
1
+ import { createRequire } from "node:module";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { z } from "zod";
4
+ const packageInfo = createRequire(import.meta.url)("../package.json");
5
+ const audioId = z.enum(["zako"]);
6
+ const title = z.string().trim().min(1).max(200);
7
+ const message = z.string().trim().min(1).max(2000);
8
+ const subtitle = z.string().max(200).optional();
9
+ function toolInput(shape) {
10
+ return z.object(shape).meta({ additionalProperties: false });
11
+ }
12
+ export function createMcpServer(service) {
13
+ const server = new McpServer({ name: "notify-mcp", version: packageInfo.version });
14
+ server.registerTool("notify_list_audio", {
15
+ title: "List built-in audio",
16
+ description: "List the fixed audio tracks available to notify-mcp.",
17
+ inputSchema: toolInput({})
18
+ }, async () => ({ content: [{ type: "text", text: JSON.stringify(service.listAudio()) }] }));
19
+ server.registerTool("notify_play_audio", {
20
+ title: "Play built-in audio",
21
+ description: "Play one fixed, built-in audio track on this computer.",
22
+ inputSchema: toolInput({ audio: audioId })
23
+ }, async ({ audio }) => {
24
+ const result = await service.playAudio(audio);
25
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
26
+ });
27
+ server.registerTool("notify_desktop", {
28
+ title: "Show desktop notification",
29
+ description: "Show a native Windows or macOS desktop notification.",
30
+ inputSchema: toolInput({ title, message, subtitle })
31
+ }, async (input) => {
32
+ await service.notify(input);
33
+ return { content: [{ type: "text", text: "Desktop notification sent" }] };
34
+ });
35
+ server.registerTool("notify", {
36
+ title: "Notify with optional audio",
37
+ description: "Play an optional built-in audio track and show a desktop notification.",
38
+ inputSchema: toolInput({ title, message, subtitle, audio: audioId.optional() })
39
+ }, async (input) => {
40
+ await service.notifyWithAudio(input);
41
+ return { content: [{ type: "text", text: "Notification sent" }] };
42
+ });
43
+ return server;
44
+ }
45
+ //# 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,10 @@
1
+ import type { AudioPlayer, DesktopNotifier, NotificationInput, NotifyRequest } from "./types.js";
2
+ export declare class NotificationService {
3
+ private readonly audio;
4
+ private readonly desktop;
5
+ constructor(audio: AudioPlayer, desktop: DesktopNotifier);
6
+ listAudio(): import("./types.js").AudioTrack[];
7
+ playAudio(id: string): Promise<NotificationInput>;
8
+ notify(input: NotificationInput): Promise<void>;
9
+ notifyWithAudio(input: NotifyRequest): Promise<void>;
10
+ }
@@ -0,0 +1,39 @@
1
+ import { getBuiltInAudio } from "./catalog.js";
2
+ export class NotificationService {
3
+ audio;
4
+ desktop;
5
+ constructor(audio, desktop) {
6
+ this.audio = audio;
7
+ this.desktop = desktop;
8
+ }
9
+ listAudio() { return getBuiltInAudioList(); }
10
+ async playAudio(id) {
11
+ const track = getBuiltInAudio(id);
12
+ await this.audio.play(track);
13
+ return { title: "Audio played", message: track.label, subtitle: track.description };
14
+ }
15
+ async notify(input) {
16
+ validateNotification(input);
17
+ await this.desktop.notify(input);
18
+ }
19
+ async notifyWithAudio(input) {
20
+ validateNotification(input);
21
+ if (input.audio)
22
+ await this.audio.play(getBuiltInAudio(input.audio));
23
+ await this.desktop.notify(input);
24
+ }
25
+ }
26
+ function getBuiltInAudioList() { return ["zako"].map(getBuiltInAudio); }
27
+ function validateNotification(input) {
28
+ if (!input.title.trim())
29
+ throw new Error("title must not be empty");
30
+ if (!input.message.trim())
31
+ throw new Error("message must not be empty");
32
+ if (input.title.length > 200)
33
+ throw new Error("title is too long");
34
+ if (input.message.length > 2000)
35
+ throw new Error("message is too long");
36
+ if (input.subtitle && input.subtitle.length > 200)
37
+ throw new Error("subtitle is too long");
38
+ }
39
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAG/C,MAAM,OAAO,mBAAmB;IACD,KAAK;IAAgC,OAAO;IAAzE,YAA6B,KAAkB,EAAmB,OAAwB;qBAA7D,KAAK;uBAAgC,OAAO;IAAoB,CAAC;IAE9F,SAAS,KAAK,OAAO,mBAAmB,EAAE,CAAC,CAAC,CAAC;IAE7C,KAAK,CAAC,SAAS,CAAC,EAAU;QACxB,MAAM,KAAK,GAAG,eAAe,CAAC,EAAE,CAAC,CAAC;QAClC,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7B,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,KAAK,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC;IACtF,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAwB;QACnC,oBAAoB,CAAC,KAAK,CAAC,CAAC;QAC5B,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,KAAoB;QACxC,oBAAoB,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,KAAK,CAAC,KAAK;YAAE,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QACrE,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;CACF;AAED,SAAS,mBAAmB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;AAExE,SAAS,oBAAoB,CAAC,KAAwB;IACpD,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IACpE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;IACxE,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;IACnE,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACxE,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;AAC7F,CAAC"}
@@ -0,0 +1,23 @@
1
+ export type AudioId = "zako";
2
+ export interface AudioTrack {
3
+ readonly id: AudioId;
4
+ readonly label: string;
5
+ readonly description: string;
6
+ readonly frequencyHz: number;
7
+ readonly durationMs: number;
8
+ readonly assetFile?: string;
9
+ }
10
+ export interface AudioPlayer {
11
+ play(track: AudioTrack): Promise<void>;
12
+ }
13
+ export interface DesktopNotifier {
14
+ notify(input: NotificationInput): Promise<void>;
15
+ }
16
+ export interface NotificationInput {
17
+ title: string;
18
+ message: string;
19
+ subtitle?: string;
20
+ }
21
+ export interface NotifyRequest extends NotificationInput {
22
+ audio?: AudioId;
23
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@zakotoys/notify-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for built-in audio playback and desktop notifications",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/zakotoys/notify-mcp.git"
9
+ },
10
+ "homepage": "https://github.com/zakotoys/notify-mcp#readme",
11
+ "bugs": { "url": "https://github.com/zakotoys/notify-mcp/issues" },
12
+ "type": "module",
13
+ "bin": {
14
+ "notify-mcp": "dist/index.js"
15
+ },
16
+ "publishConfig": { "access": "public" },
17
+ "files": [
18
+ "dist",
19
+ "assets",
20
+ "README.md",
21
+ "README.zh-CN.md",
22
+ "LICENSE"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsc -p tsconfig.build.json",
26
+ "start": "node dist/index.js",
27
+ "test": "vitest run --coverage",
28
+ "test:watch": "vitest",
29
+ "typecheck": "tsc --noEmit -p tsconfig.json",
30
+ "lint": "biome lint .",
31
+ "ci": "npm run typecheck && npm run lint && npm test && npm run build && npm pack --dry-run",
32
+ "prepublishOnly": "npm run typecheck && npm run lint && npm test && npm run build"
33
+ },
34
+ "engines": {
35
+ "node": ">=20"
36
+ },
37
+ "dependencies": {
38
+ "@modelcontextprotocol/sdk": "^1.23.0",
39
+ "node-notifier": "^10.0.1",
40
+ "zod": "^4.0.0"
41
+ },
42
+ "devDependencies": {
43
+ "@biomejs/biome": "^2.5.11",
44
+ "@types/node": "^24.0.0",
45
+ "@types/node-notifier": "^8.0.5",
46
+ "@vitest/coverage-v8": "^4.0.0",
47
+ "typescript": "^7.0.0",
48
+ "vitest": "^4.0.0"
49
+ }
50
+ }