mcpField
U
using-mtcute

using-mtcute

github.com/mtcuteDevelopment58089 installsUpdated 5d ago
Repository

About using-mtcute

using-mtcute is a skill that helps TypeScript developers use mtcute, a type-safe library for Telegram’s native MTProto API. It explains platform package selection, client methods, update handling, and bundled tools for looking up API types and signatures.

Key features

  1. 01Choose platform-specific mtcute packages
  2. 02Set up Telegram client authentication and lifecycle
  3. 03Use high-level methods for Telegram API operations
  4. 04Route updates with Dispatcher
  5. 05Look up methods, classes, and TL definitions

Use cases

  1. 01Building a TypeScript Telegram client
  2. 02Implementing message and update handling
  3. 03Finding signatures for high-level or raw TL calls

MCP support

The README mentions Context7 MCP as an optional way to consult mtcute documentation; it does not describe an MCP server or client implementation.

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/mtcute/mtcute --skill using-mtcute

After installing, type/using-mtcutein Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

tools/
get-class.js
get-constructor.js
get-method.js

Details

From the project README

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:

// ✅ 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
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:

// 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:

// 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, ...
// 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)

Some methods have "batch" counterparts that accept arrays instead of single arguments:

const durov = await tg.getUser('durov')
const [durov, telegram] = await tg.getUsers(['durov', 'telegram'])

// Similarly with `getChat` and `getChats`, `getPeer` and `getPeers`

Avoid mixing those up. If the method name is plural, it most likely returns and/or accepts arrays, keep that in mind.

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
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():

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:

import { tl } from '@mtcute/core'
TL conceptTypeScript nameExample
Object usertl.RawUser{ _: 'user', id: number, ... }
Namespaced object messages.chatstl.messages.RawChats
Method users.getUserstl.users.RawGetUsersRequest
Union typetl.TypeUsertl.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:

{
  "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.

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.

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/.

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:

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
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
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
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:

// 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():

// 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)
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:[email protected]:1080'),
})

Further reading

Full documentation and LLM-friendly reference: https://mtcute.dev/llms.txt. When Context7 MCP is available, prefer using it instead of manually fetching the docs. Library ID is /mtcute/mtcute.

Comments

Markdown supported