This follows on from “I connected my Telegram to ChatGPT. Then Claude connected to the same thing”. That one was about how telegram-business-mcp came about in the first place: as a personal bridge between Telegram Business, ChatGPT and Claude.
Back then the task was fairly simple.
I wanted to type into ChatGPT:
What did this particular person write to me on Telegram today?
And have the model actually go into the conversation, find the right chat, read the messages and answer.
Then sending messages appeared.
Then photos, voice messages, files.
Then the same MCP connected to Claude.
At some point it became clear that this was no longer a small script for personal use.
And that is where the less romantic part of the story began.
To give yourself access to your own Telegram, it is enough to confirm that everything works.
To offer the same tool to other people, that turns out not to be enough at all.
Between “it works” and “it’s a product” there was one more system
The first version lived very simply:
Telegram → collector → SQLite → MCP → ChatGPT / Claude
On my own server I controlled everything.
One user.
One Telegram.
One archive.
One person who roughly remembers why it is all arranged this way.
For a hosted service the construction is already different.
Separate users appeared, along with authentication, OAuth, scopes, tenant isolation, a public domain, a privacy policy, terms, a demo mode and a proper connection flow.
In the old article I even noted specifically that SQLite is excellent for a personal archive, but that I would not automatically turn such an architecture into a corporate multi-tenant product.
A few weeks later I did, of course, start turning it into a multi-tenant product.
Not automatically.
That qualifier matters.
The project got a name: Lemma Telepath.
It is now a hosted MCP service that connects to Telegram Business through the official API and gives an MCP client tools for working with conversations.
The user connects their Telegram once, and after that can open ChatGPT or Claude and write roughly the same thing this all started with:
See what they wrote to me.
Except that between that sentence and Telegram there is now a service that has to make sense to somebody other than me.
Then I pressed Submit
Publication was the next logical step.
Something interesting is happening with MCP right now.
The protocol is turning from a technical detail into a separate layer of the ecosystem rather quickly.
There is the server.
There are the clients.
There are permissions.
There is a catalogue of tools.
There are models that have to understand which tool may be called, what it will change and what to expect from it at all.
So I submitted Telepath for review to be published by OpenAI.
And for some reason I expected the nerve-racking part to be about infrastructure.
OAuth.
Redirects.
The domain.
Authorisation.
Some unexpected HTTP status.
But the first concrete remarks turned out to be more interesting than that.
What they reviewed first was the tool descriptions.
A tool description suddenly stopped being documentation
Telepath currently has a few dozen MCP tools.
For example:
telegram_get_messagestelegram_search_messagestelegram_get_filetelegram_send_messagetelegram_set_reaction
To a human this looks almost like ordinary API documentation.
A name.
A description.
A JSON schema.
A few annotations.
But to a model it is not quite documentation.
It is a contract.
And here the difference starts to matter.
If you write, for a human:
The tool puts a reaction on a message.
A human will most likely ask which message exactly, and on whose behalf.
A model may well decide the tool can put a reaction anywhere it has been given a chat_id and a message_id.
The Telegram Business API does not work that way.
A bot cannot simply put a reaction on my behalf in an arbitrary private Business conversation, for instance. Different chat types and different ways of connecting have their own boundaries.
The code already knew this.
The tool’s description turned out to be less precise.
The scanner put the problem roughly like this:
This tool’s description appears to claim capabilities that do not match its behavior.
And I unexpectedly liked that phrasing.
Because it really is a bug.
Not in the code.
In the promise.
It behaved correctly. It was described incorrectly
Two reaction tools had to be rewritten so that the description itself made it unambiguous where exactly they work.
I added no new function.
I fixed no old one.
The Telegram API did not change.
What changed was a few sentences of text.
But for an agent that is a real change of interface.
Before, the tool said:
I can do X
After:
I can do X only here, in this context, and on this person’s behalf
With an ordinary API that difference can sometimes be hidden in the documentation somewhere on page three.
With agent tools the documentation effectively takes part in the runtime.
The model reads it before deciding.
And this, I think, is one of those things I used to understand rather theoretically. After the review it became entirely concrete.
Then openWorldHint came up
The next remark looked even duller.
telegram_get_file carried the annotation:
openWorldHint: false
Meanwhile the tool goes to Telegram and fetches an actual file from there.
The check answered, entirely fairly:
This tool is marked openWorldHint: false, but its behavior appears to interact with an independently controlled external system.
So the tool is read-only.
It deletes nothing.
It sends nothing.
It does not change the state of Telegram.
But that still does not mean it operates in a closed world.
Telegram is an external system.
The file exists there.
Its availability does not depend on my MCP server.
And here, for the first time, I saw the difference between two notions very clearly:
readOnly ≠ closed world
Until then an annotation felt almost like minor metainformation around the real code.
In practice that metainformation tells the model what kind of reality it is dealing with right now.
We corrected openWorldHint on several Telegram tools.
Again without changing behaviour.
The server did exactly the same thing before and after the commit.
But the server’s description came closer to matching the server.
The final change looked almost comical
After all that development, the change could be described roughly like this:
- clarified the descriptions of two reaction tools;
- fixed the annotations on tools that work with the Telegram API;
- did not change tool behaviour.
In the codebase this is far from the most impressive commit.
But changes like this, I think, show the move from a project “for myself” to a public tool better than anything else.
When you build something only for yourself, the system has an enormous hidden dependency:
the author understands everything
You know what a function’s name actually means.
You remember Telegram’s limitations.
You know why a reaction cannot be made here.
You understand that this tool reaches outside.
You reconstruct the missing context in your head automatically.
A public MCP must not have that dependency.
The model does not have my head.
Nor, all the more, does the user.
Everything that matters for using a tool safely has to live in the contract itself.
The funny part is that the code turned out to be the easy half
For a long time I thought of productization as technical work around the main project.
Here is Telepath itself.
And around it you need OAuth, a site, documentation, permissions, deployment and the rest of the obligatory scaffolding.
Now the line seems to me to run somewhat differently.
A tool consists of more than the function that runs on the server.
It looks more like this:
behavior + schema + description + permissions + annotations
If one of those parts lies, the whole tool is described incorrectly.
Even when the function inside works perfectly.
For an ordinary backend you could call that a documentation problem.
For an agent it is already part of the system’s behaviour.
Because the decision to call it is not made by a person who spent months reading the documentation.
It is made by a model, right now.
Where Telepath is at the moment
As of this entry the project has moved a fair distance from that first personal prototype.
It runs as a hosted MCP.
It has its own user authentication.
Telegram connects through the official Business API.
One backend can be used from different MCP clients.
The server is published in the MCP Registry.
And the publication processes in the OpenAI and Anthropic ecosystems are under way.
I do not yet know the outcome of the review.
So there is no handsome ending here along the lines of:
We were accepted.
There may be ten more remarks ahead.
One of them may force a rewrite not of a description but of half the architecture.
But I rather like recording the moment before the result.
Because after a successful launch the whole road usually collapses into a single line:
Published
And inside that line there is a fair amount of strange engineering work.
I started a separate Telegram channel for this
The project now generates enough small events that turning them into separate articles makes no sense.
Another review stage passed.
A new tool appeared.
Something was published in the registry.
The onboarding changed.
A release went out.
For things like that I made a separate channel, Lemma Telepath — @lemma_telepath.
It will carry short updates on reviews, listings, releases and the MCP itself.
Here I would rather bring the things after which my understanding of what I am actually building changes.
Today the change is roughly this.
Three weeks ago I thought of an MCP tool as a function a model can call.
Now I think of it more as a promise the server makes to the model.
The function has to work.
But first the tool has to explain honestly what exactly it can do. And where its capabilities end.
Three weeks ago I was teaching a model to read my Telegram. Now I am teaching the connector to tell the model precisely what it is able to do.