@dikacode/telegram-mcp 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -5
- package/dist/config.js +1 -2
- package/dist/index.js +47 -4
- package/dist/setup-tools.js +23 -3
- package/dist/update-check.js +151 -0
- package/dist/version.js +76 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
13
13
|
<a href="https://www.npmjs.com/package/@dikacode/telegram-mcp"><img alt="npm" src="https://img.shields.io/npm/v/@dikacode/telegram-mcp?style=flat-square&color=5eead4&label=npm"></a>
|
|
14
|
-
<a href="https://www.npmjs.com/package/@dikacode/telegram-mcp"><img alt="downloads" src="https://img.shields.io/npm/dm/@dikacode/telegram-mcp?style=flat-square&color=38bdf8"></a>
|
|
15
14
|
<a href="https://github.com/dikaglorya/telegram-mcp/actions/workflows/ci.yml"><img alt="ci" src="https://github.com/dikaglorya/telegram-mcp/actions/workflows/ci.yml/badge.svg"></a>
|
|
16
15
|
<img alt="node" src="https://img.shields.io/badge/node-%E2%89%A5%2020-0ea5e9?style=flat-square">
|
|
17
16
|
<img alt="tools" src="https://img.shields.io/badge/tools-69-a78bfa?style=flat-square">
|
|
@@ -78,6 +77,7 @@ The server speaks MCP over stdio, so the last command is only a manual check. Th
|
|
|
78
77
|
- [Architecture](#architecture)
|
|
79
78
|
- [Requirements](#requirements)
|
|
80
79
|
- [Install](#install)
|
|
80
|
+
- [Updating](#updating)
|
|
81
81
|
- [Credentials](#credentials)
|
|
82
82
|
- [First login — from chat](#first-login--from-chat)
|
|
83
83
|
- [Connect your agent](#connect-your-agent)
|
|
@@ -150,7 +150,7 @@ telegram-mcp
|
|
|
150
150
|
}
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
-
Pin a version if you want reproducible installs: `npx -y @dikacode/telegram-mcp@1.0.
|
|
153
|
+
Pin a version if you want reproducible installs: `npx -y @dikacode/telegram-mcp@1.0.1`.
|
|
154
154
|
|
|
155
155
|
### 2. Clone and link
|
|
156
156
|
|
|
@@ -170,7 +170,7 @@ npm link # optional: makes `telegram-mcp` available on PATH
|
|
|
170
170
|
Download the tarball attached to the latest release and install it globally:
|
|
171
171
|
|
|
172
172
|
```bash
|
|
173
|
-
npm install -g https://github.com/dikaglorya/telegram-mcp/releases/download/v1.0.
|
|
173
|
+
npm install -g https://github.com/dikaglorya/telegram-mcp/releases/download/v1.0.1/dikacode-telegram-mcp-1.0.1.tgz
|
|
174
174
|
telegram-mcp
|
|
175
175
|
```
|
|
176
176
|
|
|
@@ -186,6 +186,38 @@ npx -y github:dikaglorya/telegram-mcp
|
|
|
186
186
|
|
|
187
187
|
npm fetches the repository, installs dependencies, runs the `prepare` build, and starts the launcher.
|
|
188
188
|
|
|
189
|
+
## Updating
|
|
190
|
+
|
|
191
|
+
How you installed it decides whether you have to do anything.
|
|
192
|
+
|
|
193
|
+
| Installed with | Behaviour |
|
|
194
|
+
| --- | --- |
|
|
195
|
+
| `npx -y @dikacode/telegram-mcp` (unpinned) | **Automatic.** npm resolves `latest` on every start, so the next client restart picks up a new release. |
|
|
196
|
+
| `npx -y @dikacode/telegram-mcp@1.0.1` (pinned) | **Never.** That is the point of pinning. |
|
|
197
|
+
| `npm install -g @dikacode/telegram-mcp` | **Manual**, one command: `npm update -g @dikacode/telegram-mcp` |
|
|
198
|
+
| Release tarball | **Manual.** Reinstall from the newer tarball. |
|
|
199
|
+
| `npx -y github:dikaglorya/telegram-mcp` | **Not automatic.** npm caches the fetched repo; clear the cache or pin a ref (`#main`) to move forward. |
|
|
200
|
+
|
|
201
|
+
Check what you are running:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
telegram-mcp --version
|
|
205
|
+
npm outdated -g @dikacode/telegram-mcp # global installs only
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The server also tells you. When a newer release exists it prints one line to
|
|
209
|
+
stderr at startup, after the MCP transport is already serving:
|
|
210
|
+
|
|
211
|
+
```
|
|
212
|
+
[telegram-mcp] update available: 1.0.1 -> 1.0.2. Run `npm install -g @dikacode/telegram-mcp@latest`, or set TELEGRAM_UPDATE_CHECK=0 to silence this.
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
That check is a single request to the npm registry with an 800 ms timeout,
|
|
216
|
+
cached for 24 hours, and it never blocks startup or writes to stdout. If you
|
|
217
|
+
want zero outbound traffic that is not Telegram itself, set
|
|
218
|
+
`TELEGRAM_UPDATE_CHECK=0`. `telegram_doctor` shows the version it knows about
|
|
219
|
+
without touching the network.
|
|
220
|
+
|
|
189
221
|
## Credentials
|
|
190
222
|
|
|
191
223
|
1. Open [my.telegram.org/apps](https://my.telegram.org/apps) and log in with your phone number.
|
|
@@ -477,6 +509,8 @@ All settings are environment variables. The server also loads a `.env` next to t
|
|
|
477
509
|
| `TELEGRAM_EXPORT_DIR` | `~/.telegram-mcp/exports` | History exports. |
|
|
478
510
|
| `TELEGRAM_AUDIT_LOG` | `~/.telegram-mcp/audit.log` | Call log. |
|
|
479
511
|
| `TELEGRAM_MAX_DOWNLOAD_BYTES` | `0` (unlimited) | Hard cap for one download. A non-numeric value is refused rather than treated as unlimited. |
|
|
512
|
+
| `TELEGRAM_UPDATE_CHECK` | `true` | Set to `0` to stop the startup check for a newer npm release. |
|
|
513
|
+
| `TELEGRAM_UPDATE_CHECK_TIMEOUT_MS` | `800` | Timeout for that check, capped at 10 s. |
|
|
480
514
|
| `TELEGRAM_MCP_NODE` | current Node | Launcher override, for exotic Node installs. |
|
|
481
515
|
|
|
482
516
|
`~` and `~\` are both expanded. Relative paths resolve against the current working directory.
|
|
@@ -494,6 +528,8 @@ All settings are environment variables. The server also loads a `.env` next to t
|
|
|
494
528
|
| `Cannot find module .../dist/index.js` | The package was installed without building. Run `npm run build`, or use the release tarball. |
|
|
495
529
|
| Login works, then the next run asks again | `TELEGRAM_SESSION_PATH` changed between runs. Keep it stable, or leave it unset. |
|
|
496
530
|
| Window/terminal closes when started by hand | Expected: the server speaks MCP on stdio. Start it through a client, or use `npm run login` for the standalone CLI. |
|
|
531
|
+
| `update available: …` on every start | A newer release is on npm. Run the upgrade command it prints, or set `TELEGRAM_UPDATE_CHECK=0` if you pin versions on purpose. |
|
|
532
|
+
| `telegram-mcp --version` prints nothing | You are on `1.0.0`, which predates the flag. Upgrade to `1.0.1` or newer. |
|
|
497
533
|
|
|
498
534
|
## Development
|
|
499
535
|
|
|
@@ -503,7 +539,7 @@ The repository ships with a unit suite and a real MCP harness that spawns the bu
|
|
|
503
539
|
npm install
|
|
504
540
|
npm run build
|
|
505
541
|
|
|
506
|
-
npm run test:unit #
|
|
542
|
+
npm run test:unit # 72 offline checks
|
|
507
543
|
npm run test:mcp # read-only end-to-end pass
|
|
508
544
|
npm run test:mcp:write # + self-cleaning writes
|
|
509
545
|
npm run test:destructive # + disposable chats (opt-in, flood-limited)
|
|
@@ -556,7 +592,8 @@ The TypeScript is built with `tsc` to `dist/`. There is no bundler; the publishe
|
|
|
556
592
|
|
|
557
593
|
## Release
|
|
558
594
|
|
|
559
|
-
**v1.0.
|
|
595
|
+
**v1.0.1** is the current release, published to npm as
|
|
596
|
+
[`@dikacode/telegram-mcp`](https://www.npmjs.com/package/@dikacode/telegram-mcp).
|
|
560
597
|
|
|
561
598
|
- 69 MCP tools, all registered with strict input schemas.
|
|
562
599
|
- In-chat login, including two-step verification.
|
|
@@ -564,6 +601,7 @@ The TypeScript is built with `tsc` to `dist/`. There is no bundler; the publishe
|
|
|
564
601
|
- Read-back verification for state-changing tools.
|
|
565
602
|
- Optional allowlist, audited calls, redacted secrets.
|
|
566
603
|
- Cross-platform launcher; no hardcoded paths.
|
|
604
|
+
- `--version` / `--help`, and a startup notice when a newer release exists.
|
|
567
605
|
- End-to-end harness with paging, policy probes and artifact cleanup.
|
|
568
606
|
|
|
569
607
|
See [CHANGELOG.md](https://github.com/dikaglorya/telegram-mcp/blob/main/CHANGELOG.md) for the full release notes. Release artifacts are on the [releases page](https://github.com/dikaglorya/telegram-mcp/releases).
|
package/dist/config.js
CHANGED
|
@@ -11,8 +11,7 @@ import fs from "node:fs";
|
|
|
11
11
|
import os from "node:os";
|
|
12
12
|
import path from "node:path";
|
|
13
13
|
import { lockDir, lockFile } from "./perms.js";
|
|
14
|
-
export
|
|
15
|
-
export const SERVER_VERSION = "1.0.0";
|
|
14
|
+
export { SERVER_NAME, SERVER_VERSION } from "./version.js";
|
|
16
15
|
export class ConfigError extends Error {
|
|
17
16
|
constructor(message) {
|
|
18
17
|
super(message);
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Entrypoint. Loads .env, builds the MCP server, serves over stdio.
|
|
4
4
|
* stdout = MCP protocol only; diagnostics go to stderr.
|
|
5
|
+
*
|
|
6
|
+
* Exception: `--version` / `--help` are plain CLI invocations. They answer on
|
|
7
|
+
* stdout and exit before the transport is created, so nothing is corrupted.
|
|
5
8
|
*/
|
|
6
9
|
import "./stream-guard.js";
|
|
7
10
|
import "./env.js";
|
|
@@ -9,6 +12,41 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
9
12
|
import { buildServer } from "./server.js";
|
|
10
13
|
import { ensureDirs, getConfig, redact } from "./config.js";
|
|
11
14
|
import { shutdownLogin } from "./login-tools.js";
|
|
15
|
+
import { PACKAGE_NAME, SERVER_NAME, SERVER_VERSION } from "./version.js";
|
|
16
|
+
import { warnIfOutdated } from "./update-check.js";
|
|
17
|
+
const HELP = `${SERVER_NAME} ${SERVER_VERSION}
|
|
18
|
+
|
|
19
|
+
An MCP server for a personal Telegram account, spoken over stdio.
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
telegram-mcp serve MCP on stdio (what an MCP client runs)
|
|
23
|
+
telegram-mcp --version, -v print the version and exit
|
|
24
|
+
telegram-mcp --help, -h print this help and exit
|
|
25
|
+
|
|
26
|
+
Setup:
|
|
27
|
+
1. Get api_id and api_hash from https://my.telegram.org/apps
|
|
28
|
+
2. Provide them as TELEGRAM_API_ID / TELEGRAM_API_HASH, or let the agent call
|
|
29
|
+
telegram_setup
|
|
30
|
+
3. Log in with telegram_login_start (the agent can do this for you)
|
|
31
|
+
|
|
32
|
+
Updating:
|
|
33
|
+
npx -y ${PACKAGE_NAME}@latest # npx, always current
|
|
34
|
+
npm install -g ${PACKAGE_NAME}@latest # global install
|
|
35
|
+
|
|
36
|
+
Docs: https://github.com/dikaglorya/telegram-mcp
|
|
37
|
+
`;
|
|
38
|
+
function handleCliFlags(argv) {
|
|
39
|
+
const flags = new Set(argv.slice(2));
|
|
40
|
+
if (flags.has("--version") || flags.has("-v")) {
|
|
41
|
+
process.stdout.write(`${SERVER_VERSION}\n`);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
if (flags.has("--help") || flags.has("-h")) {
|
|
45
|
+
process.stdout.write(HELP);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
12
50
|
async function main() {
|
|
13
51
|
// A missing .env must NOT be fatal: the user needs telegram_setup (and the
|
|
14
52
|
// doctor) to be reachable so they can fix it from chat. Only a genuinely
|
|
@@ -28,6 +66,9 @@ async function main() {
|
|
|
28
66
|
process.stderr.write(config
|
|
29
67
|
? `[telegram-mcp] serving over stdio (allowAll=${config.allowAllChats}, allowlist=${config.allowlist.length})\n`
|
|
30
68
|
: `[telegram-mcp] serving over stdio (unconfigured — telegram_setup required)\n`);
|
|
69
|
+
// After the transport is live, so a slow or unreachable registry can never
|
|
70
|
+
// delay the first tool call.
|
|
71
|
+
warnIfOutdated();
|
|
31
72
|
const shutdown = async (signal) => {
|
|
32
73
|
process.stderr.write(`[telegram-mcp] ${signal}, shutting down\n`);
|
|
33
74
|
try {
|
|
@@ -42,7 +83,9 @@ async function main() {
|
|
|
42
83
|
process.on("SIGINT", () => void shutdown("SIGINT"));
|
|
43
84
|
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
|
44
85
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
86
|
+
if (!handleCliFlags(process.argv)) {
|
|
87
|
+
main().catch((err) => {
|
|
88
|
+
process.stderr.write(`[telegram-mcp] fatal: ${redact(err instanceof Error ? err.stack ?? err.message : String(err))}\n`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
});
|
|
91
|
+
}
|
package/dist/setup-tools.js
CHANGED
|
@@ -23,6 +23,8 @@ import { getClient } from "./telegram.js";
|
|
|
23
23
|
import { lockFile } from "./perms.js";
|
|
24
24
|
import { normalizeError } from "./errors.js";
|
|
25
25
|
import { ok, guard } from "./util.js";
|
|
26
|
+
import { PACKAGE_NAME, SERVER_VERSION } from "./version.js";
|
|
27
|
+
import { getUpdateInfo } from "./update-check.js";
|
|
26
28
|
/** Keys this module owns when writing `.env`; everything else is preserved. */
|
|
27
29
|
const MANAGED_KEYS = [
|
|
28
30
|
"TELEGRAM_API_ID",
|
|
@@ -131,10 +133,11 @@ function buildEnvBody(values) {
|
|
|
131
133
|
export function registerSetupTools(server) {
|
|
132
134
|
server.registerTool("telegram_doctor", {
|
|
133
135
|
title: "Telegram health check",
|
|
134
|
-
description: "Read-only health check: config, state dir, session presence, session file
|
|
135
|
-
"(auto-repaired to 0600), and live authorization. Returns { healthy, checks[], account }. " +
|
|
136
|
+
description: "Read-only health check: config, installed version, state dir, session presence, session file " +
|
|
137
|
+
"permissions (auto-repaired to 0600), and live authorization. Returns { healthy, checks[], account }. " +
|
|
136
138
|
"A missing or unauthorized session is reported as a failed check, not an error — call " +
|
|
137
|
-
"telegram_login_start to fix it.
|
|
139
|
+
"telegram_login_start to fix it. An available update is informational and never fails the check. " +
|
|
140
|
+
"Never returns the api_hash or the session string.",
|
|
138
141
|
inputSchema: {},
|
|
139
142
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
140
143
|
}, async () => guard(async () => {
|
|
@@ -153,6 +156,23 @@ export function registerSetupTools(server) {
|
|
|
153
156
|
catch (err) {
|
|
154
157
|
checks.push({ name: "config", pass: false, detail: normalizeError(err).message });
|
|
155
158
|
}
|
|
159
|
+
// 2. version — always informational, never a failure. An available
|
|
160
|
+
// update is not a health problem, so it must not flip `healthy` to
|
|
161
|
+
// false and send an agent chasing a working install. Cache-only:
|
|
162
|
+
// a health check must not wait on the network.
|
|
163
|
+
try {
|
|
164
|
+
const info = await getUpdateInfo({ allowNetwork: false });
|
|
165
|
+
checks.push({
|
|
166
|
+
name: "version",
|
|
167
|
+
pass: true,
|
|
168
|
+
detail: info.updateAvailable
|
|
169
|
+
? `${info.current} (${info.latest} available — \`npm install -g ${PACKAGE_NAME}@latest\`)`
|
|
170
|
+
: `${info.current} (latest known)`,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
checks.push({ name: "version", pass: true, detail: SERVER_VERSION });
|
|
175
|
+
}
|
|
156
176
|
if (!config) {
|
|
157
177
|
// Without credentials there is no session path to inspect and no client
|
|
158
178
|
// to connect: report the remaining checks as failed-with-reason rather
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "A newer version exists" notice.
|
|
3
|
+
*
|
|
4
|
+
* Why it exists: the package is distributed through npx, where the version is
|
|
5
|
+
* resolved on every run, and through `npm i -g`, where it is not. A user who
|
|
6
|
+
* installed globally has no way to learn that a fix shipped unless they think
|
|
7
|
+
* to run `npm outdated -g`. This module prints one line to stderr when the
|
|
8
|
+
* registry has something newer.
|
|
9
|
+
*
|
|
10
|
+
* Design constraints, all deliberate:
|
|
11
|
+
* - stderr only. stdout belongs to the JSON-RPC transport; a stray line there
|
|
12
|
+
* would make the agent drop the server.
|
|
13
|
+
* - never fatal, never blocking. The check runs after the server is already
|
|
14
|
+
* serving; a timeout, an offline machine or a broken proxy just means no
|
|
15
|
+
* notice.
|
|
16
|
+
* - cached for a day, so restarting the client five times does not mean five
|
|
17
|
+
* registry requests.
|
|
18
|
+
* - opt out with TELEGRAM_UPDATE_CHECK=0. This is a local-first tool and a
|
|
19
|
+
* user is entitled to zero outbound traffic that is not Telegram itself.
|
|
20
|
+
*/
|
|
21
|
+
import fs from "node:fs";
|
|
22
|
+
import os from "node:os";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
import { PACKAGE_NAME, SERVER_VERSION, VERSION_IS_KNOWN, compareVersions } from "./version.js";
|
|
25
|
+
const DEFAULT_TIMEOUT_MS = 800;
|
|
26
|
+
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
27
|
+
function enabled() {
|
|
28
|
+
const raw = process.env.TELEGRAM_UPDATE_CHECK;
|
|
29
|
+
if (raw === undefined || raw.trim() === "")
|
|
30
|
+
return true;
|
|
31
|
+
return !/^(0|false|no|off)$/i.test(raw.trim());
|
|
32
|
+
}
|
|
33
|
+
function timeoutMs() {
|
|
34
|
+
const raw = process.env.TELEGRAM_UPDATE_CHECK_TIMEOUT_MS;
|
|
35
|
+
if (!raw)
|
|
36
|
+
return DEFAULT_TIMEOUT_MS;
|
|
37
|
+
const n = Number(raw);
|
|
38
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
39
|
+
return DEFAULT_TIMEOUT_MS;
|
|
40
|
+
return Math.min(n, 10_000);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Cache location. Deliberately independent of `getConfig()`: the notice must
|
|
44
|
+
* work before api_id/api_hash are set, and it must not create the state dir
|
|
45
|
+
* just to write a version number.
|
|
46
|
+
*/
|
|
47
|
+
function cachePath() {
|
|
48
|
+
const configured = process.env.TELEGRAM_STATE_DIR?.trim();
|
|
49
|
+
const base = configured
|
|
50
|
+
? path.resolve(configured.replace(/^~(?=$|[\\/])/, os.homedir()))
|
|
51
|
+
: path.join(os.homedir(), ".telegram-mcp");
|
|
52
|
+
return path.join(base, "update-check.json");
|
|
53
|
+
}
|
|
54
|
+
function readCache() {
|
|
55
|
+
try {
|
|
56
|
+
const parsed = JSON.parse(fs.readFileSync(cachePath(), "utf8"));
|
|
57
|
+
if (typeof parsed.latest !== "string" || typeof parsed.checkedAt !== "string")
|
|
58
|
+
return null;
|
|
59
|
+
if (Date.now() - Date.parse(parsed.checkedAt) > CACHE_TTL_MS)
|
|
60
|
+
return null;
|
|
61
|
+
return { latest: parsed.latest, checkedAt: parsed.checkedAt };
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function writeCache(latest) {
|
|
68
|
+
try {
|
|
69
|
+
const file = cachePath();
|
|
70
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
71
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
72
|
+
fs.writeFileSync(tmp, JSON.stringify({ checkedAt: new Date().toISOString(), latest }, null, 2), { encoding: "utf8", mode: 0o600 });
|
|
73
|
+
fs.renameSync(tmp, file);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
/* a cache we cannot write is not a failure — it just means we ask again */
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** Ask the registry for `dist-tags.latest`. Returns null on any problem. */
|
|
80
|
+
async function fetchLatest() {
|
|
81
|
+
try {
|
|
82
|
+
const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`, {
|
|
83
|
+
headers: { accept: "application/json" },
|
|
84
|
+
signal: AbortSignal.timeout(timeoutMs()),
|
|
85
|
+
});
|
|
86
|
+
if (!res.ok)
|
|
87
|
+
return null;
|
|
88
|
+
const body = (await res.json());
|
|
89
|
+
return typeof body.version === "string" && body.version.trim() ? body.version.trim() : null;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Resolve the current state. `allowNetwork: false` reads only the cache, which
|
|
97
|
+
* is what `telegram_doctor` uses so a health check never waits on the network.
|
|
98
|
+
*/
|
|
99
|
+
export async function getUpdateInfo(opts = {}) {
|
|
100
|
+
const current = SERVER_VERSION;
|
|
101
|
+
// Without a trustworthy local version, "newer" has no meaning: skip the
|
|
102
|
+
// notice rather than telling every user to upgrade to something.
|
|
103
|
+
if (!VERSION_IS_KNOWN)
|
|
104
|
+
return { current, latest: current, updateAvailable: false, source: "unknown" };
|
|
105
|
+
if (!enabled())
|
|
106
|
+
return { current, latest: current, updateAvailable: false, source: "disabled" };
|
|
107
|
+
const cached = readCache();
|
|
108
|
+
if (cached) {
|
|
109
|
+
return {
|
|
110
|
+
current,
|
|
111
|
+
latest: cached.latest,
|
|
112
|
+
updateAvailable: compareVersions(cached.latest, current) > 0,
|
|
113
|
+
source: "cache",
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
if (opts.allowNetwork === false) {
|
|
117
|
+
return { current, latest: current, updateAvailable: false, source: "unknown" };
|
|
118
|
+
}
|
|
119
|
+
const latest = await fetchLatest();
|
|
120
|
+
if (!latest)
|
|
121
|
+
return { current, latest: current, updateAvailable: false, source: "unknown" };
|
|
122
|
+
writeCache(latest);
|
|
123
|
+
return {
|
|
124
|
+
current,
|
|
125
|
+
latest,
|
|
126
|
+
updateAvailable: compareVersions(latest, current) > 0,
|
|
127
|
+
source: "registry",
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/** The single line users see. Shaped to be actionable, not just informative. */
|
|
131
|
+
export function formatNotice(info) {
|
|
132
|
+
const upgrade = process.env.npm_config_global === "true"
|
|
133
|
+
? `npm install -g ${PACKAGE_NAME}@latest`
|
|
134
|
+
: `npx -y ${PACKAGE_NAME}@latest`;
|
|
135
|
+
return (`[telegram-mcp] update available: ${info.current} -> ${info.latest}. ` +
|
|
136
|
+
`Run \`${upgrade}\`, or set TELEGRAM_UPDATE_CHECK=0 to silence this.\n`);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Fire-and-forget: called after the server is already serving, so a slow
|
|
140
|
+
* registry can never delay the first tool call.
|
|
141
|
+
*/
|
|
142
|
+
export function warnIfOutdated() {
|
|
143
|
+
void getUpdateInfo()
|
|
144
|
+
.then((info) => {
|
|
145
|
+
if (info.updateAvailable)
|
|
146
|
+
process.stderr.write(formatNotice(info));
|
|
147
|
+
})
|
|
148
|
+
.catch(() => {
|
|
149
|
+
/* the notice is best-effort by definition */
|
|
150
|
+
});
|
|
151
|
+
}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one place the version number comes from.
|
|
3
|
+
*
|
|
4
|
+
* `package.json` is the source of truth: npm writes the version it publishes,
|
|
5
|
+
* so reading it back means the MCP `serverInfo`, `--version` and the update
|
|
6
|
+
* check can never drift from the artifact that was actually shipped.
|
|
7
|
+
*
|
|
8
|
+
* Resolution walks up from this file, which covers every layout:
|
|
9
|
+
* <root>/dist/version.js (published / built)
|
|
10
|
+
* <root>/version.ts (tsx, dev)
|
|
11
|
+
* <root>/node_modules/@dikacode/telegram-mcp/dist/version.js
|
|
12
|
+
*/
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
export const SERVER_NAME = "telegram-mcp";
|
|
17
|
+
export const PACKAGE_NAME = "@dikacode/telegram-mcp";
|
|
18
|
+
/**
|
|
19
|
+
* Last resort when package.json is unreadable. Deliberately not a plausible
|
|
20
|
+
* version: a guess here would make the update check nag about a release the
|
|
21
|
+
* user may already be running, so an unknown version disables the notice.
|
|
22
|
+
*/
|
|
23
|
+
const FALLBACK_VERSION = "0.0.0-unknown";
|
|
24
|
+
function readVersion() {
|
|
25
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
for (let i = 0; i < 6; i++) {
|
|
27
|
+
try {
|
|
28
|
+
const raw = fs.readFileSync(path.join(dir, "package.json"), "utf8");
|
|
29
|
+
const parsed = JSON.parse(raw);
|
|
30
|
+
if (typeof parsed.version === "string" && parsed.version.trim()) {
|
|
31
|
+
return parsed.version.trim();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
/* keep walking: this directory is not the package root */
|
|
36
|
+
}
|
|
37
|
+
const parent = path.dirname(dir);
|
|
38
|
+
if (parent === dir)
|
|
39
|
+
break;
|
|
40
|
+
dir = parent;
|
|
41
|
+
}
|
|
42
|
+
return FALLBACK_VERSION;
|
|
43
|
+
}
|
|
44
|
+
export const SERVER_VERSION = readVersion();
|
|
45
|
+
/** True when package.json could not be read, so the version is not trustworthy. */
|
|
46
|
+
export const VERSION_IS_KNOWN = SERVER_VERSION !== "0.0.0-unknown";
|
|
47
|
+
/** Dotted-numeric comparison, prerelease-aware, with no dependency. */
|
|
48
|
+
export function compareVersions(a, b) {
|
|
49
|
+
const split = (v) => {
|
|
50
|
+
const cleaned = v.trim().replace(/^v/i, "").split("+")[0];
|
|
51
|
+
const dash = cleaned.indexOf("-");
|
|
52
|
+
const core = dash === -1 ? cleaned : cleaned.slice(0, dash);
|
|
53
|
+
const pre = dash === -1 ? "" : cleaned.slice(dash + 1);
|
|
54
|
+
const nums = core.split(".").map((part) => {
|
|
55
|
+
const n = Number.parseInt(part, 10);
|
|
56
|
+
return Number.isFinite(n) ? n : 0;
|
|
57
|
+
});
|
|
58
|
+
while (nums.length < 3)
|
|
59
|
+
nums.push(0);
|
|
60
|
+
return { nums, pre };
|
|
61
|
+
};
|
|
62
|
+
const left = split(a);
|
|
63
|
+
const right = split(b);
|
|
64
|
+
for (let i = 0; i < 3; i++) {
|
|
65
|
+
if (left.nums[i] !== right.nums[i])
|
|
66
|
+
return left.nums[i] < right.nums[i] ? -1 : 1;
|
|
67
|
+
}
|
|
68
|
+
// Same core: a release outranks any prerelease of it.
|
|
69
|
+
if (!left.pre && right.pre)
|
|
70
|
+
return 1;
|
|
71
|
+
if (left.pre && !right.pre)
|
|
72
|
+
return -1;
|
|
73
|
+
if (left.pre === right.pre)
|
|
74
|
+
return 0;
|
|
75
|
+
return left.pre < right.pre ? -1 : 1;
|
|
76
|
+
}
|
package/package.json
CHANGED