omni-notify-mcp 1.1.6 → 1.1.7

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.
Binary file
package/dist/ui/server.js CHANGED
@@ -970,6 +970,51 @@ BEHAVIORAL RULES for every client that connects:
970
970
  6. The 'get_idle_*' and 'get_dnd_status' tools are informational. You can
971
971
  inspect them if you want to explain a delivery decision, but they are NOT
972
972
  required pre-flights — the server gates server-side.
973
+
974
+ 7. If your tool call fails with "MCP server not connected" / "transport
975
+ closed" / similar — the SERVER IS ALMOST CERTAINLY FINE. Other clients are
976
+ connected to the same server right now. Only YOUR client's transport
977
+ broke (commonly: stdio child process died, or HTTP client gave up after a
978
+ transient slow response at startup). DO NOT tell the user "notify is down"
979
+ without first probing the server directly.
980
+
981
+ Probe before declaring the server dead:
982
+ curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3737/mcp
983
+ - 406 (Not Acceptable) = server is healthy, doing MCP protocol
984
+ negotiation. Your client is the broken party. Use the bypass below.
985
+ - Connection refused / timeout = server is genuinely down. Tell the user.
986
+
987
+ HTTP-transport bypass when your MCP client is broken (works from any
988
+ shell that has curl). Three calls, stateful — the session id from step 1
989
+ must be passed in mcp-session-id on every subsequent call:
990
+
991
+ # 1) initialize and capture session id from response headers
992
+ SID=$(curl -s -i -X POST http://localhost:3737/mcp \
993
+ -H "Content-Type: application/json" \
994
+ -H "Accept: application/json, text/event-stream" \
995
+ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl-bridge","version":"1.0"}}}' \
996
+ | grep -i "^mcp-session-id:" | awk '{print $2}' | tr -d '\r')
997
+
998
+ # 2) ack initialized (no response body, fire-and-forget)
999
+ curl -s -X POST http://localhost:3737/mcp \
1000
+ -H "Content-Type: application/json" \
1001
+ -H "Accept: application/json, text/event-stream" \
1002
+ -H "mcp-session-id: $SID" \
1003
+ -d '{"jsonrpc":"2.0","method":"notifications/initialized"}' >/dev/null
1004
+
1005
+ # 3) call any tool — successful response includes "Sent via: <channel>"
1006
+ curl -s -X POST http://localhost:3737/mcp \
1007
+ -H "Content-Type: application/json" \
1008
+ -H "Accept: application/json, text/event-stream" \
1009
+ -H "mcp-session-id: $SID" \
1010
+ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"notify","arguments":{"message":"YOUR MESSAGE","priority":"normal"}}}'
1011
+
1012
+ Same pattern works for 'ask', 'poll', 'get_idle_seconds', etc. — change
1013
+ the "name" and "arguments" in step 3. Reuse the same $SID for all calls
1014
+ in one bypass session.
1015
+
1016
+ Bottom line: rule #1 still applies. If you owe the user a notify and your
1017
+ MCP client is broken, you owe them the bypass — not an excuse.
973
1018
  `.trim();
974
1019
  function createMcpServer(clientId, sessionTag) {
975
1020
  const server = new McpServer({ name: "notify-mcp", version: "1.0.0" }, { instructions: MCP_INSTRUCTIONS });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omni-notify-mcp",
3
- "version": "1.1.6",
3
+ "version": "1.1.7",
4
4
  "description": "An MCP server that lets AI agents (Claude, Cursor, etc.) reach you on any channel — desktop, Telegram, SMS, email — with two-way ask/reply, real-time inbox push, Do Not Disturb, idle gating, multi-session routing, and a one-page web UI for setup. Zero config code; configure once, agents call notify/ask.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
package/ui/public/app.js CHANGED
@@ -117,14 +117,16 @@ function updateBadges() {
117
117
  email.connectedEmail ? "Connected" : email.clientId ? "Credentials saved" : "Not configured");
118
118
 
119
119
  const tg = config.telegram ?? {};
120
+ const tgReady = tg.token && tg.chatId;
120
121
  setBadge("telegram",
121
- tg.enabled && tg.token && tg.chatId ? "ok" : tg.token ? "warn" : "idle",
122
- tg.enabled && tg.token && tg.chatId ? "Configured" : tg.token ? "Incomplete" : "Not configured");
122
+ tg.enabled && tgReady ? "ok" : tgReady ? "warn" : tg.token ? "warn" : "idle",
123
+ tg.enabled && tgReady ? "Configured" : tgReady ? "Disabled" : tg.token ? "Incomplete" : "Not configured");
123
124
 
124
- const sms = config.sms ?? {};
125
+ const sms = config.sms ?? {};
126
+ const smsReady = sms.accountSid && sms.authToken;
125
127
  setBadge("sms",
126
- sms.enabled && sms.accountSid && sms.authToken ? "ok" : sms.accountSid ? "warn" : "idle",
127
- sms.enabled && sms.accountSid && sms.authToken ? "Configured" : sms.accountSid ? "Incomplete" : "Not configured");
128
+ sms.enabled && smsReady ? "ok" : smsReady ? "warn" : sms.accountSid ? "warn" : "idle",
129
+ sms.enabled && smsReady ? "Configured" : smsReady ? "Disabled" : sms.accountSid ? "Incomplete" : "Not configured");
128
130
 
129
131
  // DND badge: "Active" (red), "Scheduled" (warn), or "Off" (idle)
130
132
  const dnd = config.dnd ?? {};
@@ -187,7 +189,8 @@ async function loadVoices() {
187
189
  for (const v of byLocale[locale].sort((a, b) => a.shortName.localeCompare(b.shortName))) {
188
190
  const opt = document.createElement("option");
189
191
  opt.value = v.shortName;
190
- opt.textContent = `${v.shortName.replace(locale + "-", "")} (${v.gender})`;
192
+ const name = v.shortName.replace(locale + "-", "").replace(/Neural$/, "").replace(/Multilingual$/, " (Multi)");
193
+ opt.textContent = `${name} · ${v.gender}`;
191
194
  if (v.shortName === current) opt.selected = true;
192
195
  og.appendChild(opt);
193
196
  }
@@ -63,8 +63,9 @@
63
63
  <span class="toggle-lbl">Speak notification</span>
64
64
  <button class="btn btn-sm btn-ghost" onclick="testTts()">Test voice</button>
65
65
  </div>
66
- <div id="tts-voice-row" class="actions" style="margin-top:6px; display:none">
67
- <select id="desktop-tts-voice" onchange="saveDesktop()" style="flex:1; min-width:0"></select>
66
+ <div id="tts-voice-row" class="fg" style="margin-top:8px; display:none">
67
+ <label>Voice</label>
68
+ <select id="desktop-tts-voice" onchange="saveDesktop()"></select>
68
69
  </div>
69
70
  <span id="os-hint" class="os-tag hidden"></span>
70
71
  </div>
@@ -102,11 +102,11 @@ main {
102
102
  flex: 1;
103
103
  min-height: 0;
104
104
  height: 100%;
105
- overflow: hidden;
106
- display: flex;
107
- flex-direction: column;
108
- flex-wrap: wrap;
109
- align-content: flex-start;
105
+ overflow: auto;
106
+ display: grid;
107
+ grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
108
+ grid-auto-rows: min-content;
109
+ align-content: start;
110
110
  gap: 12px;
111
111
  }
112
112
 
@@ -207,8 +207,9 @@ main {
207
207
  /* ── Cards ───────────────────────────────────────────────────────────────── */
208
208
 
209
209
  .card {
210
- width: 280px;
210
+ width: auto;
211
211
  max-width: 100%;
212
+ min-width: 0;
212
213
  background: var(--surface);
213
214
  border: 2px solid #4a4a5a;
214
215
  border-radius: var(--r);
@@ -362,6 +363,27 @@ input[type="time"] {
362
363
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.08);
363
364
  }
364
365
  input::placeholder { color: #cdcdda; opacity: 1; }
366
+
367
+ select {
368
+ width: 100%;
369
+ max-width: 100%;
370
+ box-sizing: border-box;
371
+ padding: 8px 11px;
372
+ border: 2px solid #7a7a95;
373
+ border-radius: 7px;
374
+ font-size: 14px;
375
+ font-weight: 500;
376
+ color: #ffffff;
377
+ background: #55556a;
378
+ outline: none;
379
+ transition: border-color .15s, box-shadow .15s;
380
+ color-scheme: dark;
381
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.08);
382
+ cursor: pointer;
383
+ text-overflow: ellipsis;
384
+ }
385
+ select:hover { border-color: #9a9ab5; }
386
+ select:focus { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(124,109,250,.15); }
365
387
  input[type="text"]:hover,
366
388
  input[type="email"]:hover,
367
389
  input[type="tel"]:hover,