using-mtcute

Use when working with mtcute, @mtcute/* packages, Telegram MTProto in TypeScript, TL types, Telegram API methods, or building Telegram bots/clients in TypeScript. Not for Bot API wrapper libraries (grammy, telegraf, node-telegram-bot-api).

451 stars

Best use case

using-mtcute is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Use when working with mtcute, @mtcute/* packages, Telegram MTProto in TypeScript, TL types, Telegram API methods, or building Telegram bots/clients in TypeScript. Not for Bot API wrapper libraries (grammy, telegraf, node-telegram-bot-api).

Teams using using-mtcute should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/using-mtcute/SKILL.md --create-dirs "https://raw.githubusercontent.com/mtcute/mtcute/main/.claude/_skills/using-mtcute/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/using-mtcute/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How using-mtcute Compares

Feature / Agentusing-mtcuteStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Use when working with mtcute, @mtcute/* packages, Telegram MTProto in TypeScript, TL types, Telegram API methods, or building Telegram bots/clients in TypeScript. Not for Bot API wrapper libraries (grammy, telegraf, node-telegram-bot-api).

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# mtcute — TypeScript MTProto Library

## What is mtcute?

mtcute is a modern, type-safe TypeScript library for Telegram's **MTProto** protocol. Unlike Bot API wrappers (grammy, telegraf, node-telegram-bot-api), mtcute speaks Telegram's native binary protocol directly, giving access to the **full** Telegram API — not just the bot subset.

## Architecture overview

Core packages (you likely won't use these directly):
- @mtcute/core: MTProto client, high-level methods, TL schema, types
- @mtcute/html-parser: HTML ↔ Telegram entities
- @mtcute/markdown-parser: Markdown ↔ Telegram entities
- @mtcute/file-id: Bot API file_id parsing

Platform packages (use the one that matches your target platform):
- @mtcute/node, @mtcute/bun, @mtcute/deno: Runtime bindings for server-side JavaScript
- @mtcute/web: Browser bindings (IndexedDB, WebCrypto, WebSocket)

Extra packages (only use these for the specific functionality they cover):
- @mtcute/dispatcher: Update routing, filters, bot framework
- @mtcute/convert: Importing sessions from other libraries
- @mtcute/test: Helpers for unit tests

Always install and import everything from the **platform package**, not from `@mtcute/core`:

```typescript
// ✅ Correct — platform package pre-configures crypto, transport, storage
import { TelegramClient } from '@mtcute/node'

// ❌ Wrong — raw core client requires manual platform setup
import { TelegramClient } from '@mtcute/core'
```

## Basic usage

### Creating a client

```typescript
import { TelegramClient } from '@mtcute/node'

const tg = new TelegramClient({
  apiId: Number(process.env.API_ID),
  apiHash: process.env.API_HASH,
  storage: 'my-account', // SQLite file name (Node.js)
})
```

### Client lifecycle

When you create a `TelegramClient`, it starts in the `disconnected` state.

In most cases, you will want to use the `start` method to connect and authenticate:

```typescript
// Bot login
const self = await tg.start({ botToken: process.env.BOT_TOKEN })

// User login (interactive)
const self = await tg.start({
  phone: () => input('Phone: '),
  code: () => input('Code: '),
  password: () => input('2FA password: '),
})
```

However in some cases you may want to manually control the lifecycle:

```typescript
// Manually connect
await tg.connect()

// Manually disconnect
await tg.disconnect()

// Disconnect and destroy the instance
await tg.destroy()
```

### Calling high-level methods

The `TelegramClient` class has 300+ high-level convenience methods. Some of the most commonly used ones are listed below:
- **messages**: `sendText`, `sendMedia`, `getHistory`, `editMessage`, `deleteMessages`, `forwardMessages`, `searchMessages`, ...
- **chats**: `getChat`, `getChatMember`, `banChatMember`, `setChatTitle`, `setChatPhoto`, ...
- **users**: `getUsers`, `getMe`, `setMyProfilePhoto`, ...
- **files**: `downloadFile`, `uploadFile`, `downloadAsStream`, ...
- **dialogs**: `getDialogs`, `getPeerDialogs`, ...
- **bots**: `answerInlineQuery`, `answerCallbackQuery`, `setMyCommands`, ...
- **forums**: `createForumTopic`, `editForumTopic`, ...
- **stickers**: `getStickerSet`, `createStickerSet`, ...
- **stories**: `sendStory`, `getStories`, ...
- **auth**: `start`, `logOut`, `sendCode`, ...
- **password**: `enable2fa`, `change2fa`, ...

```typescript
// Send a text message
await tg.sendText('me', 'Hello!')

// Send with formatting
await tg.sendText(chatId, html`Hello, <b>${name}</b>!`)

// Send media
await tg.sendMedia(chatId, {
  type: 'photo',
  file: 'file:./photo.jpg', // note the file: prefix!
  caption: 'Check this out',
})

// Get chat info
const chat = await tg.getChat('username')

// Download file
await tg.downloadToFile('path/to/save.jpg', photo)
```

### Peer resolution

High-level methods in mtcute accept `InputPeerLike` for chat/user arguments, which can be:
- Numeric ID: `123456789`
- Username string: `'username'`
- `'me'` / `'self'` for the current user
- A `tl.TypeInputPeer` object 
- A high-level object (`User`, `Chat`, `Message`, etc.)

### Handling updates with Dispatcher

```typescript
import { Dispatcher, filters } from '@mtcute/dispatcher'

const dp = Dispatcher.for(tg)

dp.onNewMessage(filters.chat('private'), async (msg) => {
  await msg.replyText('Hello!')
})

dp.onNewMessage(filters.command('start'), async (msg) => {
  await msg.replyText('Welcome!')
})

dp.onCallbackQuery(filters.dataPrefix('btn_'), async (query) => {
  await query.answer({ text: 'Clicked!' })
})

// Start receiving updates
await tg.startUpdatesLoop()
```

### Raw TL method calls

For methods not wrapped by high-level API, use `.call()`:

```typescript
const result = await tg.call({
  _: 'messages.getEmojiKeywords',
  langCode: 'en',
})
// result is fully typed based on the method's return type
```

## TL type system

mtcute generates TypeScript types from Telegram's TL schema. All types live under the `tl` namespace:

```typescript
import { tl } from '@mtcute/core'
```

| TL concept | TypeScript name | Example |
|---|---|---|
| Object `user` | `tl.RawUser` | `{ _: 'user', id: number, ... }` |
| Namespaced object `messages.chats` | `tl.messages.RawChats` | |
| Method `users.getUsers` | `tl.users.RawGetUsersRequest` | |
| Union type | `tl.TypeUser` | `tl.RawUser \| tl.RawUserEmpty` |

Every TL object has a `_` discriminant field with its constructor name (e.g., `_: 'user'`).

Type guards are available: `tl.isAnyPeer()`, `tl.isAnyMessage()`, etc.

The full TL schema is available as JSON at `@mtcute/node/tl/api-schema.json` (or any platform package). Format:
```jsonc
{
  "l": 223, // layer number
  "e": [ // array of TL entries
    {
      "kind": "class" | "method",
      "name": "user",
      "id": 826896937, // constructor ID
      "type": "User", // return/union type
      "arguments": [{
        "name": "id",
        "type": "int53", // base type name (union or primitive)
        "typeModifiers"?: {
          "predicate"?: "flags.0", // conditional field, e.g. "flags.3"
          "isVector"?: true, // optional: when true, type is actually Vector<type>
        }
      }, ...],
      "comment": "..."
    },
    ...
  ],
  "u": { "User": "..." } // union comments
}
```

When `typeModifiers.predicate` is set (e.g. `"flags.3"`), the field is optional and only present when bit 3 of the `flags` field is set. When `isVector` is true, the actual type is `Vector<type>`.

## Looking up types and method signatures

Three bundled tools read from the installed `@mtcute/*` packages in `node_modules`. All support fuzzy matching and auto-correct typos.

**When to use which:**
- Need a raw TL type/method definition or its TS interface → `get-constructor`
- Need a high-level client method signature (params, return type, JSDoc) → `get-method`
- Need a high-level wrapper class/interface definition (Message, Chat, etc.) → `get-class`
- Need the TL parameters a raw API call expects → `get-constructor --with-references`
- Not sure which method/class to use → `get-method --search` or `get-class --search`

### get-constructor: TL schema lookup

Shows TL definition, TypeScript type, union membership, and return type.

```bash
node .claude/skills/using-mtcute/tools/get-constructor.js <name>
node .claude/skills/using-mtcute/tools/get-constructor.js --with-references <name>
```

Accepts any of these input formats:
- TL names: `user`, `messages.sendMessage`
- TS type names: `RawUser`, `TypeInputUser`, `tl.messages.RawChats`, `messages.RawGetHistoryRequest`

Use `--with-references` when you need to understand the types referenced by a constructor's arguments or return type — it appends all referenced constructors/unions to the output.

### get-method: high-level client method lookup

Shows the full JSDoc + method signature from `TelegramClient`.

```bash
node .claude/skills/using-mtcute/tools/get-method.js <method-name>
node .claude/skills/using-mtcute/tools/get-method.js --search <keyword>
node .claude/skills/using-mtcute/tools/get-method.js --list
```

- Default mode: exact/fuzzy match on method name, falls back to substring in names then descriptions
- `--search`: search in both method names and descriptions, lists all matches
- `--list`: print all available methods with one-line descriptions

### get-class: high-level type/class lookup

Shows the full class/interface/type definition with all members from `highlevel/types/`.

```bash
node .claude/skills/using-mtcute/tools/get-class.js <name>
node .claude/skills/using-mtcute/tools/get-class.js --no-members <name>
node .claude/skills/using-mtcute/tools/get-class.js --search <keyword>
node .claude/skills/using-mtcute/tools/get-class.js --list
```

- Default mode: exact/fuzzy match on export name, shows full definition with all members
- `--no-members`: only show the declaration line + JSDoc, not the body
- `--search`: search in both names and descriptions, lists all matches (e.g., `--search keyboard`)
- `--list`: print all available exports with one-line descriptions

## High-level types

mtcute wraps raw TL objects in convenient classes with helper methods:

- `Message` — wraps `tl.RawMessage`, has `.replyText()`, `.editText()`, `.delete()`, `.forward()`, etc.
- `Chat` — wraps channel/group/user chat info
- `User` — wraps `tl.RawUser`
- `Document`, `Photo`, `Video`, `Audio`, `Voice`, `Sticker` — media types
- `InlineQuery`, `CallbackQuery`, `ChatJoinRequest` — update types

These are in `node_modules/@mtcute/core/dist/highlevel/types/`.

## Text formatting

mtcute supports tagged template literals for formatting:

```typescript
import { html, md } from '@mtcute/node'

await tg.sendText(chat, html`<b>Bold</b> and <i>italic</i>`)
await tg.sendText(chat, md`**Bold** and *italic*`)
```

The `html` and `md` tags handle escaping automatically when interpolating variables.

## Common patterns

### Error handling

```typescript
import { tl } from '@mtcute/node'

try {
  await tg.sendText(chatId, 'hello')
} catch (e) {
  if (tl.RpcError.is(e, 'FLOOD_WAIT_%d')) {
    console.log(`Rate limited, wait ${e.seconds}s`)
  }
}
```

### Sending media

```typescript
import { InputMedia } from '@mtcute/node'

// Upload from path (note the file: prefix!)
await tg.sendMedia(chatId, InputMedia.document('file:/path/to/file.pdf', {
  fileName: 'report.pdf',
}))

// Upload from buffer/stream
await tg.sendMedia(chatId, InputMedia.photo(buffer))

// Upload from URL
await tg.sendMedia(chatId, InputMedia.photo('https://example.com/image.jpg'))
```

### Keyboards

```typescript
import { BotKeyboard } from '@mtcute/node'

// Inline keyboard
await tg.sendText(chatId, 'Pick one:', {
  replyMarkup: BotKeyboard.inline([
    [BotKeyboard.callback('Yes', 'btn_yes'), BotKeyboard.callback('No', 'btn_no')],
    [BotKeyboard.url('Docs', 'https://mtcute.dev')],
  ]),
})

// Reply keyboard
await tg.sendText(chatId, 'Choose:', {
  replyMarkup: BotKeyboard.reply([
    [BotKeyboard.text('Option A'), BotKeyboard.text('Option B')],
  ]),
})
```

### Pagination / iteration

Many methods have an `iter*` counterpart returning `AsyncIterableIterator`:

```typescript
// Iterate over chat history
for await (const msg of tg.iterHistory(chatId, { chunkSize: 100 })) {
  console.log(msg.text)
}

// Iterate over search results
for await (const msg of tg.iterSearchMessages({ query: 'hello', chatId })) {
  console.log(msg.id)
}

// Non-iter methods return paginated results with a `.next` cursor
const page = await tg.getHistory(chatId, { limit: 50 })
const nextPage = await tg.getHistory(chatId, { limit: 50, offset: page.next })
```

### Client events

The client exposes `Emitter` instances for connection lifecycle and errors. Subscribe with `.add()`, unsubscribe with `.remove()`:

```typescript
// Connection state: 'offline' | 'connecting' | 'updating' | 'connected'
// 'updating' means connected but catching up on missed updates
tg.onConnectionState.add((state) => {
  console.log('Connection:', state)
})

// Unhandled errors (if no listener, errors are logged to console)
tg.onError.add((err) => {
  console.error('Error:', err)
})

// Raw TL updates (before dispatcher processing)
tg.onRawUpdate.add((update) => { ... })
```

### Proxy / custom transport (Node.js)

```typescript
import { TelegramClient } from '@mtcute/node'
import {
  HttpProxyTcpTransport,
  SocksProxyTcpTransport,
  MtProxyTcpTransport,
  proxyTransportFromUrl,
} from '@mtcute/node'

const tg = new TelegramClient({
  // or parse from URL (socks4/5, http, https, mtproto):
  transport: proxyTransportFromUrl('socks5://user:pass@1.2.3.4:1080'),
})
```

## Further reading

Full documentation and LLM-friendly reference: https://mtcute.dev/llms.txt

Related Skills

writing-methods

451
from mtcute/mtcute

Use when adding or modifying high-level client methods in packages/core/src/highlevel/methods/. Covers function signatures, codegen annotations, peer resolution, update handling, pagination, and all common patterns.

update-layer

451
from mtcute/mtcute

Use when updating TL schema layer in mtcute, fetching new Telegram API schemas, or when user asks to update the layer/schema

using-git-worktrees

31392
from sickn33/antigravity-awesome-skills

Git worktrees create isolated workspaces sharing the same repository, allowing work on multiple branches simultaneously without switching.

using-superpowers

31392
from sickn33/antigravity-awesome-skills

Use when starting any conversation - establishes how to find and use skills, requiring Skill tool invocation before ANY response including clarifying questions

using-neon

31392
from sickn33/antigravity-awesome-skills

Neon is a serverless Postgres platform that separates compute and storage to offer autoscaling, branching, instant restore, and scale-to-zero. It's fully compatible with Postgres and works with any language, framework, or ORM that supports Postgres.

using-superpowers

24269
from davila7/claude-code-templates

Use when starting any conversation - establishes how to find and use skills, requiring Skill tool invocation before ANY response including clarifying questions

using-git-worktrees

24269
from davila7/claude-code-templates

Use when starting feature work that needs isolation from current workspace or before executing implementation plans - creates isolated git worktrees with smart directory selection and safety verification

using-neon

24269
from davila7/claude-code-templates

Guides and best practices for working with Neon Serverless Postgres. Covers getting started, local development with Neon, choosing a connection method, Neon features, authentication (@neondatabase/auth), PostgREST-style data API (@neondatabase/neon-js), Neon CLI, and Neon's Platform API/SDKs. Use for any Neon-related questions.

PDF OCR using Gemini LLM

3891
from openclaw/skills

Extract text from PDFs using Google Gemini OCR. Use when extracting text from PDFs, performing OCR on scanned documents, or processing image-based PDFs.

upwork-automation-using-ai

3891
from openclaw/skills

Automate Upwork job search and proposal drafting in one browser session using the Browser Relay/Browser Automation workflow. Use when the user wants to: (1) open/login Upwork, (2) find top matching jobs from explicit criteria, (3) filter out disqualifiers, (4) open the best job, and (5) fill proposal fields without submitting. Also use when the user asks to persist in the same tab/session across steps.

using-superpowers

1864
from LeoYeAI/openclaw-master-skills

Use when starting any conversation - establishes how to find and use skills, requiring Skill tool invocation before ANY response including clarifying questions

using-git-worktrees

1864
from LeoYeAI/openclaw-master-skills

Use when starting feature work that needs isolation from current workspace or before executing implementation plans - creates isolated git worktrees with smart directory selection and safety verification