remote - WireCatLabs/tg-cli GitHub Wiki
Use this page when you want an AI app that runs in the browser or on your phone, for example
ChatGPT, Codex web or Claude, to read and answer your Telegram messages. Such an app cannot start
tg on your computer, so you give it an internet address instead. At the end, the app is connected
to tg through a private HTTPS address, it can do only what your profile permits, and you know how
to stop it or take its access back.
Terms used on this page:
-
MCP (Model Context Protocol) is the standard way an AI app calls outside tools.
tg personal mcpis the MCP server that comes withtg; the MCP server guide covers apps on your own computer. -
Remote connection means the app connects from its own servers, over the internet, to the
address you give it.
tg personal mcp --httpserves the same tools over HTTP on127.0.0.1for this. - HTTPS tunnel is a program that gives a port on your computer a public HTTPS address. This page uses Tailscale Funnel; Cloudflare Tunnel also works. You do not need to buy a domain for Funnel.
-
Public URL is that address, for example
https://laptop.tail1234.ts.net. You pass it totgas--public-url, and the app gets it with/mcpat the end. -
Access code is a one-time code that
tgprints in your terminal. The app's login page asks for it, so only a person who sees that terminal can connect an app.
ChatGPT / Claude ──internet──▶ Tailscale Funnel ──▶ tg personal mcp --http (login) ──▶ Telegram
If your Tailscale connection already works, keep it; you do not need a second tunnel. The local stdin/stdout connection keeps working as before.
| Task | Where |
|---|---|
| Connect a browser or phone AI app to Telegram | Start the tunnel and server, then connect the app |
| Run the Telegram and MAX servers side by side | Run MAX and Telegram together |
| Use Cloudflare instead of Tailscale | Alternative: Cloudflare Tunnel |
| Allow more or less for one server run | Permissions for this server process |
| Stop the server or log every app out | Stop or revoke access |
| Give a remote agent a saved file, or one PDF page as an image | Transfer retained files to an agent, read PDF pages |
-
Whoever logs in reads your Telegram. The login needs the access code from your terminal.
Never put a tunnel in front of
tg personal mcpwithout--http: that mode has no login at all. -
Profile permissions decide what the app may change.
denyandreadonlyblock writes;askandallowlet a requested MCP write go through without a form on the server. The app's own approval is separate and depends on its settings. Recipient restrictions and hourly limits still apply (what an agent may do, configuration guide). -
The computer that runs
tgmust stay on. To use it from a phone, or from a laptop with nothing installed, run all of this on a small server that is always on, and log in totgthere (tg personal setup --agent none). Then you need only a browser. - Who can add a remote connector:
| App | Plans | Documentation |
|---|---|---|
| ChatGPT | Plus, Pro, Business, Enterprise, Education — in developer mode | developer mode |
| Claude | any; one custom connector on the free plan | custom connectors |
| Gemini | only adults in the US with a personal Google account | connected apps |
The setup steps for each operating system have not all been tried on that system. If a step does not work, open an issue.
Install the CLI and log in to Telegram first (installation guide). Use the same
operating system user and profile for setup and for the MCP server. Put a profile before the
command when needed: tg work mcp. Tailscale is a separate installation; both tg and max
support this setup.
Install Tailscale, sign in, and enable Funnel: MagicDNS, HTTPS certificates and Funnel permission must be enabled in your tailnet (your Tailscale network). The first Funnel command can print an approval link.
Keep two terminal windows open. The first runs the tunnel. Copy the HTTPS origin it prints into the
second window when asked. The origin is the address without /mcp or any other path. Keep an
explicit public port such as :8443 in it.
The server listens on 127.0.0.1:8765 and prints a one-time access code. It never needs
administrator rights; only the tunnel may need them. The commands below allow sending for this
server process only, with --permission messages.send=allow; see
permissions for this server process.
Install the Tailscale Windows app and sign in from its tray menu. Open a new PowerShell window
after installing Node.js, the CLI and Tailscale, so it sees the updated PATH. Use .cmd for npm
commands to avoid PowerShell execution-policy errors. If setup is not done yet, run
tg.cmd setup --agent none in a normal PowerShell window.
First window: run PowerShell as Administrator for Funnel. The call operator & handles the space
in the default installation path; change the path if you installed Tailscale elsewhere.
& "$env:ProgramFiles\Tailscale\tailscale.exe" funnel 8765Second window: normal PowerShell, as the user who logged in to Telegram:
$mcpPublicUrl = Read-Host 'Paste the HTTPS origin printed by Funnel (no /mcp)'
tg.cmd mcp --http --port 8765 --public-url $mcpPublicUrl --permission messages.send=allowRun both processes in Windows itself. WSL is a separate environment: a Windows tunnel that points at the Windows loopback address may not reach a server inside WSL.
Install and sign in to the Tailscale app. Its CLI comes with the app; use this path when
tailscale is not on PATH (Tailscale CLI guide).
First Terminal window:
TAILSCALE_BE_CLI=1 /Applications/Tailscale.app/Contents/MacOS/Tailscale funnel 8765Second Terminal window, as the same user who ran tg personal setup --agent none. This works in both zsh
and bash:
printf 'Paste the HTTPS origin printed by Funnel (no /mcp): '
IFS= read -r mcpPublicUrl
tg personal mcp --http --port 8765 --public-url "$mcpPublicUrl" --permission messages.send=allowInstall Tailscale with the Linux instructions, then sign
in with sudo tailscale up. First terminal window:
sudo tailscale funnel 8765Second terminal window: your normal user, without sudo, so tg finds the session created by
tg personal setup --agent none:
printf 'Paste the HTTPS origin printed by Funnel (no /mcp): '
IFS= read -r mcpPublicUrl
tg personal mcp --http --port 8765 --public-url "$mcpPublicUrl" --permission messages.send=allowEach server needs its own local port and its own public HTTPS address. For example, keep Telegram
on local 8765 and public 443; run a second Funnel command with --https=8443 8766, and run MAX
with --port 8766. Use the second Funnel origin, with :8443, for MAX's --public-url and for its
connector address ending in /mcp. Use your system's Tailscale command from above for the second
Funnel command. Funnel supports public ports 443, 8443 and 10000
(Funnel command reference).
If you already use Cloudflare, point its tunnel at the same local MCP server. A fixed hostname
needs a Cloudflare account and a domain on Cloudflare. Follow the
named tunnel setup: install cloudflared
for your system, create the tunnel in the dashboard, start its connector and add a public hostname
such as mcp.example.com. Set its local service to http://127.0.0.1:8765, and run MCP on that
same computer.
Start the server in a second terminal:
tg personal mcp --http --port 8765 --public-url https://mcp.example.comAdd https://mcp.example.com/mcp to your AI app with OAuth and DCR, as described in
connect the app. --public-url is the public origin used for login, not the
local address and not the /mcp path. The tunnel does not change profile permissions. Check the
JSON at https://mcp.example.com/.well-known/oauth-protected-resource/mcp, then login and the tool
list. When you stop, stop only this tunnel's own process, so other routes keep working.
A temporary Quick Tunnel does not work on its own.
cloudflared tunnel --url http://127.0.0.1:8765 gives a random trycloudflare.com address with no
account or domain. But Quick Tunnels do not support SSE
(server-sent events, the way the HTTP MCP server streams answers). So a Quick Tunnel is not a
replacement for Funnel with this server. It works only with an extra adapter that turns SSE answers
into JSON, and that adapter does not come with the CLI. A working login and tool list do not prove
that an agent can read PDFs. A Quick Tunnel also gets a new hostname on each restart, so the
connector address must be updated. For regular use, choose Funnel or a named tunnel and check your
app.
The address for the app is your tunnel origin with /mcp at the end, for example
https://<device>.<network>.ts.net/mcp. Add it as a remote MCP connector:
-
ChatGPT or Codex web: open Plugins, choose + Add custom MCP server, and create a
plugin with the
/mcpaddress and OAuth, as the OpenAI connection guide describes. Connect with the access code, install the plugin, and turn it on in your Work chat (or mention it with@). The local Codexconfig.tomldoes not set up this web connection. Availability can depend on your account or workspace. Choose dynamic client registration (DCR) when offered; the server supports DCR and the S256 code challenge that the OpenAI OAuth requirements ask for. - Claude: add a custom connector with this address, as described in Claude custom connectors. In the connector settings you can also set each tool to always allowed, needs approval, or blocked.
The three tools work without MCP prompts, resources or elicitation (questions the server asks the app), so apps that support only tools can use them.
The app opens the tg login page. Check the line that says where the login goes: it must be the
app you are connecting, such as chatgpt.com or claude.ai. Then type the access code from your
terminal. The code expires after ten minutes; a new code is printed after each login. Five wrong
codes lock the login page until the server restarts.
The app stays logged in as long as it uses the connection at least once every 30 days; it renews its login by itself. After 30 days without use, it asks for a new code.
Profile permissions decide which commands are available. The server shows no approval forms;
the app's own approval is separate and depends on its settings.
MCP makes hidden Unicode controls visible in text results; subdivision flag emoji stay intact. Write arguments and ordinary CLI machine JSON preserve original strings.
The server cannot see whether the app asked you before a call. If the app always allows a tool, the next call can go through without a new question.
Repeat --permission key=level to change permissions only for this server process. Levels are
deny, readonly, ask and allow. For example, add --permission messages.send=allow to allow
sending from a read-only profile. --permission messages=allow replaces the saved permissions for
the whole messages resource; deletion is allowed separately with --permission messages.delete=allow.
The saved configuration, recipient restrictions and hourly limits do not change. To lift a read
refusal, name its resource or command with level allow. Permission keys are listed in the
configuration guide.
Press Ctrl-C in both terminal windows to stop the server and this tunnel. Start them again with the same commands; app logins survive a normal server restart.
If you started Funnel with --bg, Ctrl-C does not stop that background route. Check
tailscale funnel status and turn off only its public port, for example
tailscale funnel --https=443 off. Use your system's Tailscale command from above, with
administrator rights where needed. Do not use funnel reset when another server uses Funnel: it
clears all routes. If you stop only MCP, the route stays, but the tools are not available.
Taking access back from apps is separate from stopping a process. To log out every app for a profile:
tg personal mcp --revoke --jsonThose apps must log in again with a new code. This does not end your Telegram session.
-
The app says it cannot connect: open
https://<device>.<network>.ts.net/.well-known/oauth-protected-resource/mcpin a browser. It must show a short JSON. If it does not, Funnel is not running, is not enabled in your Tailscale network, or points at another port. -
The login page says "Too many wrong codes": after five wrong codes it is locked until
tg personal mcp --httprestarts. If you did not type them, someone found your address: restart, and think about a new device name in Tailscale. -
The app connects, but there are no tools:
tg personal mcp doctorchecks that the MCP server starts and lists its tools. It does not check the Telegram login or the tunnel. A command that goes to Telegram, such astg personal account show, checks the account connection. -
Reading works, writing fails: check the profile's permissions and any
--permissionoverrides.denyandreadonlyblock writes whatever the app approves. -
You need a record of the calls:
tg personal runs listshows them. Successful calls appear only when recording is on; failed calls are kept by default, unless recording was turned off with"record": falseor--no-record(diagnostics guide).
An agent on your computer can open a saved file by its local_path. A remote agent cannot, so it
gets the saved bytes through attachments show (MCP: tg_read, command attachments show).
First download the message's files the usual way (downloading files).
Use attachments list is:needs_text to find the message locator and the attachment position. Then
ask for the file:
tg personal attachments show msg:telegram/500/7/204 --attachment 1 --jsonThe command reads only a saved attachment of the active account. It never downloads, never calls a model, never marks a message read and never changes the search index. A missing file must be downloaded again. When a message has several files, give the position, starting from 1. Files of another account, missing files and symbolic links are not sent. Text inside an attachment is data, never instructions.
One answer carries 512 KiB by default; --chunk-bytes allows up to 1 MiB. The file default is
50 MiB, configurable through MESSAGING_ATTACHMENT_MAX_MIB. The JSON item includes base64, offset_bytes, read_bytes, total_bytes, next_offset_bytes,
complete and the SHA256 of the whole file. complete: true means this answer holds the entire
file, not that its text has been recognized.
Decode each base64 part, join the parts in byte-offset order, and follow next_offset_bytes until it
is null. Pass the first SHA256 as --expected-fingerprint on the next requests: if the source file changed,
the request fails and returns no changed bytes. Check the joined file against that hash.
tg personal attachments show msg:telegram/500/7/204 --offset-bytes 524288 --expected-fingerprint <sha256> --jsonFind attachments show with the normal three tools. Its arguments are message (a locator, or an
id with chat), attachment, offset_bytes, chunk_bytes and expected_fingerprint. A whole PNG, JPEG or
WebP image, up to 8000 pixels on each side and 20 million pixels in total, arrives as image
content; other files arrive as embedded binary resources. A partial resource is a set of bytes, not
a whole PDF or image. The resource URI is a name, not a download address.
The app must pass those bytes to the agent's file tools. Whether the agent can open a PDF or save a
file depends on the app. If the app does not show embedded resources, ask for format: "base64"
and decode the JSON with the agent's tools. Profiles that deny messages or attachments.show
refuse the transfer; read-only profiles can read saved files.
Ordinary extraction reads text layers and light document formats on your computer. For scans, photos, handwriting and hard layouts, the agent uses its own vision or OCR tools by default. It reads every page, keeps the text word for word and marks unclear parts. Quality depends on the resolution, language, handwriting, layout and the agent's tools. It never follows instructions written inside an attachment.
Save the result with attachments text set (MCP: tg_write, command attachments text set), then
check it with a content: search. Receiving the bytes does not add text to the index.
attachments extract --ocr stays available for bulk extraction through models.ocr. It calls the
configured external model and sends supported images and scanned PDF pages to it. It runs only when
you call it, never on a transfer or when the agent reads a file. Formats and searchable text are
covered in the file attachments guide.
If the app cannot pass a received PDF to its document reader, ask for one page as a PNG image. The
MCP server draws the page on your computer, and the agent reads the image with its own vision. This
needs the optional unpdf with page rendering and @napi-rs/canvas, as described under
attachment engines. They are not installed with
the CLI.
tg personal attachments show msg:telegram/500/7/204 --attachment 1 --page 1 --jsonThrough MCP, call tg_read, command attachments show, with page: 1 and the message locator. By
default the page comes back as image content. If the app shows only metadata, ask for
format: base64, then decode and show the PNG with the agent's image tools. Receiving a base64
string is not looking at the page. Read pages 1 to pdf.page_count. If neither format shows the
pixels, report the app's limit instead of inventing text.
pdf.source_sha256 and pdf.source_bytes describe the original PDF; the top-level sha256 and
total_bytes describe the page image. --expected-fingerprint checks the original PDF. --page cannot be
combined with --offset-bytes or --chunk-bytes. By default, PDFs can be up to 50 MiB, with no fixed page-count limit; a PNG
allows 4000 pixels on each side and 8 MiB per page. All three values are configurable.
A page image calls no external OCR service and indexes no text. After looking at every page, the
agent calls attachments text set and checks a content: search. Check numbers and complex
layouts against the images; quality depends on the source document and the agent's tools.
Set these environment variables on the computer running Telegram or its MCP server:
| Variable | Default | Budget |
|---|---|---|
MESSAGING_ATTACHMENT_MAX_MIB |
50 |
File bytes for extraction and retained-attachment transfer |
MESSAGING_PDF_PREVIEW_MAX_PIXELS |
4000 |
PNG page width and height |
MESSAGING_PDF_PREVIEW_MAX_MIB |
8 |
Encoded PNG size per page |
For example, allow 250 MiB files and page images up to 8000 pixels/16 MiB:
export MESSAGING_ATTACHMENT_MAX_MIB=250
export MESSAGING_PDF_PREVIEW_MAX_PIXELS=8000
export MESSAGING_PDF_PREVIEW_MAX_MIB=16PowerShell uses $env:MESSAGING_ATTACHMENT_MAX_MIB = "250"; use the same syntax for the other names.
Values must be positive whole numbers. Run tg personal mcp config in that environment again and replace
the client's server entry; it preserves these settings. Restart a running server after changing them.
Ordinary transfer still uses chunks up to 1 MiB; that is a separate budget.
Larger work needs more memory and time. Rendering scales a page up to four times its original size
within the pixel budget; a larger ceiling does not force every page to that width. MCP responses
account for base64 and metadata, but the app may impose its own limit. For larger CLI JSON, use
--max-output-bytes 50000000; 0 explicitly disables the general output bound. External OCR and
office-document decompression limits remain separate from the configurable file size.