feat: init commit

This commit is contained in:
Kyle Fang
2025-06-29 11:53:01 +08:00
parent 966f4d1e03
commit 7e1877f41c
14 changed files with 1137 additions and 29 deletions

View File

@@ -0,0 +1,12 @@
{
"permissions": {
"allow": [
"Bash(bun add:*)",
"Bash(bun run tsc:*)",
"Bash(find:*)",
"Bash(bun run:*)",
"Bash(mkdir:*)"
],
"deny": []
}
}

37
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Type check
run: bun run tsc --noEmit
- name: Build
run: bun run build
- name: Check build output
run: |
if [ ! -f telegram-mcp ]; then
echo "Build failed: telegram-mcp executable not found"
exit 1
fi
echo "Build successful: telegram-mcp executable created"

95
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,95 @@
name: Build and Release
on:
push:
tags:
- 'v*'
workflow_dispatch:
permissions:
contents: write
jobs:
build:
strategy:
matrix:
include:
- os: ubuntu-latest
target: linux-x64
build_name: telegram-mcp-linux-x64
- os: macos-latest
target: darwin-x64
build_name: telegram-mcp-darwin-x64
- os: macos-latest
target: darwin-arm64
build_name: telegram-mcp-darwin-arm64
- os: windows-latest
target: win-x64
build_name: telegram-mcp-win-x64.exe
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Build executable
run: |
if [ "${{ matrix.os }}" = "windows-latest" ]; then
bun build ./src/main.ts --compile --outfile ${{ matrix.build_name }} --target=bun-${{ matrix.target }}
else
bun build ./src/main.ts --compile --outfile ${{ matrix.build_name }} --target=bun-${{ matrix.target }}
fi
shell: bash
- name: Create tarball (Unix)
if: matrix.os != 'windows-latest'
run: tar -czf ${{ matrix.build_name }}.tar.gz ${{ matrix.build_name }} README.md
- name: Create zip (Windows)
if: matrix.os == 'windows-latest'
run: |
Compress-Archive -Path ${{ matrix.build_name }}, README.md -DestinationPath ${{ matrix.build_name }}.zip
shell: pwsh
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.build_name }}
path: |
${{ matrix.build_name }}.tar.gz
${{ matrix.build_name }}.zip
release:
needs: build
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/')
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Create Release
uses: softprops/action-gh-release@v2
with:
files: |
telegram-mcp-linux-x64/telegram-mcp-linux-x64.tar.gz
telegram-mcp-darwin-x64/telegram-mcp-darwin-x64.tar.gz
telegram-mcp-darwin-arm64/telegram-mcp-darwin-arm64.tar.gz
telegram-mcp-win-x64.exe/telegram-mcp-win-x64.exe.zip
draft: false
prerelease: false
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

52
.gitignore vendored
View File

@@ -1,9 +1,49 @@
# Dependencies
node_modules/
.pnp
.pnp.js
# Build outputs
telegram-mcp
telegram-mcp-*
*.exe
dist/
.nyc_output/
**/.DS_Store
.idea
.vscode
*.log
*.tsbuildinfo
build/
# Session data
bot-data/
session*
*.session
# Environment files
.env
.env.local
.env.*.local
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# TypeScript
*.tsbuildinfo
# Testing
.nyc_output/
# Misc
.cache/
*.tmp
*.temp
telegram-mcp

128
README.md
View File

@@ -1,14 +1,130 @@
# telegram-mcp
mtcute powered Telegram bot
A Model Context Protocol (MCP) server for interacting with Telegram using mtcute.
## Features
- Send text messages to chats
- Read messages from chats
- Search messages
- List and get information about dialogs (chats)
- Get recent messages across all chats
## Setup
### Installation
#### Option 1: Download Pre-built Binary
Download the latest release for your platform from the [releases page](https://github.com/yourusername/telegram-mcp/releases):
- **macOS (Apple Silicon)**: `telegram-mcp-darwin-arm64.tar.gz`
- **macOS (Intel)**: `telegram-mcp-darwin-x64.tar.gz`
- **Linux**: `telegram-mcp-linux-x64.tar.gz`
- **Windows**: `telegram-mcp-win-x64.exe.zip`
Extract the archive and make the binary executable (Unix systems):
```bash
tar -xzf telegram-mcp-*.tar.gz
chmod +x telegram-mcp
```
#### Option 2: Build from Source
1. Clone the repository and install dependencies:
```bash
git clone https://github.com/yourusername/telegram-mcp.git
cd telegram-mcp
bun install
```
2. Build the executable:
```bash
bun run build
```
### Initial Setup (First Time Only)
1. Get your Telegram API credentials from https://my.telegram.org
2. Run the initial setup to authenticate with Telegram:
```bash
export API_ID=your_api_id
export API_HASH=your_api_hash
./telegram-mcp
```
The server will:
- Prompt you to enter your phone number
- Send you a verification code via Telegram
- Ask for the verification code
- Display the absolute storage path (you'll need this for MCP configuration)
3. Note the storage path displayed in the output. It will look something like:
```
Storage path: /Users/username/telegram-mcp/bot-data/session
```
## Usage
### As an MCP Server
Add to your Claude Desktop config using the storage path from the initial setup:
```json
{
"mcpServers": {
"telegram": {
"command": "/path/to/telegram-mcp",
"env": {
"API_ID": "your_api_id",
"API_HASH": "your_api_hash",
"TELEGRAM_STORAGE_PATH": "/absolute/path/from/initial/setup"
}
}
}
}
```
**Important**: The `TELEGRAM_STORAGE_PATH` must be the absolute path shown during initial setup. This ensures the MCP server uses the authenticated session.
### Available Tools
#### Message Tools
- `messages_sendText` - Send a text message to a chat
- `chatId` (required): Chat/User ID or username
- `text` (required): Message text to send
- `replyToMessageId`: Optional message ID to reply to
- `messages_getHistory` - Get message history from a chat
- `chatId` (required): Chat/User ID or username
- `limit`: Number of messages (default: 100, max: 100)
- `offsetId`: Message ID for pagination
- `messages_search` - Search for messages
- `query` (required): Search query
- `chatId`: Specific chat to search in (optional)
- `limit`: Number of results (default: 50)
- `messages_getRecent` - Get recent messages from all chats
- `limit`: Number of chats (default: 10)
- `messagesPerChat`: Messages per chat (default: 10)
#### Dialog Tools
- `dialogs_list` - List all dialogs
- `limit`: Maximum dialogs (default: 50)
- `filter`: Filter options (onlyUsers, onlyGroups, onlyChannels)
- `dialogs_getInfo` - Get detailed dialog information
- `chatId` (required): Chat/User ID or username
## Development
Run in development mode:
```bash
bun install --frozen-lockfile
cp .env.example .env
# edit .env
bun start
bun run dev
```
*generated with @mtcute/create-bot*
The server stores session data in `bot-data/` directory.

175
bun.lock
View File

@@ -4,6 +4,7 @@
"": {
"name": "telegram-mcp",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.13.2",
"@mtcute/bun": "^0.24.3",
},
"devDependencies": {
@@ -22,6 +23,8 @@
"@fuman/utils": ["@fuman/utils@0.0.15", "", {}, "sha512-3H3WzkfG7iLKCa/yNV4s80lYD4yr5hgiNzU13ysLY2BcDqFjM08XGYuLd5wFVp4V8+DA/fe8gIDW96To/JwDyA=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.13.2", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-Vx7qOcmoKkR3qhaQ9qf3GxiVKCEu+zfJddHv6x3dY/9P6+uIwJnmuAur5aB+4FDXf41rRrDnOEGkviX5oYZ67w=="],
"@mtcute/bun": ["@mtcute/bun@0.24.3", "", { "dependencies": { "@fuman/bun": "0.0.15", "@fuman/io": "0.0.15", "@fuman/net": "0.0.15", "@fuman/utils": "0.0.15", "@mtcute/core": "^0.24.3", "@mtcute/html-parser": "^0.24.0", "@mtcute/markdown-parser": "^0.24.0", "@mtcute/wasm": "^0.24.3" } }, "sha512-voNLlACw7Su8+DKCiiBRi8F1yjl3r/AulUW8KFncPMILXo2H94qNengVg9KXUYx1mTLhJIqfRM3ouuuQQna9Lg=="],
"@mtcute/core": ["@mtcute/core@0.24.4", "", { "dependencies": { "@fuman/io": "0.0.15", "@fuman/net": "0.0.15", "@fuman/utils": "0.0.15", "@mtcute/file-id": "^0.24.3", "@mtcute/tl": "^204.0.0", "@mtcute/tl-runtime": "^0.24.3", "@types/events": "3.0.0", "long": "5.2.3" } }, "sha512-4dQ1MhY1DmEUqpOLyssgv9WljDr/itpUTVanTg6pZsraU6Z+ohH4E3V2UOQcfmWCQbecmMZL7UeLlScfw0oDXQ=="],
@@ -44,8 +47,36 @@
"@types/node": ["@types/node@24.0.7", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-YIEUUr4yf8q8oQoXPpSlnvKNVKDQlPMWrmOcgzoduo7kvA2UF0/BwJ/eMKFTiTtkNL17I0M6Xe2tvwFU7be6iw=="],
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
"body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="],
"bun-types": ["bun-types@1.2.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ElC7ItwT3SCQwYZDYoAH+q6KT4Fxjl8DtZ6qDulUFBmXA8YB4xo+l54J9ZJN+k2pphfn9vk7kfubeSd5QfTVJQ=="],
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
"content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="],
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
"cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
@@ -54,16 +85,160 @@
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
"entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
"eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
"eventsource-parser": ["eventsource-parser@3.0.3", "", {}, "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA=="],
"express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
"express-rate-limit": ["express-rate-limit@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
"finalhandler": ["finalhandler@2.1.0", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="],
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"htmlparser2": ["htmlparser2@10.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.1", "entities": "^6.0.0" } }, "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g=="],
"http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
"is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"long": ["long@5.2.3", "", {}, "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
"mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-to-regexp": ["path-to-regexp@8.2.0", "", {}, "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="],
"pkce-challenge": ["pkce-challenge@5.0.0", "", {}, "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ=="],
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
"raw-body": ["raw-body@3.0.0", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.6.3", "unpipe": "1.0.0" } }, "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g=="],
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="],
"serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="],
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
"side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
"statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
"type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"zod": ["zod@3.25.67", "", {}, "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw=="],
"zod-to-json-schema": ["zod-to-json-schema@3.24.6", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg=="],
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
}
}

View File

@@ -6,9 +6,11 @@
"packageManager": "bun@1.2.16",
"scripts": {
"dev": "bun --watch ./src/main.ts",
"start": "bun ./src/main.ts"
"start": "bun ./src/main.ts",
"build": "bun build ./src/main.ts --compile --outfile telegram-mcp"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.13.2",
"@mtcute/bun": "^0.24.3"
},
"devDependencies": {

View File

@@ -1,10 +1,11 @@
import process from 'node:process'
import process from 'node:process';
const API_ID = Number.parseInt(process.env.API_ID!)
const API_HASH = process.env.API_HASH!
const API_ID = Number.parseInt(process.env.API_ID!);
const API_HASH = process.env.API_HASH!;
const STORAGE_PATH = process.env.TELEGRAM_STORAGE_PATH || 'bot-data/session';
if (Number.isNaN(API_ID) || !API_HASH) {
throw new Error('API_ID or API_HASH not set!')
throw new Error('API_ID or API_HASH not set!');
}
export { API_HASH, API_ID }
export { API_HASH, API_ID, STORAGE_PATH };

View File

@@ -1,13 +1,14 @@
import { TelegramClient } from '@mtcute/bun'
import { TelegramServer } from './server/telegram-server.js';
import * as env from './env.ts'
async function main() {
const server = new TelegramServer();
const tg = new TelegramClient({
apiId: env.API_ID,
apiHash: env.API_HASH,
storage: 'bot-data/session',
})
try {
await server.start();
} catch (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
}
const user = await tg.start()
console.log('Logged in as', user.username)
main().catch(console.error);

View File

@@ -0,0 +1,101 @@
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
ListToolsRequestSchema,
CallToolRequestSchema,
ErrorCode,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
import { TelegramClient } from '@mtcute/bun';
import * as path from 'node:path';
import * as env from '../env.js';
import { registerTools, handleToolCall } from '../tools/index.js';
export class TelegramServer {
private server: Server;
private telegramClient: TelegramClient | null = null;
constructor() {
this.server = new Server(
{
name: 'telegram-mcp',
version: '0.0.1',
},
{
capabilities: {
tools: {},
},
}
);
this.setupHandlers();
this.setupErrorHandling();
}
private setupHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: registerTools(),
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (!this.telegramClient) {
throw new McpError(
ErrorCode.InternalError,
'Telegram client not initialized'
);
}
return handleToolCall(request.params.name, request.params.arguments || {}, this.telegramClient);
});
}
private setupErrorHandling() {
process.on('SIGINT', async () => {
await this.cleanup();
process.exit(0);
});
process.on('SIGTERM', async () => {
await this.cleanup();
process.exit(0);
});
}
private async cleanup() {
if (this.telegramClient) {
await this.telegramClient.disconnect();
}
await this.server.close();
}
async start() {
// Initialize Telegram client
this.telegramClient = new TelegramClient({
apiId: env.API_ID,
apiHash: env.API_HASH,
storage: env.STORAGE_PATH,
});
try {
// Print storage path for initial setup
const absoluteStoragePath = path.resolve(env.STORAGE_PATH);
console.error(`\n=== Telegram MCP Setup ===`);
console.error(`Storage path: ${absoluteStoragePath}`);
console.error(`\nIf this is your first run, you'll need to authenticate with your phone number.`);
console.error(`After authentication, use the storage path above in your MCP configuration.\n`);
const user = await this.telegramClient.start();
console.error(`\nConnected to Telegram as ${user.username || user.id}`);
console.error(`Storage path: ${absoluteStoragePath}`);
console.error(`\nReady to accept MCP requests.`);
} catch (error) {
console.error('Failed to start Telegram client:', error);
throw error;
}
// Start MCP server
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Telegram MCP server started');
}
}

196
src/tools/dialog-tools.ts Normal file
View File

@@ -0,0 +1,196 @@
import type { TelegramClient, Dialog } from '@mtcute/bun';
import type { ToolInfo } from './index.js';
export const dialogTools: ToolInfo[] = [
{
name: 'dialogs_list',
description: 'List all dialogs (chats)',
inputSchema: {
type: 'object',
properties: {
limit: {
type: 'number',
description: 'Maximum number of dialogs to return (default: 50)',
default: 50,
},
filter: {
type: 'object',
description: 'Filter options',
properties: {
onlyUsers: {
type: 'boolean',
description: 'Only show user chats',
},
onlyGroups: {
type: 'boolean',
description: 'Only show group chats',
},
onlyChannels: {
type: 'boolean',
description: 'Only show channels',
},
},
},
},
},
},
{
name: 'dialogs_getInfo',
description: 'Get detailed information about a specific dialog',
inputSchema: {
type: 'object',
properties: {
chatId: {
type: 'string',
description: 'Chat/User ID or username',
},
},
required: ['chatId'],
},
},
];
export async function handleDialogTools(
name: string,
args: any,
client: TelegramClient
) {
switch (name) {
case 'dialogs_list':
return await listDialogs(client, args);
case 'dialogs_getInfo':
return await getDialogInfo(client, args);
default:
throw new Error(`Unknown dialog tool: ${name}`);
}
}
async function listDialogs(client: TelegramClient, args: any) {
const { limit = 50, filter = {} } = args;
try {
const dialogs: Dialog[] = [];
let count = 0;
for await (const dialog of client.iterDialogs()) {
if (count >= limit) break;
// Apply filters
if (filter.onlyUsers && dialog.peer.type !== 'user') continue;
if (filter.onlyGroups && dialog.peer.type !== 'chat') continue;
if (filter.onlyChannels && dialog.peer.type !== 'chat') continue;
dialogs.push(dialog);
count++;
}
return {
content: [
{
type: 'text',
text: JSON.stringify({
dialogs: dialogs.map(formatDialog),
count: dialogs.length,
}, null, 2),
},
],
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Error listing dialogs: ${error.message}`,
},
],
isError: true,
};
}
}
async function getDialogInfo(client: TelegramClient, args: any) {
const { chatId } = args;
try {
// Find the dialog first
let dialog: Dialog | null = null;
const numericChatId = Number(chatId);
const searchId = Number.isNaN(numericChatId) ? chatId : numericChatId;
for await (const d of client.iterDialogs()) {
if (d.peer.id === searchId || d.peer.username === chatId) {
dialog = d;
break;
}
}
if (!dialog) {
throw new Error('Dialog not found');
}
// Get full info about the peer
let fullInfo: any = {};
if (dialog.peer.type === 'user') {
const userFull = await client.getFullUser(dialog.peer);
fullInfo = {
bio: userFull.bio,
commonChatsCount: userFull.commonChatsCount,
isBlocked: userFull.isBlocked,
};
} else if (dialog.peer.type === 'chat') {
const chatFull = await client.getFullChat(dialog.peer);
fullInfo = {
bio: chatFull.bio,
participantsCount: chatFull.onlineCount || 0,
adminsCount: chatFull.adminsCount,
};
}
return {
content: [
{
type: 'text',
text: JSON.stringify({
peer: {
id: dialog.peer.id,
type: dialog.peer.type,
username: dialog.peer.username,
displayName: dialog.peer.displayName,
},
dialog: formatDialog(dialog),
fullInfo,
}, null, 2),
},
],
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Error getting dialog info: ${error.message}`,
},
],
isError: true,
};
}
}
function formatDialog(dialog: Dialog) {
return {
id: dialog.peer.id,
name: dialog.peer.displayName || `Chat ${dialog.peer.id}`,
username: dialog.peer.username,
type: dialog.peer.type,
unreadCount: dialog.unreadCount,
unreadMentionsCount: dialog.unreadMentionsCount,
isPinned: dialog.isPinned,
isMuted: dialog.isMuted,
lastMessage: dialog.lastMessage ? {
id: dialog.lastMessage.id,
date: dialog.lastMessage.date,
text: dialog.lastMessage.text,
isOutgoing: dialog.lastMessage.isOutgoing,
} : null,
};
}

35
src/tools/index.ts Normal file
View File

@@ -0,0 +1,35 @@
import type { TelegramClient } from '@mtcute/bun';
import { messageTools, handleMessageTools } from './message-tools.js';
import { dialogTools, handleDialogTools } from './dialog-tools.js';
export type ToolInfo = {
name: string;
description: string;
inputSchema: {
type: 'object';
properties: Record<string, any>;
required?: string[];
};
};
export function registerTools(): ToolInfo[] {
return [
...messageTools,
...dialogTools,
];
}
export async function handleToolCall(
name: string,
args: any,
client: TelegramClient
) {
// Route to appropriate handler based on tool name prefix
if (name.startsWith('messages_')) {
return handleMessageTools(name, args, client);
} else if (name.startsWith('dialogs_')) {
return handleDialogTools(name, args, client);
}
throw new Error(`Unknown tool: ${name}`);
}

297
src/tools/message-tools.ts Normal file
View File

@@ -0,0 +1,297 @@
import type { TelegramClient, Dialog, Message } from '@mtcute/bun';
import type { ToolInfo } from './index.js';
export const messageTools: ToolInfo[] = [
{
name: 'messages_sendText',
description: 'Send a text message to a chat',
inputSchema: {
type: 'object',
properties: {
chatId: {
type: 'string',
description: 'Chat/User ID or username to send message to',
},
text: {
type: 'string',
description: 'Message text to send',
},
replyToMessageId: {
type: 'number',
description: 'Optional message ID to reply to',
},
},
required: ['chatId', 'text'],
},
},
{
name: 'messages_getHistory',
description: 'Get message history from a chat',
inputSchema: {
type: 'object',
properties: {
chatId: {
type: 'string',
description: 'Chat/User ID or username to get messages from',
},
limit: {
type: 'number',
description: 'Number of messages to retrieve (default: 100, max: 100)',
default: 100,
},
offsetId: {
type: 'number',
description: 'Message ID to start from (for pagination)',
},
},
required: ['chatId'],
},
},
{
name: 'messages_search',
description: 'Search for messages in a chat',
inputSchema: {
type: 'object',
properties: {
chatId: {
type: 'string',
description: 'Chat/User ID or username to search in (optional, searches all chats if not provided)',
},
query: {
type: 'string',
description: 'Search query',
},
limit: {
type: 'number',
description: 'Number of messages to retrieve (default: 50)',
default: 50,
},
},
required: ['query'],
},
},
{
name: 'messages_getRecent',
description: 'Get recent messages from all chats',
inputSchema: {
type: 'object',
properties: {
limit: {
type: 'number',
description: 'Number of recent chats to include (default: 10)',
default: 10,
},
messagesPerChat: {
type: 'number',
description: 'Number of messages per chat (default: 10)',
default: 10,
},
},
},
},
];
export async function handleMessageTools(
name: string,
args: any,
client: TelegramClient
) {
switch (name) {
case 'messages_sendText':
return await sendTextMessage(client, args);
case 'messages_getHistory':
return await getMessageHistory(client, args);
case 'messages_search':
return await searchMessages(client, args);
case 'messages_getRecent':
return await getRecentMessages(client, args);
default:
throw new Error(`Unknown message tool: ${name}`);
}
}
async function sendTextMessage(client: TelegramClient, args: any) {
const { chatId, text, replyToMessageId } = args;
try {
const sentMessage = await client.sendText(
Number.isNaN(Number(chatId)) ? chatId : Number(chatId),
text,
{
replyTo: replyToMessageId ? replyToMessageId : undefined,
}
);
return {
content: [
{
type: 'text',
text: JSON.stringify({
success: true,
message: formatMessage(sentMessage),
}, null, 2),
},
],
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Error sending message: ${error.message}`,
},
],
isError: true,
};
}
}
async function getMessageHistory(client: TelegramClient, args: any) {
const { chatId, limit = 100, offsetId } = args;
try {
const messages = await client.getHistory(Number.isNaN(Number(chatId)) ? chatId : Number(chatId), {
limit: Math.min(limit, 100),
offset: offsetId ? { id: offsetId, date: 0 } : undefined,
});
return {
content: [
{
type: 'text',
text: JSON.stringify({
messages: messages.map(formatMessage),
count: messages.length,
}, null, 2),
},
],
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Error getting message history: ${error.message}`,
},
],
isError: true,
};
}
}
async function searchMessages(client: TelegramClient, args: any) {
const { chatId, query, limit = 50 } = args;
try {
const results: Message[] = [];
if (chatId) {
// Search in specific chat
const messages = await client.searchMessages({
chatId: Number.isNaN(Number(chatId)) ? chatId : Number(chatId),
query,
limit,
});
results.push(...messages);
} else {
// Search globally
const messages = await client.searchGlobal({
query,
limit,
});
results.push(...messages);
}
return {
content: [
{
type: 'text',
text: JSON.stringify({
messages: results.map(formatMessage),
count: results.length,
query,
}, null, 2),
},
],
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Error searching messages: ${error.message}`,
},
],
isError: true,
};
}
}
async function getRecentMessages(client: TelegramClient, args: any) {
const { limit = 10, messagesPerChat = 10 } = args;
try {
const recentChats: Array<{
dialog: any;
messages: any[];
}> = [];
let count = 0;
for await (const dialog of client.iterDialogs()) {
if (count >= limit) break;
const messages = await client.getHistory(dialog.peer, {
limit: messagesPerChat,
});
recentChats.push({
dialog: {
id: dialog.peer.id,
name: dialog.peer.displayName || `Chat ${dialog.peer.id}`,
username: dialog.peer.username,
type: dialog.peer.type,
lastMessageDate: dialog.lastMessage?.date,
},
messages: messages.map(formatMessage),
});
count++;
}
return {
content: [
{
type: 'text',
text: JSON.stringify({
chats: recentChats,
count: recentChats.length,
}, null, 2),
},
],
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Error getting recent messages: ${error.message}`,
},
],
isError: true,
};
}
}
function formatMessage(msg: Message) {
return {
id: msg.id,
date: msg.date,
text: msg.text,
senderId: msg.sender.id,
senderName: msg.sender.displayName || msg.sender.username || `User ${msg.sender.id}`,
senderUsername: msg.sender.username,
isOutgoing: msg.isOutgoing,
chatId: msg.chat.id,
chatName: msg.chat.displayName || msg.chat.username || `Chat ${msg.chat.id}`,
};
}

View File

@@ -4,7 +4,7 @@
"module": "ESNext",
"moduleResolution": "Bundler",
"noEmit": true,
"allowImportingTsExtensions": true,
"allowImportingTsExtensions": false,
"target": "es2022",
"allowJs": true,
"sourceMap": true,