mcgram 0.2.0__tar.gz
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.
- mcgram-0.2.0/.env.example +3 -0
- mcgram-0.2.0/.github/workflows/ci.yml +50 -0
- mcgram-0.2.0/.gitignore +34 -0
- mcgram-0.2.0/CHANGELOG.md +127 -0
- mcgram-0.2.0/LICENSE +21 -0
- mcgram-0.2.0/PKG-INFO +311 -0
- mcgram-0.2.0/README.md +271 -0
- mcgram-0.2.0/config.example.yaml +34 -0
- mcgram-0.2.0/docs/architecture.md +121 -0
- mcgram-0.2.0/docs/images/flow.png +0 -0
- mcgram-0.2.0/docs/images/hero.png +0 -0
- mcgram-0.2.0/docs/images/logo.png +0 -0
- mcgram-0.2.0/docs/manual-smoke-test.md +78 -0
- mcgram-0.2.0/docs/security-threat-model.md +82 -0
- mcgram-0.2.0/plans/20260521-brainstorm-mcgram-telegram-mcp/brainstorm-report.md +240 -0
- mcgram-0.2.0/plans/20260521-implement-mcgram-telegram-mcp/phase-01-scaffolding-config-audit.md +144 -0
- mcgram-0.2.0/plans/20260521-implement-mcgram-telegram-mcp/phase-02-telegram-client-send-tools.md +154 -0
- mcgram-0.2.0/plans/20260521-implement-mcgram-telegram-mcp/phase-03-ask-flow-reminders.md +204 -0
- mcgram-0.2.0/plans/20260521-implement-mcgram-telegram-mcp/phase-04-cli-companion-skill.md +194 -0
- mcgram-0.2.0/plans/20260521-implement-mcgram-telegram-mcp/phase-05-tests-docs-ci.md +177 -0
- mcgram-0.2.0/plans/20260521-implement-mcgram-telegram-mcp/plan.md +69 -0
- mcgram-0.2.0/plans/260522-1355-ntfy-transport-integration/phase-01-ntfy-client.md +95 -0
- mcgram-0.2.0/plans/260522-1355-ntfy-transport-integration/phase-02-config-schema.md +150 -0
- mcgram-0.2.0/plans/260522-1355-ntfy-transport-integration/phase-03-tools-branch.md +79 -0
- mcgram-0.2.0/plans/260522-1355-ntfy-transport-integration/phase-04-ask-and-reminders.md +74 -0
- mcgram-0.2.0/plans/260522-1355-ntfy-transport-integration/phase-05-cli.md +176 -0
- mcgram-0.2.0/plans/260522-1355-ntfy-transport-integration/phase-06-docs-skill.md +79 -0
- mcgram-0.2.0/plans/260522-1355-ntfy-transport-integration/plan.md +138 -0
- mcgram-0.2.0/pyproject.toml +110 -0
- mcgram-0.2.0/scripts/live-smoke.py +101 -0
- mcgram-0.2.0/src/mcgram/__init__.py +3 -0
- mcgram-0.2.0/src/mcgram/ask_registry.py +140 -0
- mcgram-0.2.0/src/mcgram/audit.py +144 -0
- mcgram-0.2.0/src/mcgram/cli.py +72 -0
- mcgram-0.2.0/src/mcgram/cli_audit.py +191 -0
- mcgram-0.2.0/src/mcgram/cli_channel.py +176 -0
- mcgram-0.2.0/src/mcgram/cli_doctor.py +180 -0
- mcgram-0.2.0/src/mcgram/cli_init.py +216 -0
- mcgram-0.2.0/src/mcgram/config.py +275 -0
- mcgram-0.2.0/src/mcgram/data/__init__.py +1 -0
- mcgram-0.2.0/src/mcgram/data/config.example.yaml +59 -0
- mcgram-0.2.0/src/mcgram/data/skill/SKILL.md +131 -0
- mcgram-0.2.0/src/mcgram/dispatch.py +101 -0
- mcgram-0.2.0/src/mcgram/errors.py +58 -0
- mcgram-0.2.0/src/mcgram/lock.py +116 -0
- mcgram-0.2.0/src/mcgram/ntfy_client.py +173 -0
- mcgram-0.2.0/src/mcgram/polling.py +82 -0
- mcgram-0.2.0/src/mcgram/rate_limiter.py +45 -0
- mcgram-0.2.0/src/mcgram/reminders.py +136 -0
- mcgram-0.2.0/src/mcgram/runtime.py +37 -0
- mcgram-0.2.0/src/mcgram/server.py +249 -0
- mcgram-0.2.0/src/mcgram/skill_installer.py +37 -0
- mcgram-0.2.0/src/mcgram/tg_client.py +166 -0
- mcgram-0.2.0/src/mcgram/tools/__init__.py +1 -0
- mcgram-0.2.0/src/mcgram/tools/ask.py +127 -0
- mcgram-0.2.0/src/mcgram/tools/cancel_reminder.py +33 -0
- mcgram-0.2.0/src/mcgram/tools/list_reminders.py +24 -0
- mcgram-0.2.0/src/mcgram/tools/send_file.py +123 -0
- mcgram-0.2.0/src/mcgram/tools/send_message.py +94 -0
- mcgram-0.2.0/src/mcgram/tools/send_video.py +115 -0
- mcgram-0.2.0/src/mcgram/tools/set_reminder.py +57 -0
- mcgram-0.2.0/src/mcgram/update_dispatcher.py +45 -0
- mcgram-0.2.0/tests/__init__.py +0 -0
- mcgram-0.2.0/tests/conftest.py +57 -0
- mcgram-0.2.0/tests/test_ask_registry.py +194 -0
- mcgram-0.2.0/tests/test_audit.py +115 -0
- mcgram-0.2.0/tests/test_channels.py +63 -0
- mcgram-0.2.0/tests/test_cli.py +66 -0
- mcgram-0.2.0/tests/test_cli_audit.py +97 -0
- mcgram-0.2.0/tests/test_cli_channel.py +111 -0
- mcgram-0.2.0/tests/test_cli_channel_ntfy.py +139 -0
- mcgram-0.2.0/tests/test_cli_doctor.py +107 -0
- mcgram-0.2.0/tests/test_cli_doctor_ntfy.py +118 -0
- mcgram-0.2.0/tests/test_cli_init.py +95 -0
- mcgram-0.2.0/tests/test_cli_init_ntfy.py +184 -0
- mcgram-0.2.0/tests/test_config.py +126 -0
- mcgram-0.2.0/tests/test_config_ntfy.py +265 -0
- mcgram-0.2.0/tests/test_lock.py +57 -0
- mcgram-0.2.0/tests/test_ntfy_client.py +185 -0
- mcgram-0.2.0/tests/test_polling.py +125 -0
- mcgram-0.2.0/tests/test_polling_conflict.py +88 -0
- mcgram-0.2.0/tests/test_rate_limiter.py +44 -0
- mcgram-0.2.0/tests/test_reminders.py +137 -0
- mcgram-0.2.0/tests/test_server_main.py +105 -0
- mcgram-0.2.0/tests/test_server_smoke.py +86 -0
- mcgram-0.2.0/tests/test_skill_installer.py +52 -0
- mcgram-0.2.0/tests/test_tg_client.py +160 -0
- mcgram-0.2.0/tests/test_tools_ask.py +90 -0
- mcgram-0.2.0/tests/test_tools_ntfy.py +238 -0
- mcgram-0.2.0/tests/test_tools_reminder.py +49 -0
- mcgram-0.2.0/tests/test_tools_send_file.py +121 -0
- mcgram-0.2.0/tests/test_tools_send_message.py +89 -0
- mcgram-0.2.0/tests/test_tools_send_video.py +66 -0
- mcgram-0.2.0/tests/test_update_dispatcher.py +54 -0
- mcgram-0.2.0/uv.lock +1254 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
test:
|
|
15
|
+
name: py${{ matrix.python }} / ${{ matrix.os }}
|
|
16
|
+
runs-on: ${{ matrix.os }}
|
|
17
|
+
strategy:
|
|
18
|
+
fail-fast: false
|
|
19
|
+
matrix:
|
|
20
|
+
python: ["3.11", "3.12"]
|
|
21
|
+
os: [ubuntu-latest, windows-latest]
|
|
22
|
+
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
|
|
26
|
+
- name: Install uv
|
|
27
|
+
uses: astral-sh/setup-uv@v3
|
|
28
|
+
|
|
29
|
+
- name: Install Python ${{ matrix.python }}
|
|
30
|
+
run: uv python install ${{ matrix.python }}
|
|
31
|
+
|
|
32
|
+
- name: Sync dependencies
|
|
33
|
+
run: uv sync --extra dev
|
|
34
|
+
|
|
35
|
+
- name: Lint (ruff)
|
|
36
|
+
run: uv run ruff check src/ tests/
|
|
37
|
+
|
|
38
|
+
- name: Type check (mypy)
|
|
39
|
+
run: uv run mypy src/mcgram
|
|
40
|
+
continue-on-error: true # strict mode is opt-in; loosen later
|
|
41
|
+
|
|
42
|
+
- name: Run tests
|
|
43
|
+
run: uv run pytest --cov=mcgram --cov-report=xml --timeout=30
|
|
44
|
+
|
|
45
|
+
- name: Upload coverage to Codecov
|
|
46
|
+
if: matrix.python == '3.12' && matrix.os == 'ubuntu-latest'
|
|
47
|
+
uses: codecov/codecov-action@v4
|
|
48
|
+
with:
|
|
49
|
+
file: ./coverage.xml
|
|
50
|
+
fail_ci_if_error: false
|
mcgram-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.venv/
|
|
6
|
+
venv/
|
|
7
|
+
.pytest_cache/
|
|
8
|
+
.mypy_cache/
|
|
9
|
+
.ruff_cache/
|
|
10
|
+
.coverage
|
|
11
|
+
.coverage.*
|
|
12
|
+
htmlcov/
|
|
13
|
+
coverage.xml
|
|
14
|
+
|
|
15
|
+
# Build
|
|
16
|
+
dist/
|
|
17
|
+
build/
|
|
18
|
+
*.whl
|
|
19
|
+
|
|
20
|
+
# Local config / runtime
|
|
21
|
+
.env
|
|
22
|
+
*.local.yaml
|
|
23
|
+
config.yaml
|
|
24
|
+
audit.jsonl
|
|
25
|
+
audit.jsonl.*
|
|
26
|
+
.lock
|
|
27
|
+
|
|
28
|
+
# IDE
|
|
29
|
+
.vscode/
|
|
30
|
+
.idea/
|
|
31
|
+
|
|
32
|
+
# OS
|
|
33
|
+
.DS_Store
|
|
34
|
+
Thumbs.db
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to mcgram will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
|
|
6
|
+
this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
|
+
|
|
8
|
+
## [0.2.0] — 2026-05-22
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **ntfy.sh transport** — one-way push notifications via the [ntfy.sh](https://ntfy.sh) HTTP API.
|
|
12
|
+
No bot, no token: subscribe to a topic in the ntfy mobile app and receive pings.
|
|
13
|
+
Useful for machines where `api.telegram.org` is blocked.
|
|
14
|
+
- `NtfyClient` async HTTP wrapper (`src/mcgram/ntfy_client.py`)
|
|
15
|
+
- `NtfyError` exception type
|
|
16
|
+
- `dispatch.py` — transport-aware send helpers (`send_text` / `send_document` / `send_video_file`)
|
|
17
|
+
- `Settings.ntfy: NtfyConfig | None` — optional `ntfy` config section with
|
|
18
|
+
`server`, `default_topic`, `access_token_env`
|
|
19
|
+
- `ChannelConfig.transport: telegram | ntfy` — every named channel declares its transport
|
|
20
|
+
- `Settings.resolve_destination(name)` — returns a `Destination` dataclass
|
|
21
|
+
carrying chat_id (telegram) or topic+server (ntfy). `resolve_channel()`
|
|
22
|
+
preserved as legacy alias for telegram-only callers.
|
|
23
|
+
- `BotConfig.disable_polling: bool` — keep `bot:` section but skip Telegram
|
|
24
|
+
long-poll on a specific machine (e.g. when api.telegram.org is blocked).
|
|
25
|
+
Eliminates poll-loop log spam in send-only deployments.
|
|
26
|
+
- `LimitsConfig.ntfy_file_max_bytes: int = 15728640` — effective `send_file` /
|
|
27
|
+
`send_video` cap for ntfy channels (15 MB matches public free tier).
|
|
28
|
+
Telegram channels keep using `file_max_bytes` (50 MB).
|
|
29
|
+
- CLI:
|
|
30
|
+
- `mcgram init` now generates a random ntfy topic (`mcgram-<16-hex>`) and writes
|
|
31
|
+
it to `ntfy.default_topic` in the scaffolded config
|
|
32
|
+
- `mcgram channel add-ntfy NAME [--topic T] [-d DESC]` — declare a ntfy channel
|
|
33
|
+
- `mcgram channel list` shows a transport column
|
|
34
|
+
- `mcgram doctor` runs per-transport checks (telegram + ntfy independent),
|
|
35
|
+
catches `httpx.HTTPError` (SSL/network) and reports as FAIL instead of crashing
|
|
36
|
+
- `mcgram clear-lock` — remove `~/.mcgram/.lock` after a crashed instance.
|
|
37
|
+
Use when MCP fails to start with error -32000 due to a stale PID-recycling
|
|
38
|
+
collision (see Known issues).
|
|
39
|
+
- Companion Claude Code skill (`SKILL.md`) rewritten to be transport-aware:
|
|
40
|
+
- Per-tool transport matrix (✅ / ❌ on telegram vs ntfy)
|
|
41
|
+
- Explicit guidance: `ask` is Telegram-only; on ntfy channels, return
|
|
42
|
+
`transport_unsupported` without retry — fall back to `send_message`
|
|
43
|
+
- PII/secrets warning for public ntfy topics
|
|
44
|
+
- Vietnamese trigger phrases added: "gửi qua ntfy", "push to my phone"
|
|
45
|
+
- Tests: +57 unit + integration tests (NtfyClient, ntfy config resolution,
|
|
46
|
+
transport branching in tools, ntfy CLI subcommands, doctor per-transport,
|
|
47
|
+
per-transport file size cap). Suite now at 223 tests.
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
- `Settings.bot` is now optional. Config must declare AT LEAST ONE of
|
|
51
|
+
`bot` or `ntfy`; both is valid. Telegram-only configs (pre-0.2 format)
|
|
52
|
+
continue to work unchanged.
|
|
53
|
+
- Tool responses now include a `transport` field (`"telegram"` | `"ntfy"`)
|
|
54
|
+
so callers know which backend handled the request.
|
|
55
|
+
- Audit log entries gain a `transport` field for the same reason.
|
|
56
|
+
- README quickstart now leads with ntfy (faster setup, no token);
|
|
57
|
+
Telegram retained as Option B for `ask` 2-way input.
|
|
58
|
+
- `_NEXT_STEPS` printed by `mcgram init` uses ASCII arrows (`->`) instead of
|
|
59
|
+
Unicode `→` for Windows console compatibility on legacy code pages.
|
|
60
|
+
- `mcgram init` idempotent path now reads `ntfy.default_topic` from the
|
|
61
|
+
EXISTING config when skipping, so the printed topic always matches what's
|
|
62
|
+
actually on disk.
|
|
63
|
+
|
|
64
|
+
### Fixed
|
|
65
|
+
- `mcgram doctor` previously crashed with an uncaught `httpx.ConnectError`
|
|
66
|
+
when `api.telegram.org` was unreachable. Now reports `[FAIL] telegram ...
|
|
67
|
+
network/SSL error` and continues to check ntfy.
|
|
68
|
+
|
|
69
|
+
### Security
|
|
70
|
+
- ntfy public topics are URL-discoverable. Generated topics use 64-bit
|
|
71
|
+
entropy (16 hex chars). Documentation in README + SKILL.md warns against
|
|
72
|
+
sending PII/secrets unless self-hosting ntfy with auth.
|
|
73
|
+
- `ntfy.access_token_env` supports Bearer token auth for paid/self-hosted
|
|
74
|
+
ntfy servers (unit tests cover the path; integration test against a real
|
|
75
|
+
authed server tracked in `TODO(ntfy-auth-e2e)` in `ntfy_client.py`).
|
|
76
|
+
|
|
77
|
+
### Known issues
|
|
78
|
+
- **PID-recycling collision on Windows** — the single-instance lock at
|
|
79
|
+
`~/.mcgram/.lock` stores only the owner PID. If mcgram is killed ungracefully
|
|
80
|
+
(e.g. host reboot) and Windows later assigns the same PID to a different
|
|
81
|
+
long-running process (Python, IDE, etc.), the next `mcgram` invocation will
|
|
82
|
+
see the lock as "held" and refuse to start with error -32000 from Claude
|
|
83
|
+
Code's MCP layer. Workaround: run `mcgram clear-lock`. Proper fix (storing
|
|
84
|
+
process creation time alongside the PID) deferred.
|
|
85
|
+
|
|
86
|
+
### Migration notes
|
|
87
|
+
- 0.1.x configs (`bot:` only) load unchanged on 0.2.0.
|
|
88
|
+
- To add ntfy on top of a working 0.1.x setup: either run `mcgram init --force`
|
|
89
|
+
(regenerates config, **erases your operator_chat_id**), or manually append an
|
|
90
|
+
`ntfy:` section to `~/.mcgram/config.yaml` (preserves bot config).
|
|
91
|
+
- On machines that block Telegram, set `bot.disable_polling: true` to stop the
|
|
92
|
+
poll loop without removing the bot section.
|
|
93
|
+
|
|
94
|
+
[0.2.0]: https://github.com/tvtdev94/mcgram/releases/tag/v0.2.0
|
|
95
|
+
|
|
96
|
+
## [0.1.0] — 2026-05-22
|
|
97
|
+
|
|
98
|
+
### Added
|
|
99
|
+
- MCP stdio server (`mcgram`) exposing 6 tools:
|
|
100
|
+
- `send_message(text, silent?, parse_mode?)` — post text to operator chat
|
|
101
|
+
- `send_file(path, caption?, silent?)` — upload file (≤50 MB, CWD-guarded)
|
|
102
|
+
- `ask(question, options?, timeout_s?)` — block until reply (button/freetext/timeout)
|
|
103
|
+
- `set_reminder(text, delay_s)` — in-process scheduler (≤10 pending, ≤24h)
|
|
104
|
+
- `cancel_reminder(reminder_id)`
|
|
105
|
+
- `list_reminders()`
|
|
106
|
+
- Async Telegram Bot API client (`httpx` wrapper, long-poll loop with backoff)
|
|
107
|
+
- Operator allowlist enforced at dispatcher entry (rejects non-`operator_chat_id` updates)
|
|
108
|
+
- Per-tool token-bucket rate limiter (default 20/min)
|
|
109
|
+
- JSONL audit log with `fsync`, 3-backup rotation, optional `redact_text`,
|
|
110
|
+
optional `retention_days` pruning
|
|
111
|
+
- Single-instance PID-file lock (cross-platform: Windows + POSIX)
|
|
112
|
+
- CLI: `mcgram init`, `mcgram doctor`, `mcgram audit`, `mcgram install-skill`
|
|
113
|
+
- Bundled companion Claude Code skill (`SKILL.md`) — installed by `mcgram init`,
|
|
114
|
+
recognizes both English and Vietnamese trigger phrases
|
|
115
|
+
- Docs: `README.md`, `docs/architecture.md`, `docs/security-threat-model.md`,
|
|
116
|
+
`docs/manual-smoke-test.md`
|
|
117
|
+
- GitHub Actions CI matrix: Python 3.11 + 3.12 × Ubuntu + Windows
|
|
118
|
+
- ≥80% test coverage with `pytest` + `pytest-httpx`
|
|
119
|
+
|
|
120
|
+
### Security
|
|
121
|
+
- Bot token loaded from env var, never written to logs / audit / stdout
|
|
122
|
+
- `mcgram doctor` masks token to `***<last-4>`
|
|
123
|
+
- `send_file` rejects paths outside CWD by default; opt-in `allow_outside_cwd`
|
|
124
|
+
- Hard caps on `ask` timeout, reminder count + delay + text length, file size
|
|
125
|
+
- Full STRIDE analysis published before v0.1 release
|
|
126
|
+
|
|
127
|
+
[0.1.0]: https://github.com/tvtdev94/mcgram/releases/tag/v0.1.0
|
mcgram-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 tvtdev94
|
|
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.
|
mcgram-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mcgram
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Notification bridge for Claude Code (Telegram + ntfy.sh) — send notifications, ask short questions, set reminders via Telegram bot or ntfy.sh topic.
|
|
5
|
+
Project-URL: Homepage, https://github.com/tvtdev94/mcgram
|
|
6
|
+
Project-URL: Repository, https://github.com/tvtdev94/mcgram
|
|
7
|
+
Project-URL: Issues, https://github.com/tvtdev94/mcgram/issues
|
|
8
|
+
Project-URL: Documentation, https://github.com/tvtdev94/mcgram#readme
|
|
9
|
+
Author: tvtdev94
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: ai,bot,claude,claude-code,mcp,model-context-protocol,notifications,ntfy,push-notifications,reminders,telegram
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Communications :: Chat
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Requires-Dist: httpx>=0.27
|
|
25
|
+
Requires-Dist: mcp<2.0,>=1.0
|
|
26
|
+
Requires-Dist: pydantic-settings>=2.0
|
|
27
|
+
Requires-Dist: pydantic>=2.5
|
|
28
|
+
Requires-Dist: python-dotenv>=1.0
|
|
29
|
+
Requires-Dist: pyyaml>=6.0
|
|
30
|
+
Requires-Dist: tzdata>=2024.1; sys_platform == 'win32'
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest-timeout>=2.3; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
38
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
<div align="center">
|
|
42
|
+
|
|
43
|
+
<img src="docs/images/logo.png" alt="mcgram logo" width="120" />
|
|
44
|
+
|
|
45
|
+
# mcgram
|
|
46
|
+
|
|
47
|
+
**Notification bridge for Claude Code** — Telegram **and** ntfy.sh. Claude can ping you, ask you, and remind you on your phone.
|
|
48
|
+
|
|
49
|
+
[](https://github.com/tvtdev94/mcgram/actions/workflows/ci.yml)
|
|
50
|
+
[](https://pypi.org/project/mcgram/)
|
|
51
|
+
[](https://www.python.org/downloads/)
|
|
52
|
+
[](LICENSE)
|
|
53
|
+
[](#)
|
|
54
|
+
[](#)
|
|
55
|
+
|
|
56
|
+
<img src="docs/images/hero.png" alt="mcgram hero" width="720" />
|
|
57
|
+
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## What it does
|
|
63
|
+
|
|
64
|
+
Walk away from a long-running task. mcgram lets Claude **tap you on the shoulder** through Telegram or ntfy.sh — and lets you tap back (Telegram only).
|
|
65
|
+
|
|
66
|
+
| | |
|
|
67
|
+
|---|---|
|
|
68
|
+
| 📣 **Notify** | "Build passed in 3m12s ✅" / send the failing log as attachment |
|
|
69
|
+
| ❓ **Ask & wait** | Inline buttons (Approve / Cancel) with a timeout — Telegram only |
|
|
70
|
+
| ⏰ **Remind** | "nhắc tôi sau 30 phút check container logs" |
|
|
71
|
+
| 🎥 **Send video** | Demo recordings play right in-chat |
|
|
72
|
+
| 🛰 **Multi-channel** | Route messages to named destinations across both transports |
|
|
73
|
+
| 🌐 **No-token mode** | Use ntfy.sh on machines where `api.telegram.org` is blocked |
|
|
74
|
+
|
|
75
|
+
Single process, no daemon, no VPS, no webhook setup. Lives inside the MCP server while Claude Code is open.
|
|
76
|
+
|
|
77
|
+
## How it fits together
|
|
78
|
+
|
|
79
|
+
<div align="center">
|
|
80
|
+
<img src="docs/images/flow.png" alt="Architecture flow" width="720" />
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
Claude Code spawns `mcgram` over stdio. mcgram opens a long-poll to the Telegram Bot API. When you tap a button or type a reply, the update lands on your phone-out / mcgram-in side, the operator allowlist filters non-you traffic, and the answer flows back to Claude as the tool result.
|
|
84
|
+
|
|
85
|
+
## Quickstart
|
|
86
|
+
|
|
87
|
+
> **Prereqs:** Python 3.11+, [`uv`](https://docs.astral.sh/uv/) or `pipx`. Pick ONE transport below (or set up both).
|
|
88
|
+
|
|
89
|
+
### Option A — ntfy.sh (fastest, no token, ~30 seconds)
|
|
90
|
+
|
|
91
|
+
Best for: machines where `api.telegram.org` is blocked, or you just want 1-way pings to your phone.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# 1) Install + scaffold (generates a random ntfy topic for you)
|
|
95
|
+
uv tool install git+https://github.com/tvtdev94/mcgram # or: pipx install ...
|
|
96
|
+
mcgram init # writes ~/.mcgram/config.yaml with a unique topic
|
|
97
|
+
|
|
98
|
+
# 2) Install the ntfy mobile app: https://ntfy.sh/ (iOS / Android / Web)
|
|
99
|
+
# In the app, subscribe to the topic printed by `mcgram init` (e.g. mcgram-a1b2c3d4...)
|
|
100
|
+
|
|
101
|
+
# 3) Verify
|
|
102
|
+
mcgram doctor # ✅ ntfy health, ✅ test message delivered
|
|
103
|
+
|
|
104
|
+
# 4) Restart Claude Code → /mcp → mcgram appears → done
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
That's it. **No bot, no token, no chat ID.** Now in Claude Code: *"báo cho tôi qua ntfy khi xong"* or *"push log to my phone"* — Claude knows what to do.
|
|
108
|
+
|
|
109
|
+
> ⚠️ ntfy.sh topics are URL-addressable. Anyone who knows the topic name can subscribe and read your messages. Topics generated by `mcgram init` use 64-bit entropy (16 hex chars), which is fine for casual notifications but **don't send secrets / PII** unless you self-host ntfy.
|
|
110
|
+
|
|
111
|
+
### Option B — Telegram (required for 2-way `ask`)
|
|
112
|
+
|
|
113
|
+
Best for: you want Claude to wait for your Approve/Cancel button before deploying.
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# 1) Install + scaffold
|
|
117
|
+
uv tool install git+https://github.com/tvtdev94/mcgram
|
|
118
|
+
mcgram init
|
|
119
|
+
|
|
120
|
+
# 2) Create a bot with @BotFather → /newbot → copy token
|
|
121
|
+
# Find your chat ID: /start the bot, then visit
|
|
122
|
+
# https://api.telegram.org/bot<TOKEN>/getUpdates → copy chat.id
|
|
123
|
+
|
|
124
|
+
# 3) Paste credentials and uncomment the bot section
|
|
125
|
+
# Edit ~/.mcgram/.env → MCGRAM_BOT_TOKEN=...
|
|
126
|
+
# Edit ~/.mcgram/config.yaml → uncomment `bot:` block, set operator_chat_id
|
|
127
|
+
|
|
128
|
+
# 4) Verify
|
|
129
|
+
mcgram doctor # ✅ get_me OK, ✅ test message delivered
|
|
130
|
+
|
|
131
|
+
# 5) Restart Claude Code → /mcp → mcgram appears → done
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Both transports
|
|
135
|
+
|
|
136
|
+
Declare both `ntfy:` and `bot:` in config — each named channel picks its own transport.
|
|
137
|
+
|
|
138
|
+
## The 7 tools
|
|
139
|
+
|
|
140
|
+
| Tool | Telegram | ntfy.sh | When to use |
|
|
141
|
+
|---|:---:|:---:|---|
|
|
142
|
+
| **`send_message`** | ✅ | ✅ | Notify on completion / send a status update |
|
|
143
|
+
| **`send_file`** | ✅ | ✅ | Send logs, screenshots, generated artifacts |
|
|
144
|
+
| **`send_video`** | ✅ | ✅ | Send a video that **plays in-chat** (mp4/mov/mkv/webm/m4v) |
|
|
145
|
+
| **`ask`** | ✅ | ❌ | Question with inline buttons OR freetext, blocks until reply. Telegram-only |
|
|
146
|
+
| **`set_reminder`** | ✅ | ✅ | Schedule an in-process reminder (lost on restart) |
|
|
147
|
+
| **`cancel_reminder`** | — | — | Cancel a pending reminder |
|
|
148
|
+
| **`list_reminders`** | — | — | List currently pending reminders |
|
|
149
|
+
|
|
150
|
+
ntfy.sh has no 2-way input, so `ask` returns `transport_unsupported` on ntfy channels. The companion [Claude Code skill](src/mcgram/data/skill/SKILL.md) (installed by `mcgram init`) teaches Claude when to call which tool — both **English** and **Vietnamese** trigger phrases are recognized.
|
|
151
|
+
|
|
152
|
+
## Channels
|
|
153
|
+
|
|
154
|
+
Route messages to different destinations across both transports:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
mcgram channel add oncall -1001234567890 -d "Pager rotation" # Telegram
|
|
158
|
+
mcgram channel add-ntfy alerts -d "Build alerts" # ntfy.sh — auto-generates topic
|
|
159
|
+
mcgram channel add-ntfy alerts --topic mcgram-myalerts -d "..." # explicit topic
|
|
160
|
+
mcgram channel list
|
|
161
|
+
# alerts ntfy topic=mcgram-a1b2c3d4e5f6g7h8 Build alerts
|
|
162
|
+
# default ntfy topic=mcgram-xxxxxxxxxxxxxxxx Auto-created from ntfy.default_topic
|
|
163
|
+
# oncall telegram chat_id=-1001234567890 Pager rotation
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Then in Claude Code: *"send the failing log to the alerts channel"* → `send_file(path="…", channel="alerts")`.
|
|
167
|
+
|
|
168
|
+
## CLI
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
mcgram # MCP stdio server (Claude Code calls this)
|
|
172
|
+
mcgram init [--force] # scaffold ~/.mcgram/ + skill + auto-register MCP (generates ntfy topic)
|
|
173
|
+
mcgram doctor # config + connectivity check (per-transport)
|
|
174
|
+
mcgram audit [opts] # analyze audit.jsonl: --since 1h, --tool ask, --rejected, --tail
|
|
175
|
+
mcgram channel <action> # list | add NAME CHAT_ID | add-ntfy NAME [--topic T] | remove NAME
|
|
176
|
+
mcgram install-skill [--force] # reinstall ~/.claude/skills/mcgram/SKILL.md
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Config example
|
|
180
|
+
|
|
181
|
+
`~/.mcgram/config.yaml` — declare at least one of `ntfy` / `bot`:
|
|
182
|
+
|
|
183
|
+
```yaml
|
|
184
|
+
ntfy: # Transport A — fast, no token
|
|
185
|
+
server: https://ntfy.sh
|
|
186
|
+
default_topic: mcgram-a1b2c3d4e5f6g7h8 # generated by `mcgram init`
|
|
187
|
+
# access_token_env: NTFY_TOKEN # optional, for paid/self-hosted
|
|
188
|
+
|
|
189
|
+
bot: # Transport B — required for `ask`
|
|
190
|
+
token_env: MCGRAM_BOT_TOKEN # token loaded from ~/.mcgram/.env
|
|
191
|
+
operator_chat_id: 123456789 # your personal chat ID
|
|
192
|
+
|
|
193
|
+
defaults:
|
|
194
|
+
parse_mode: plain # plain | markdown_v2
|
|
195
|
+
ask_timeout_s: 120
|
|
196
|
+
rate_limit_per_min: 20
|
|
197
|
+
|
|
198
|
+
limits:
|
|
199
|
+
ask_timeout_max_s: 600
|
|
200
|
+
reminder_max_delay_s: 86400 # 24h
|
|
201
|
+
reminder_max_pending: 10
|
|
202
|
+
file_max_bytes: 52428800 # 50 MB
|
|
203
|
+
ask_options_max: 6
|
|
204
|
+
|
|
205
|
+
channels: # optional named destinations
|
|
206
|
+
oncall: # Telegram channel
|
|
207
|
+
transport: telegram
|
|
208
|
+
chat_id: -1001234567890
|
|
209
|
+
description: Pager rotation
|
|
210
|
+
alerts: # ntfy.sh channel
|
|
211
|
+
transport: ntfy
|
|
212
|
+
ntfy_topic: mcgram-alerts-x9k2
|
|
213
|
+
description: Build alerts
|
|
214
|
+
|
|
215
|
+
audit:
|
|
216
|
+
path: ~/.mcgram/audit.jsonl
|
|
217
|
+
rotate_mb: 25
|
|
218
|
+
redact_text: false # true → outbound `text` masked in audit
|
|
219
|
+
retention_days: null # e.g. 14
|
|
220
|
+
timezone: UTC
|
|
221
|
+
|
|
222
|
+
allow_outside_cwd: false # send_file restricted to CWD by default
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Security model
|
|
226
|
+
|
|
227
|
+
| Layer | Mitigation |
|
|
228
|
+
|---|---|
|
|
229
|
+
| 🔐 **Token** | Loaded from env var; never logged; masked in `mcgram doctor` |
|
|
230
|
+
| 🚪 **Operator filter** | Non-`operator_chat_id` updates rejected at dispatcher entry — never reach tool handlers |
|
|
231
|
+
| 🛡 **`send_file` traversal** | Path resolved + checked against CWD; size capped at `file_max_bytes` |
|
|
232
|
+
| ⚡ **Rate limit** | Per-tool token bucket (default 20/min) |
|
|
233
|
+
| 🔒 **Single instance** | PID-file lock at `~/.mcgram/.lock`; second `mcgram` exits with `LockHeldError` |
|
|
234
|
+
| 📜 **Audit trail** | Every call logged JSONL with `fsync`; survives `kill -9` |
|
|
235
|
+
| 🛌 **Reminder spam** | Max 10 pending, 24h delay cap, 1000-char text cap |
|
|
236
|
+
| ⏱ **`ask` DoS** | Hard timeout cap (600s) so a forgetful user can't freeze Claude forever |
|
|
237
|
+
| 🔁 **Polling conflict** | Telegram 409 caught with clean error — no crash loop |
|
|
238
|
+
|
|
239
|
+
Full STRIDE analysis: [docs/security-threat-model.md](docs/security-threat-model.md).
|
|
240
|
+
|
|
241
|
+
## Audit
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
mcgram audit # summary: counts by status + by tool
|
|
245
|
+
mcgram audit --since 1h # last hour only
|
|
246
|
+
mcgram audit --tool send_file # filter by tool
|
|
247
|
+
mcgram audit --rejected # group rejections by reason
|
|
248
|
+
mcgram audit --tail # follow new entries (Ctrl-C to stop)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Sample lines:
|
|
252
|
+
|
|
253
|
+
```jsonc
|
|
254
|
+
{"ts":"2026-05-21T10:00:00+00:00","tool":"send_message","status":"ok","chat_id":123,"channel":"default","text":"build passed","text_len":12,"ms":150}
|
|
255
|
+
{"ts":"2026-05-21T10:00:05+00:00","tool":"ask","status":"ok","channel":"oncall","question_id":"q_a1","source":"button","ms":3200}
|
|
256
|
+
{"ts":"2026-05-21T10:00:10+00:00","tool":"send_file","status":"rejected","reason":"file_too_large","bytes":62914560}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Known limitations
|
|
260
|
+
|
|
261
|
+
- **No persistent reminders** — schedules live in process memory. Restart = lost.
|
|
262
|
+
- **`ask` blocks the MCP call** — keep timeouts short or Claude waits idle.
|
|
263
|
+
- **`ask` is Telegram-only** — ntfy.sh has no 2-way input. On ntfy channels, `ask` returns `transport_unsupported` without contacting the network.
|
|
264
|
+
- **ntfy.sh public topics are URL-discoverable** — anyone with the topic name can subscribe. mcgram generates 64-bit-entropy topics, but **don't send secrets / PII** unless you self-host ntfy with auth.
|
|
265
|
+
- **ntfy.sh file size cap** — public free tier ~15 MB (vs Telegram's 50 MB). Self-hosted ntfy can raise the cap.
|
|
266
|
+
- **No remote control** — Claude can send, but the bot doesn't accept arbitrary commands FROM Telegram. (Future v0.2.)
|
|
267
|
+
- **No webhook / VPS support** — Telegram uses long-poll only.
|
|
268
|
+
- **Same Telegram token on 2 machines** → 409 Conflict (only one poller per bot). mcgram backs off cleanly; use different bots for different machines. (When `bot:` is omitted, Telegram polling is fully disabled — no 409, no log spam.)
|
|
269
|
+
|
|
270
|
+
## Architecture
|
|
271
|
+
|
|
272
|
+
Full module map, lifecycle diagram, and data flow: [docs/architecture.md](docs/architecture.md).
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
src/mcgram/
|
|
276
|
+
├── cli.py · cli_init · cli_doctor · cli_audit · cli_channel · skill_installer
|
|
277
|
+
├── config · errors · audit · lock · rate_limiter
|
|
278
|
+
├── tg_client · ntfy_client · dispatch · update_dispatcher · polling · server · runtime
|
|
279
|
+
├── ask_registry · reminders
|
|
280
|
+
└── tools/ send_message · send_file · send_video · ask · set_reminder · cancel_reminder · list_reminders
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
All modules <200 LOC, 153 tests, ruff clean, py3.11/3.12 × ubuntu/windows in CI.
|
|
284
|
+
|
|
285
|
+
## Update / uninstall
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
uv tool upgrade mcgram # or: pipx upgrade mcgram
|
|
289
|
+
mcgram install-skill --force # refresh bundled Claude skill if changed
|
|
290
|
+
claude mcp remove mcgram # unregister from Claude Code
|
|
291
|
+
uv tool uninstall mcgram # remove binary
|
|
292
|
+
rm -rf ~/.mcgram ~/.claude/skills/mcgram # remove config + skill
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Develop
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
git clone https://github.com/tvtdev94/mcgram && cd mcgram
|
|
299
|
+
uv sync --extra dev
|
|
300
|
+
uv run pytest -q
|
|
301
|
+
uv run ruff check src/ tests/
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Credits
|
|
305
|
+
|
|
306
|
+
- Patterns mirrored from sister project [dbread](https://github.com/tvtdev94/dbread) (read-only DB MCP).
|
|
307
|
+
- Built around the [Model Context Protocol](https://modelcontextprotocol.io/) and the Telegram Bot API.
|
|
308
|
+
|
|
309
|
+
## License
|
|
310
|
+
|
|
311
|
+
MIT — see [LICENSE](LICENSE).
|