mcpmon 0.1.4 → 0.3.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.
@@ -104,7 +104,7 @@ jobs:
104
104
  target: linux-x64
105
105
  - os: macos-latest
106
106
  target: darwin-arm64
107
- - os: macos-13
107
+ - os: macos-15-intel
108
108
  target: darwin-x64
109
109
  steps:
110
110
  - uses: actions/checkout@v4
package/.nojekyll ADDED
File without changes
@@ -0,0 +1,44 @@
1
+ # ============================================================================
2
+ # .pre-commit-config.yaml
3
+ # Install: pip install pre-commit && pre-commit install
4
+ # Run manually: pre-commit run --all-files
5
+ # ============================================================================
6
+
7
+ repos:
8
+ # -------------------------------------------------------------------------
9
+ # SECRET DETECTION
10
+ # -------------------------------------------------------------------------
11
+
12
+ - repo: https://github.com/gitleaks/gitleaks
13
+ rev: v8.18.1
14
+ hooks:
15
+ - id: gitleaks
16
+ name: Detect secrets with gitleaks
17
+
18
+ # -------------------------------------------------------------------------
19
+ # GENERAL
20
+ # -------------------------------------------------------------------------
21
+
22
+ - repo: https://github.com/pre-commit/pre-commit-hooks
23
+ rev: v4.5.0
24
+ hooks:
25
+ - id: trailing-whitespace
26
+ - id: end-of-file-fixer
27
+ - id: check-yaml
28
+ - id: check-json
29
+ - id: check-toml
30
+ - id: check-added-large-files
31
+ args: ['--maxkb=500']
32
+ - id: detect-private-key
33
+ - id: detect-aws-credentials
34
+
35
+ # -------------------------------------------------------------------------
36
+ # PYTHON
37
+ # -------------------------------------------------------------------------
38
+
39
+ - repo: https://github.com/astral-sh/ruff-pre-commit
40
+ rev: v0.3.0
41
+ hooks:
42
+ - id: ruff
43
+ args: [--fix]
44
+ - id: ruff-format
package/README.md CHANGED
@@ -25,23 +25,57 @@ mcpmon --watch src/ -- python -m my_mcp_server
25
25
 
26
26
  ### Options
27
27
 
28
- - `--watch, -w` - Directory to watch (default: current directory)
29
- - `--ext, -e` - File extensions to watch, comma-separated (default: py)
28
+ | Option | Description |
29
+ |--------|-------------|
30
+ | `-w, --watch <dir>` | Directory to watch (default: `.`) |
31
+ | `-e, --ext <exts>` | Extensions to watch, comma-separated (default: `py`) |
32
+ | `-q, --quiet` | Only show errors |
33
+ | `-v, --verbose` | Show file change details |
34
+ | `--debug` | Show all debug output |
35
+ | `-t, --timestamps` | Include timestamps in output |
36
+ | `-l, --log-file <file>` | Also write logs to file |
37
+
38
+ ### Logging Levels
39
+
40
+ ```
41
+ --quiet Only errors
42
+ (default) Start, stop, restart events + PID
43
+ --verbose + file change details
44
+ --debug + everything (ignored files, spawning, exit codes)
45
+ ```
30
46
 
31
47
  ### Examples
32
48
 
33
49
  ```bash
34
- # Watch current directory for .py changes
50
+ # Basic usage - watch current directory for .py changes
35
51
  mcpmon -- python server.py
36
52
 
37
53
  # Watch src/ for .py and .json changes
38
54
  mcpmon --watch src/ --ext py,json -- python -m myserver
39
55
 
56
+ # With timestamps and verbose output
57
+ mcpmon --timestamps --verbose -- python server.py
58
+
59
+ # Log to file for debugging
60
+ mcpmon --debug --log-file mcpmon.log -- python server.py
61
+
40
62
  # With crucible-mcp
41
63
  mcpmon --watch src/crucible/ -- crucible-mcp
42
64
 
43
65
  # With sage-mcp
44
- mcpmon --watch ~/.sage/ --ext py,yaml -- sage-mcp
66
+ mcpmon --watch sage/ --ext py -- python -m sage.mcp_server
67
+ ```
68
+
69
+ ### Sample Output
70
+
71
+ ```
72
+ [mcpmon 16:08:50] Watching sage for .py changes
73
+ [mcpmon 16:08:50 pid:53307] Started: python -m sage.mcp_server
74
+ [mcpmon 16:08:54 pid:53307] Restarting...
75
+ [mcpmon 16:08:54 pid:53411] Started: python -m sage.mcp_server
76
+ [mcpmon 16:08:54 pid:53411] Restart #1 complete
77
+ [mcpmon 16:08:57] Received SIGTERM, shutting down...
78
+ [mcpmon 16:08:57] Shutdown complete (restarts: 1)
45
79
  ```
46
80
 
47
81
  ## MCP Config
@@ -65,3 +99,24 @@ Use mcpmon in your `.mcp.json` for hot reload during development:
65
99
  2. Watches specified directory for file changes
66
100
  3. On change: SIGTERM → wait 2s → SIGKILL → restart
67
101
  4. Claude Code automatically reconnects to the restarted server
102
+
103
+ ## Dual Implementation
104
+
105
+ mcpmon ships as both:
106
+ - **Bun/TypeScript** (`mcpmon.ts`) - Zero dependencies, fast startup
107
+ - **Python** (`mcpmon.py`) - Uses `watchfiles` for robust file watching
108
+
109
+ Both implementations have feature parity.
110
+
111
+ ## Development
112
+
113
+ ```bash
114
+ # Install dev dependencies (Python)
115
+ pip install -e ".[dev]"
116
+
117
+ # Run Python tests (27 tests)
118
+ pytest tests/ -v
119
+
120
+ # Run Bun/TS tests (12 tests)
121
+ bun test
122
+ ```
package/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Allow running mcpmon as `python -m mcpmon`."""
2
+
3
+ from mcpmon import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
package/index.html ADDED
@@ -0,0 +1,414 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>mcpmon - Hot reload for MCP servers</title>
7
+ <meta name="description" content="Hot reload for MCP servers. Like nodemon, but for MCP.">
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=VT323&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">
11
+ <style>
12
+ :root {
13
+ --bg-primary: #0a0a0f;
14
+ --bg-secondary: #12121a;
15
+ --bg-tertiary: #1a1a2e;
16
+ --neon-cyan: #00f5ff;
17
+ --neon-magenta: #ff00aa;
18
+ --neon-yellow: #ffe600;
19
+ --neon-green: #00ff88;
20
+ --text-primary: #e0e0e0;
21
+ --text-secondary: #888888;
22
+ }
23
+
24
+ * {
25
+ margin: 0;
26
+ padding: 0;
27
+ box-sizing: border-box;
28
+ }
29
+
30
+ body {
31
+ background: var(--bg-primary);
32
+ color: var(--text-primary);
33
+ font-family: 'VT323', monospace;
34
+ font-size: 18px;
35
+ line-height: 1.6;
36
+ min-height: 100vh;
37
+ overflow-x: hidden;
38
+ }
39
+
40
+ /* Scanlines overlay */
41
+ body::before {
42
+ content: '';
43
+ position: fixed;
44
+ top: 0;
45
+ left: 0;
46
+ width: 100%;
47
+ height: 100%;
48
+ background: repeating-linear-gradient(
49
+ 0deg,
50
+ rgba(0, 0, 0, 0.1),
51
+ rgba(0, 0, 0, 0.1) 1px,
52
+ transparent 1px,
53
+ transparent 2px
54
+ );
55
+ pointer-events: none;
56
+ z-index: 1000;
57
+ }
58
+
59
+ /* CRT vignette */
60
+ body::after {
61
+ content: '';
62
+ position: fixed;
63
+ top: 0;
64
+ left: 0;
65
+ width: 100%;
66
+ height: 100%;
67
+ background: radial-gradient(
68
+ ellipse at center,
69
+ transparent 0%,
70
+ transparent 60%,
71
+ rgba(0, 0, 0, 0.4) 100%
72
+ );
73
+ pointer-events: none;
74
+ z-index: 999;
75
+ }
76
+
77
+ .container {
78
+ max-width: 900px;
79
+ margin: 0 auto;
80
+ padding: 2rem;
81
+ }
82
+
83
+ /* Header */
84
+ header {
85
+ display: flex;
86
+ justify-content: space-between;
87
+ align-items: center;
88
+ padding: 1rem 0;
89
+ border-bottom: 2px solid var(--bg-tertiary);
90
+ margin-bottom: 3rem;
91
+ }
92
+
93
+ .logo {
94
+ font-family: 'Press Start 2P', monospace;
95
+ font-size: 14px;
96
+ color: var(--neon-cyan);
97
+ text-decoration: none;
98
+ }
99
+
100
+ .github-link {
101
+ color: var(--text-secondary);
102
+ text-decoration: none;
103
+ font-size: 20px;
104
+ transition: color 0.2s;
105
+ }
106
+
107
+ .github-link:hover {
108
+ color: var(--neon-cyan);
109
+ }
110
+
111
+ /* Hero */
112
+ .hero {
113
+ text-align: center;
114
+ padding: 2rem 0 3rem;
115
+ }
116
+
117
+ .ascii-art {
118
+ font-family: 'Fira Code', monospace;
119
+ font-size: 14px;
120
+ color: var(--neon-cyan);
121
+ text-shadow: 0 0 10px var(--neon-cyan), 0 0 20px var(--neon-cyan);
122
+ margin-bottom: 2rem;
123
+ line-height: 1.2;
124
+ white-space: pre;
125
+ }
126
+
127
+ @media (max-width: 600px) {
128
+ .ascii-art {
129
+ font-size: 10px;
130
+ }
131
+ }
132
+
133
+ .tagline {
134
+ font-family: 'VT323', monospace;
135
+ font-size: 28px;
136
+ color: var(--text-primary);
137
+ margin-bottom: 0.5rem;
138
+ }
139
+
140
+ .subtext {
141
+ font-size: 22px;
142
+ color: var(--text-secondary);
143
+ }
144
+
145
+ /* Terminal */
146
+ .terminal {
147
+ background: var(--bg-secondary);
148
+ border: 2px solid var(--bg-tertiary);
149
+ border-radius: 4px;
150
+ padding: 1.5rem;
151
+ margin: 3rem 0;
152
+ font-family: 'Fira Code', monospace;
153
+ font-size: 14px;
154
+ overflow-x: auto;
155
+ box-shadow:
156
+ 0 0 20px rgba(0, 245, 255, 0.1),
157
+ inset 0 0 60px rgba(0, 0, 0, 0.3);
158
+ }
159
+
160
+ .terminal-header {
161
+ display: flex;
162
+ gap: 6px;
163
+ margin-bottom: 1rem;
164
+ padding-bottom: 0.75rem;
165
+ border-bottom: 1px solid var(--bg-tertiary);
166
+ }
167
+
168
+ .terminal-dot {
169
+ width: 12px;
170
+ height: 12px;
171
+ border-radius: 50%;
172
+ }
173
+
174
+ .terminal-dot.red { background: #ff5f56; }
175
+ .terminal-dot.yellow { background: #ffbd2e; }
176
+ .terminal-dot.green { background: #27ca40; }
177
+
178
+ .terminal-line {
179
+ margin: 0.25rem 0;
180
+ opacity: 0;
181
+ animation: typeIn 0.3s ease forwards;
182
+ }
183
+
184
+ .terminal-line:nth-child(1) { animation-delay: 0.2s; }
185
+ .terminal-line:nth-child(2) { animation-delay: 0.6s; }
186
+ .terminal-line:nth-child(3) { animation-delay: 1.0s; }
187
+ .terminal-line:nth-child(4) { animation-delay: 1.8s; }
188
+ .terminal-line:nth-child(5) { animation-delay: 2.2s; }
189
+ .terminal-line:nth-child(6) { animation-delay: 2.6s; }
190
+
191
+ @keyframes typeIn {
192
+ from { opacity: 0; transform: translateX(-10px); }
193
+ to { opacity: 1; transform: translateX(0); }
194
+ }
195
+
196
+ .prompt { color: var(--neon-green); }
197
+ .cmd { color: var(--text-primary); }
198
+ .output { color: var(--neon-cyan); }
199
+ .file { color: var(--neon-yellow); }
200
+
201
+ /* Install cards */
202
+ .install-section {
203
+ margin: 3rem 0;
204
+ }
205
+
206
+ .section-title {
207
+ font-family: 'Press Start 2P', monospace;
208
+ font-size: 12px;
209
+ color: var(--neon-cyan);
210
+ margin-bottom: 1.5rem;
211
+ }
212
+
213
+ .install-grid {
214
+ display: grid;
215
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
216
+ gap: 1rem;
217
+ }
218
+
219
+ .install-card {
220
+ background: var(--bg-secondary);
221
+ border: 2px solid var(--bg-tertiary);
222
+ padding: 1.5rem;
223
+ text-align: center;
224
+ transition: all 0.2s;
225
+ }
226
+
227
+ .install-card:hover {
228
+ border-color: var(--neon-cyan);
229
+ box-shadow: 0 0 20px rgba(0, 245, 255, 0.2);
230
+ }
231
+
232
+ .install-card h3 {
233
+ font-family: 'Press Start 2P', monospace;
234
+ font-size: 11px;
235
+ color: var(--text-primary);
236
+ margin-bottom: 1rem;
237
+ }
238
+
239
+ .install-card code {
240
+ display: block;
241
+ background: var(--bg-primary);
242
+ padding: 0.75rem;
243
+ font-family: 'Fira Code', monospace;
244
+ font-size: 13px;
245
+ color: var(--neon-green);
246
+ border: 1px solid var(--bg-tertiary);
247
+ }
248
+
249
+ .install-card .note {
250
+ font-size: 14px;
251
+ color: var(--text-secondary);
252
+ margin-top: 0.75rem;
253
+ }
254
+
255
+ /* Usage */
256
+ .usage-section {
257
+ margin: 3rem 0;
258
+ }
259
+
260
+ .code-block {
261
+ background: var(--bg-secondary);
262
+ border: 2px solid var(--bg-tertiary);
263
+ padding: 1.5rem;
264
+ font-family: 'Fira Code', monospace;
265
+ font-size: 13px;
266
+ overflow-x: auto;
267
+ }
268
+
269
+ .code-block .comment { color: var(--text-secondary); }
270
+ .code-block .key { color: var(--neon-magenta); }
271
+ .code-block .string { color: var(--neon-green); }
272
+ .code-block .bracket { color: var(--text-primary); }
273
+
274
+ /* Footer */
275
+ footer {
276
+ margin-top: 4rem;
277
+ padding: 2rem 0;
278
+ border-top: 2px solid var(--bg-tertiary);
279
+ text-align: center;
280
+ }
281
+
282
+ .footer-links {
283
+ display: flex;
284
+ justify-content: center;
285
+ gap: 2rem;
286
+ margin-bottom: 1.5rem;
287
+ }
288
+
289
+ .footer-links a {
290
+ color: var(--text-secondary);
291
+ text-decoration: none;
292
+ font-size: 18px;
293
+ transition: color 0.2s;
294
+ }
295
+
296
+ .footer-links a:hover {
297
+ color: var(--neon-cyan);
298
+ }
299
+
300
+ .footer-family {
301
+ font-size: 16px;
302
+ color: var(--text-secondary);
303
+ }
304
+
305
+ .footer-family a {
306
+ color: var(--neon-cyan);
307
+ text-decoration: none;
308
+ }
309
+
310
+ .footer-family a:hover {
311
+ text-decoration: underline;
312
+ }
313
+ </style>
314
+ </head>
315
+ <body>
316
+ <div class="container">
317
+ <header>
318
+ <a href="/" class="logo">mcpmon</a>
319
+ <a href="https://github.com/b17z/mcpmon" class="github-link" aria-label="GitHub">[GitHub]</a>
320
+ </header>
321
+
322
+ <main>
323
+ <section class="hero">
324
+ <pre class="ascii-art">
325
+ ░█▄█░█▀▀░█▀█░█▄█░█▀█░█▀█
326
+ ░█░█░█░░░█▀▀░█░█░█░█░█░█
327
+ ░▀░▀░▀▀▀░▀░░░▀░▀░▀▀▀░▀░▀</pre>
328
+ <p class="tagline">Hot reload for MCP servers.</p>
329
+ <p class="subtext">Change code. Server restarts. Context stays.</p>
330
+ </section>
331
+
332
+ <section class="terminal">
333
+ <div class="terminal-header">
334
+ <span class="terminal-dot red"></span>
335
+ <span class="terminal-dot yellow"></span>
336
+ <span class="terminal-dot green"></span>
337
+ </div>
338
+ <div class="terminal-content">
339
+ <div class="terminal-line"><span class="prompt">$</span> <span class="cmd">mcpmon --timestamps --verbose --watch src/ -- python -m my_server</span></div>
340
+ <div class="terminal-line"><span class="output">[mcpmon 14:32:01]</span> Watching src/ for .py changes</div>
341
+ <div class="terminal-line"><span class="output">[mcpmon 14:32:01 <span class="file">pid:48291</span>]</span> Started: python -m my_server</div>
342
+ <div class="terminal-line"><span class="output">[mcpmon 14:32:15]</span> File modified: <span class="file">tools.py</span></div>
343
+ <div class="terminal-line"><span class="output">[mcpmon 14:32:15 <span class="file">pid:48291</span>]</span> Restarting...</div>
344
+ <div class="terminal-line"><span class="output">[mcpmon 14:32:15 <span class="file">pid:48342</span>]</span> Restart #1 complete</div>
345
+ </div>
346
+ </section>
347
+
348
+ <section class="install-section">
349
+ <h2 class="section-title">LOGGING LEVELS</h2>
350
+ <div class="install-grid">
351
+ <div class="install-card">
352
+ <h3>--quiet</h3>
353
+ <code>errors only</code>
354
+ </div>
355
+ <div class="install-card">
356
+ <h3>default</h3>
357
+ <code>start/stop + PIDs</code>
358
+ </div>
359
+ <div class="install-card">
360
+ <h3>--verbose</h3>
361
+ <code>+ file changes</code>
362
+ </div>
363
+ <div class="install-card">
364
+ <h3>--debug</h3>
365
+ <code>+ everything</code>
366
+ </div>
367
+ </div>
368
+ </section>
369
+
370
+ <section class="install-section">
371
+ <h2 class="section-title">INSTALL</h2>
372
+ <div class="install-grid">
373
+ <div class="install-card">
374
+ <h3>Python</h3>
375
+ <code>pip install mcpmon</code>
376
+ <p class="note">requires watchfiles</p>
377
+ </div>
378
+ <div class="install-card">
379
+ <h3>Bun</h3>
380
+ <code>bunx mcpmon</code>
381
+ <p class="note">no install needed</p>
382
+ </div>
383
+ <div class="install-card">
384
+ <h3>Binary</h3>
385
+ <code>download & run</code>
386
+ <p class="note">no dependencies</p>
387
+ </div>
388
+ </div>
389
+ </section>
390
+
391
+ <section class="usage-section">
392
+ <h2 class="section-title">MCP CONFIG</h2>
393
+ <pre class="code-block"><span class="bracket">{</span>
394
+ <span class="key">"mcpServers"</span><span class="bracket">:</span> <span class="bracket">{</span>
395
+ <span class="key">"my-server"</span><span class="bracket">:</span> <span class="bracket">{</span>
396
+ <span class="key">"command"</span><span class="bracket">:</span> <span class="string">"mcpmon"</span><span class="bracket">,</span>
397
+ <span class="key">"args"</span><span class="bracket">:</span> <span class="bracket">[</span><span class="string">"--watch"</span><span class="bracket">,</span> <span class="string">"src/"</span><span class="bracket">,</span> <span class="string">"--"</span><span class="bracket">,</span> <span class="string">"python"</span><span class="bracket">,</span> <span class="string">"-m"</span><span class="bracket">,</span> <span class="string">"my_server"</span><span class="bracket">]</span>
398
+ <span class="bracket">}</span>
399
+ <span class="bracket">}</span>
400
+ <span class="bracket">}</span></pre>
401
+ </section>
402
+ </main>
403
+
404
+ <footer>
405
+ <div class="footer-links">
406
+ <a href="https://github.com/b17z/mcpmon">GitHub</a>
407
+ <a href="https://pypi.org/project/mcpmon/">PyPI</a>
408
+ <a href="https://www.npmjs.com/package/mcpmon">npm</a>
409
+ </div>
410
+ <p class="footer-family">part of: <a href="https://github.com/b17z/sage">sage</a> | <a href="https://github.com/b17z/crucible">crucible</a></p>
411
+ </footer>
412
+ </div>
413
+ </body>
414
+ </html>