Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 63 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,35 @@
> Community-maintained integration. Not an official Telegram product or
> Telegram LLC publication.

Telegram Plugin packages a working local stack for agent-safe Telegram access:
Telegram Plugin packages a working local stack for owner-local Telegram access:
a Telethon-backed MCP server, a Codex plugin bundle, and an optional
control-plane for local audits and repair planning.

Most Telegram automation tools expose too much too quickly. This project takes
the opposite position: agents should start with a narrow read/search/context
surface, clear source routing, drift checks, and explicit boundaries around
sessions, media, archives, and write actions.
Most Telegram automation tools hide their safety boundary. This project takes
the opposite position: the owner-local full MCP surface is explicit, the
restricted facade profile is still available for narrow compatibility, and
control-plane checks make drift visible.

## Why It Exists

Use this project when you want an AI coding agent to inspect Telegram context
without handing it the whole account by default.
Use this project when you want an AI coding agent to work with a local Telegram
account under an explicit owner-controlled MCP contract.

- **Local-first:** Telegram credentials and sessions stay on your machine.
- **Narrow by default:** Default Mode reads, searches, collects context, drafts,
previews, downloads scoped media, and transcribes voice locally.
- **Explicit writes:** sending, admin actions, profile changes, and broad export
workflows require separate Power Mode or operator wiring.
- **Owner-local full surface:** `plugin/.mcp.json` points at the owner-local
MCP daemons and intentionally does not use a legacy `allowedTools` allowlist.
- **Restricted facade available:** set `TELEGRAM_MCP_TOOL_PROFILE=facade` when
you need the narrow read/search/context/draft surface.
- **Explicit write discipline:** sending, admin actions, profile changes, and
broad export workflows are visible in the full surface and should be routed
through preview/confirmation or operator workflows when risk warrants it.
- **Auditable setup:** contract smoke checks, plugin drift checks, and
control-plane reports make the local state explainable.

## What Is Included

- `mcp/` - a Telethon-backed MCP server with high-level dialog facade tools.
- `plugin/` - a Codex plugin bundle that points at a local MCP daemon and
exposes a restricted default allowlist.
- `plugin/` - a Codex plugin bundle that points at local owner MCP daemons.
- `control-plane/` - optional local doctor/status/audit commands for plugin
drift, LaunchAgent inventory, sessions, source routing, and repair planning.
- `docs/` - safety model and routing notes for operating the stack.
Expand All @@ -53,32 +55,54 @@ without handing it the whole account by default.

| Mode | Use it for | Can change Telegram? | Enabled by default |
| --- | --- | --- | --- |
| Default Mode | read, search, context, drafts, previews, scoped media, voice transcription | No direct writes | Yes |
| Power Mode | send/reply, contacts, groups/channels, stories, profile, privacy state | Yes | No |
| Owner Local Full MCP | owner-controlled live work across the local full Telegram surface | Yes | Yes |
| Facade Profile | read, search, context, drafts, previews, scoped media inspection/download | No direct writes | No |
| Power Mode Example | wildcard client config for the same full local surface | Yes | No |
| Operator Workflows | mirror/archive, subscriber export, control-plane repair and audits | Can read in bulk or create sensitive artifacts | No |

Power Mode is not a hidden feature. It is the explicit mode for users who want
the broader Telegram MCP surface and accept that agents can perform externally
visible actions.
The full surface is not a hidden feature. It is the explicit owner-local mode
for users who want agents to work with the broader Telegram MCP surface and
accept that tools can perform externally visible actions.

For a short, private-data-free example, see
[Default Mode demo](docs/demo-default-mode.md).

## Surface Contract

Current healthy local mode is `owner_local_full_mcp`.

- `plugin/.mcp.json` points at owner-local MCP daemons and intentionally exposes
their full local surface without a legacy `allowedTools` allowlist.
- `TELEGRAM_MCP_TOOL_PROFILE=default` is not a restricted profile. In current
runtime behavior, unset/unknown/default profile names register the full
surface.
- The restricted facade profile is explicit: use
`TELEGRAM_MCP_TOOL_PROFILE=facade` (or `safe` / `restricted`) for narrow
read/search/context/draft workflows.
- `plugin/.mcp.full.example.json` is only a wildcard client example, not the
only way to reach full MCP.

## Release Gate

Before publishing or treating the local stack as healthy, run the release gates
that compare runtime tools, plugin metadata, docs, and control-plane policy.

## Safety Model

The runtime boundary is enforced in two layers:
The runtime boundary is enforced by explicit local operator choice and audit
checks:

- MCP runtime profile (`TELEGRAM_MCP_TOOL_PROFILE=default`) keeps write/admin
tools outside the default served surface.
- Plugin allowlist (`plugin/.mcp.json`) exposes only Default Mode facade tools
even when a broader local daemon exists.
- Owner-local full MCP is represented by
`control-plane/policy/surface-contract.json`.
- Restricted facade mode is represented by explicit MCP profile selection
(`TELEGRAM_MCP_TOOL_PROFILE=facade`, `safe`, or `restricted`).
- HTTP/SSE daemon transports require `TELEGRAM_MCP_AUTH_TOKEN`; stdio remains
local process-only.

```mermaid
flowchart LR
Agent["AI agent"] --> Plugin["Codex plugin<br/>restricted allowlist"]
Plugin --> MCP["Local Telegram MCP<br/>default profile"]
Agent["AI agent"] --> Plugin["Codex plugin<br/>owner-local config"]
Plugin --> MCP["Local Telegram MCP<br/>owner_local_full_mcp"]
MCP --> Telethon["Telethon session<br/>on this machine"]
Control["Control-plane audits"] -.-> Plugin
Control -.-> MCP
Expand Down Expand Up @@ -155,18 +179,18 @@ manual `.mcp.json` wiring only as a fallback:
- For HTTP daemon mode, set `TELEGRAM_MCP_AUTH_TOKEN` in client environment;
the plugin MCP config references it via `bearer_token_env_var`.

6. Unified workflow rule: use facade tools in Default Mode first. Switch to
Power Mode only for explicit write/admin operations. Direct Telethon calls are
an operator/debug path, not normal user onboarding.
6. Unified workflow rule: use task-shaped tools first, and treat direct
write/admin operations as explicit owner-local actions. Direct Telethon calls
are an operator/debug path, not normal user onboarding.

To inspect the full server surface locally, run the daemon with:
To inspect a restricted facade server surface locally, run the daemon with:

```bash
TELEGRAM_MCP_POWER_MODE=enabled TELEGRAM_MCP_TOOL_PROFILE=full .venv/bin/telegram-mcp
TELEGRAM_MCP_TOOL_PROFILE=facade .venv/bin/telegram-mcp
```

Use `plugin/.mcp.full.example.json` only when you intentionally want Power Mode
in a local agent client.
Use `plugin/.mcp.full.example.json` only when you intentionally want a wildcard
client config for the full local surface.

Dependency strategy for `mcp/`: commit and review `uv.lock` changes for
reproducible installs. Do not run broad upgrades as part of routine docs or
Expand Down Expand Up @@ -222,13 +246,14 @@ blocks those by default.

## Capability Boundaries

- Default Mode: live read/search/context/draft/preview, scoped local media
download, and selected voice/video transcription.
- Power Mode: broader Telegram API operations, including writes, contacts,
groups/channels, stories, profile, and privacy state. This is opt-in and not
enabled by the Default Mode allowlist.
- Owner Local Full MCP: broader Telegram API operations, including writes,
contacts, groups/channels, stories, profile, and privacy state. This is the
current local owner default.
- Facade Profile: live read/search/context/draft/preview, scoped local media
inspection/download, and selected voice/video transcription. This is opt-in
via `TELEGRAM_MCP_TOOL_PROFILE=facade`.
- Operator Workflows: mirror/archive/subscriber export/control-plane work. These
are not Default Mode functions and require their own setup and safety checks.
require their own setup and safety checks.

## Safety Defaults

Expand Down
1 change: 1 addition & 0 deletions control-plane/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
__pycache__/
*.pyc
.DS_Store
*.egg-info/
.cursor/
backups/
outputs/
Expand Down
4 changes: 3 additions & 1 deletion control-plane/tests/test_command_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ def bin_wrapper_names() -> set[str]:
return {
path.name
for path in BIN_DIR.iterdir()
if path.is_file() and path.name not in NON_COMMAND_BIN
if path.is_file()
and not path.name.startswith(".")
and path.name not in NON_COMMAND_BIN
}


Expand Down
14 changes: 13 additions & 1 deletion control-plane/tests/test_surface_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,24 @@

from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[2]


def test_readme_surface_contract_uses_owner_local_full_mcp_as_healthy_default() -> None:
readme = Path("README.md").read_text(encoding="utf-8")
readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8")
surface_start = readme.index("## Surface Contract")
release_start = readme.index("## Release Gate")
surface_section = readme[surface_start:release_start]

assert "owner_local_full_mcp" in surface_section
assert "TELEGRAM_MCP_TOOL_PROFILE=default" in surface_section
assert "is not a restricted profile" in surface_section
assert "must not expose raw send/reply" not in surface_section


def test_operator_workflows_do_not_claim_default_profile_is_restricted() -> None:
text = (REPO_ROOT / "docs/operator-workflows.md").read_text(encoding="utf-8")

assert "owner_local_full_mcp" in text
assert "TELEGRAM_MCP_TOOL_PROFILE=default" not in text
assert "TELEGRAM_MCP_TOOL_PROFILE=facade" in text
63 changes: 39 additions & 24 deletions docs/operator-workflows.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,47 @@
# Power Mode And Operator Workflows

Default Mode is intentionally safe for first install. The repository also
contains a full Telegram MCP surface and operator workflows. Those are not
second-class features; they are explicit modes because they can change Telegram
state, read in bulk, or create sensitive artifacts.
The current local owner setup uses `owner_local_full_mcp`. The repository also
keeps a restricted facade profile and broader operator workflows. These modes
must stay explicit because they differ in how much Telegram state they can read
or change.

Unified workflow for users:
- Default path: Telegram facade in Default Mode.
- Escalation path: Power Mode for explicit write/admin requests.
- Default owner path: local full MCP on explicit owner accounts.
- Restricted path: Telegram facade profile for read/search/context/draft work.
- Non-default path: direct Telethon is operator/debug only.

## Default Mode
## Owner Local Full MCP

Default Mode is the normal plugin path. It supports live read/search/context,
non-sending drafts and previews, scoped local media download, and selected
voice/video transcription.
Owner local full MCP is the normal local plugin path in this repository. It
exposes the full Telegram MCP surface on explicit owner accounts and relies on
local operator discipline, bearer auth, and control-plane checks.

Default Mode should be safe to try without the agent sending messages, changing
chats, changing profile state, or launching background mirror/archive jobs.
This boundary is runtime-enforced when these controls are kept intact:

This boundary is runtime-enforced when both controls are kept intact:

- MCP daemon runs with `TELEGRAM_MCP_TOOL_PROFILE=default`.
- MCP daemon runs with unset/default/full profile for the owner local full
surface, or with an explicit account-specific launchd config.
- HTTP/SSE daemon transport has `TELEGRAM_MCP_AUTH_TOKEN` configured in both
server and client.
- Plugin MCP config stays on `plugin/.mcp.json` allowlist and is not replaced by
`plugin/.mcp.full.example.json`.
- Plugin MCP config stays on `plugin/.mcp.json` without legacy `allowedTools`
allowlists.
- Control-plane policy stays on `owner_local_full_mcp`.

## Facade Profile

Facade profile is the restricted compatibility path. It supports live
read/search/context, non-sending drafts and previews, scoped local media
download, and selected voice/video transcription.

Facade profile should be safe to try without the agent sending messages,
changing chats, changing profile state, or launching background mirror/archive
jobs.

Enable it intentionally:

```bash
cd mcp
TELEGRAM_MCP_TOOL_PROFILE=facade .venv/bin/telegram-mcp
```

## Power Mode

Expand All @@ -41,14 +57,13 @@ TELEGRAM_MCP_POWER_MODE=enabled TELEGRAM_MCP_TOOL_PROFILE=full .venv/bin/telegra
```

Then point a local client at the same MCP endpoint using
`plugin/.mcp.full.example.json` as a starting point.
`plugin/.mcp.full.example.json` as a wildcard client-config starting point.

Power Mode is enforced by explicit operator choice (runtime profile + allowlist
switch). It is not reachable through the default plugin files alone.
Power Mode is enforced by explicit operator choice and local client config.

Before using Power Mode:

- verify Default Mode first;
- verify owner-local full MCP status first;
- use a test chat or explicit stable target;
- keep exact message text and target identity stable;
- do not copy Power Mode allowlists into `plugin/.mcp.json`;
Expand All @@ -65,7 +80,7 @@ Operator Workflows are broader local operations around the Telegram stack:

These workflows often involve allowlists, local databases, background jobs,
freshness checks, or PII-heavy outputs. They are intentionally separate from
Default Mode and Power Mode.
owner-local full MCP and facade profile.

## Mirror

Expand All @@ -81,7 +96,7 @@ Mirror use should be:
- kept cold unless there is a separate runtime plan.

The control-plane can audit mirror state, but it does not turn mirror runtime
jobs into Default Mode tools.
jobs into facade tools.

## Telecrawl Archive

Expand All @@ -95,7 +110,7 @@ freshness, and known import gaps.
## Subscriber Export

Subscriber/member export is sensitive and can produce PII-heavy local artifacts.
It is not exposed in the Default Mode allowlist.
It is not part of the facade profile.

Use subscriber export only with explicit user intent, private local output
paths, and clear reporting of `visible_count`, `exported_count`, `missing`, and
Expand Down
2 changes: 2 additions & 0 deletions mcp/src/telegram_mcp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
from .telethon_compat import apply_telethon_compat

apply_telethon_compat()

from . import mcp_prewarm as mcp_prewarm # noqa: E402,F401
Loading