The bot supports Model Context Protocol (MCP) servers that extend Claude’s capabilities beyond basic file access and shell commands. Built-in MCP servers provide session control and user interaction, and you can add custom servers for Things, Notion, Typefully, and more.
Built-In MCP Servers
ask_user shows Telegram inline buttons in the active chat and pauses the turn so the user can pick an option.
Tool input shape:
{
"question": "Which file format do you prefer?",
"options": ["PDF", "PNG", "HTML"]
}
question is required
options is required
- At least 2 options are required (schema allows up to 10; 2-6 is recommended)
IPC mechanism (how it works internally):
- MCP server writes
/tmp/ask-user-<id>.json with status: "pending" and the current chat_id
- The streaming handler scans
/tmp, sends ❓ <question> with inline buttons, then marks the request as sent
- When the user taps a button, callback handling loads the same request file, validates the selected index, and deletes the file
- The selected option text is injected as the user’s next message, and the model continues from that choice
Return text to Claude:
- Success:
[Buttons sent to user. STOP HERE - do not output any more text. Wait for user to tap a button.]
- Invalid input: throws
question and at least 2 options required
Example:
You: Prepare a report
Claude: (calls ask_user with options PDF/PNG/HTML and stops)
You: [Tap PDF]
Claude: Generating PDF...
bot_control — Session Management
bot_control is the bot’s control-plane tool. It handles usage checks, model/driver switching, session management, and process restart.
Tool input shape:
{
"action": "switch_model",
"params": {
"model": "claude-sonnet-4-6",
"effort": "high"
}
}
action is required
params is optional and depends on action
IPC mechanism (how it works internally):
- MCP server writes a pending request file to
/tmp/bot-control-<id>.json
- The streaming handler scans
/tmp, executes the action, then writes result back into the same file
- MCP server polls every 100ms, up to 10 seconds, and returns the result text to Claude
- Request files are deleted after completion/error (best-effort cleanup)
Action reference (quick table):
| Action | Params | What it does | Returns |
|---|
usage | none | Fetches Claude usage and, when enabled, Codex quota | Usage text block, or failure message |
switch_model | model?: string, effort?: string | Updates current session model/effort | Updated model/effort summary, or validation error |
switch_driver | driver: "claude" | "codex" | Switches active driver and resets running driver sessions | "Switched to Codex" / "Switched to Claude Code", or error |
new_session | none | Stops and kills current session so next message starts fresh | "Session cleared. Next message will start a fresh session." |
list_sessions | none | Lists saved sessions with title/date/ID prefix | Numbered list, or "No saved sessions." |
resume_session | session_id: string | Resumes a saved session by full ID or prefix | Resumed: "...", or validation/failure message |
restart | none | Stops active driver sessions and exits process after a short delay | "Restarting bot..." |
Per-action details and examples:
usage
- Params: none
- Example call:
- Return:
USAGE DATA (show this to the user as-is, in a code block): ... or Failed to fetch usage data.
switch_model
- Params:
model (optional): model ID or display name
effort (optional)
- Effort values:
- Claude driver:
low, medium, high
- Codex driver:
minimal, low, medium, high, xhigh
- Example call:
{ "action": "switch_model", "params": { "model": "claude-opus-4-6", "effort": "high" } }
- Return: current or updated model/effort summary, or an
Unknown model... / Invalid effort... message
switch_driver
- Params:
driver required (claude or codex)
- Example call:
{ "action": "switch_driver", "params": { "driver": "codex" } }
- Return:
- Success:
"Switched to Codex" or "Switched to Claude Code"
- Validation:
Invalid driver "...". Use: claude or codex
- Availability failure:
Cannot switch to Codex: ... if Codex is disabled/unavailable
new_session
pino_logs — Pino Log Tail
pino_logs returns recent entries from the bot’s Pino log file with simple filters.
Tool input shape:
{
"level": "error",
"limit": 50,
"module": "claude"
}
level is the minimum severity (default: error)
levels (optional) is an exact list of levels like ["error","warn"] (overrides level)
limit controls how many entries to return (1-500)
module filters by module field (e.g., claude, streaming, bot)
IPC mechanism (how it works internally):
- MCP server writes
/tmp/pino-logs-<id>.json with status: "pending" and the current chat_id
- The streaming handler scans
/tmp, tails the log file, filters entries, and writes the result back
- MCP server polls every 100ms, up to 10 seconds, and returns the result text to Claude
Example calls:
{ "level": "error", "limit": 100 }
{ "levels": ["error", "warn"], "module": "streaming" }
- Example call:
{ "action": "new_session" }
- Return:
"Session cleared. Next message will start a fresh session."
list_sessions
- Params: none
- Example call:
{ "action": "list_sessions" }
- Return: numbered lines in the format
"<index>. "<title>" (<date>) — ID: <prefix>..." or "No saved sessions."
resume_session
- Params:
session_id required (full ID or prefix)
- Example call:
{ "action": "resume_session", "params": { "session_id": "a1b2c3d4" } }
- Return:
- Success:
Resumed: "<title>"
- Errors:
Missing session_id parameter., No session found matching "...", or Failed: ...
restart
- Params: none
- Example call:
- Return:
"Restarting bot..."
send_turtle — Emoji Kitchen Turtle Stickers
send_turtle sends a turtle mashup sticker to the current Telegram chat. It combines 🐢 with another emoji using Google’s Emoji Kitchen images.
Tool input shape:
{
"emoji": "😍",
"caption": "Mood"
}
emoji is optional. It accepts either a Unicode emoji ("😍") or hex codepoint ("1f60d"). If omitted, it sends turtle + turtle.
caption is optional text.
What happens internally:
- MCP server resolves the emoji combo to a prebuilt Emoji Kitchen URL
- It writes
/tmp/send-turtle-<id>.json
- The streaming handler sends the sticker to Telegram for the active chat
- If sticker upload fails, it falls back to sending the URL as a text message
Return text to Claude:
- Success:
[Turtle sticker sent to chat: 🐢 + <emoji>]
- Missing combo:
[No turtle combo found for emoji "..." (codepoint: ...). Try a different emoji ...]
Example:
You: Send me a turtle sticker with heart eyes
Claude: (uses send_turtle with emoji "😍")
Claude: 🐢 sticker sent
Custom MCP Servers
The bot loads MCP server definitions from mcp-config.ts. This file connects Claude to external tools like:
- Things 3 — Your to-do manager
- Notion — Your notes and databases
- Typefully — Social media scheduling
- Slack — Team communication
- GitHub — Repository management
- Stripe — Payment processing
- Custom tools you create
Setup: Create mcp-config.ts
Copy the example:
cd super_turtle/claude-telegram-bot
cp mcp-config.example.ts mcp-config.ts
Example configuration:
// mcp-config.ts
import type { McpServerConfig } from "./src/types";
export const MCP_SERVERS: Record<string, McpServerConfig> = {
// Built-ins are auto-included
// Example: Things 3
things: {
command: "npx",
args: ["-y", "@soulmen/things-mcp"],
env: {
THINGS_DATABASE_PATH: "/Users/you/Library/CloudStorage/iCloud~com~culturedcode~things/Things Database.thingsdatabase",
},
},
// Example: Notion
notion: {
command: "npx",
args: ["-y", "@github/notion-mcp"],
env: {
NOTION_API_KEY: "secret_...", // Get from Notion settings
},
},
// Example: Typefully
typefully: {
command: "npx",
args: ["-y", "@typefully/mcp"],
env: {
TYPEFULLY_API_TOKEN: "...",
},
},
};
mcp-config.ts is gitignored — keep it local. Store secrets in environment variables, not in the file.
Server Configuration
Each MCP server config has:
| Field | Type | Description |
|---|
command | string | Command to run (e.g., npx, python, /path/to/binary) |
args | string[] | Arguments passed to the command |
env | object | Environment variables (credentials, configs) |
timeout | number? | Timeout in ms (default: 5000) |
Example with all options:
myservice: {
command: "python",
args: ["/path/to/mcp-server.py"],
env: {
API_KEY: "sk-...",
DEBUG: "true",
},
timeout: 10000, // 10 seconds
},
Popular MCP Servers
Things 3 (macOS)
Todo management via natural language:
You: Add "Review PRs" to my inbox
Claude: (uses Things MCP)
Claude: ✅ Added to Things
Setup:
things: {
command: "npx",
args: ["-y", "@soulmen/things-mcp"],
env: {
THINGS_DATABASE_PATH: `${require("os").homedir()}/Library/CloudStorage/iCloud~com~culturedcode~things/Things Database.thingsdatabase`,
},
},
Notion
Query and update Notion databases:
You: What's on my agenda this week?
Claude: (reads from Notion)
Claude: You have: [lists items from your Notion calendar]
Setup:
notion: {
command: "npx",
args: ["-y", "@github/notion-mcp"],
env: {
NOTION_API_KEY: process.env.NOTION_API_KEY || "",
},
},
Get your API key from Notion Settings → Integrations.
GitHub
Access repositories and pull requests:
You: Show me open PRs on my projects
Claude: (queries GitHub)
Claude: [lists PRs with review status]
Setup:
github: {
command: "npx",
args: ["-y", "@anthropics/github-mcp"],
env: {
GITHUB_TOKEN: process.env.GITHUB_TOKEN || "",
},
},
Slack
Send messages and query channels:
You: Send a message to #engineering
Claude: (uses ask_user to confirm)
You: [Taps Send]
Claude: ✅ Message sent
Secrets Management
Never commit secrets to git. Use environment variables:
# .env (gitignored)
NOTION_API_KEY=ntn_...
THINGS_DATABASE_PATH=/path/to/database
GITHUB_TOKEN=ghp_...
Then reference them in mcp-config.ts:
export const MCP_SERVERS: Record<string, McpServerConfig> = {
notion: {
command: "npx",
args: ["-y", "@github/notion-mcp"],
env: {
NOTION_API_KEY: process.env.NOTION_API_KEY || "",
},
},
};
Environment variables are passed to the MCP process but not logged or exposed. Keep sensitive values in .env only.
Troubleshooting
MCP server not connecting?
-
Check the bot logs:
tail -f /tmp/claude-telegram-bot-ts.log
-
Verify the command runs:
-
Check environment variables are set:
Claude won’t use the tool?
- Ensure
mcp-config.ts exists (not just the example)
- Restart the bot after editing
mcp-config.ts
- Ask Claude explicitly: “Use my Notion integration to…”
- Check
/status to see which MCP servers loaded
Tool is slow or timing out?
- Increase the
timeout in the config (default: 5000ms)
- Check if the external service is responding
- Look at service logs for errors
Examples
Personal Assistant Setup
Connect your entire workflow:
export const MCP_SERVERS: Record<string, McpServerConfig> = {
things: {
command: "npx",
args: ["-y", "@soulmen/things-mcp"],
env: {
THINGS_DATABASE_PATH: `${require("os").homedir()}/Library/CloudStorage/iCloud~com~culturedcode~things/Things Database.thingsdatabase`,
},
},
notion: {
command: "npx",
args: ["-y", "@github/notion-mcp"],
env: {
NOTION_API_KEY: process.env.NOTION_API_KEY || "",
},
},
github: {
command: "npx",
args: ["-y", "@anthropics/github-mcp"],
env: {
GITHUB_TOKEN: process.env.GITHUB_TOKEN || "",
},
},
slack: {
command: "npx",
args: ["-y", "@slackhq/slack-mcp"],
env: {
SLACK_BOT_TOKEN: process.env.SLACK_BOT_TOKEN || "",
},
},
};
Now Claude can:
- Check your to-dos (Things)
- Query your notes (Notion)
- Review PRs (GitHub)
- Send team messages (Slack)
- All from your phone
Workflow Example
You: Add "Review design specs" to my inbox and create a Notion reminder
Claude: (sees your request)
Claude: Adding to Things... Done
Claude: Creating Notion entry... Done
Claude: ✅ Added to Things and created Notion reminder
Next Steps