sendkit-mcp 0.1.0
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/dist/db.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +132 -0
- package/dist/oauth-store.d.ts +17 -0
- package/dist/oauth-store.js +85 -0
- package/dist/pkce.d.ts +7 -0
- package/dist/routes/auth.d.ts +2 -0
- package/dist/routes/auth.js +1764 -0
- package/dist/users.d.ts +9 -0
- package/dist/users.js +79 -0
- package/package.json +43 -0
package/dist/db.d.ts
ADDED
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
|
|
7
|
+
// ../core/dist/index.js
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
var telegramMessageInputSchema = z.object({
|
|
10
|
+
chatId: z.string().min(1, "Chat ID is required"),
|
|
11
|
+
message: z.string().min(1, "Message is required")
|
|
12
|
+
});
|
|
13
|
+
var telegramMessageOptionsSchema = telegramMessageInputSchema.extend({
|
|
14
|
+
botToken: z.string().min(1, "Telegram bot token is required")
|
|
15
|
+
});
|
|
16
|
+
var telegramSendMessageRequestSchema = z.object({
|
|
17
|
+
chat_id: z.string().min(1),
|
|
18
|
+
text: z.string().min(1)
|
|
19
|
+
});
|
|
20
|
+
var telegramSendMessageResponseSchema = z.object({
|
|
21
|
+
ok: z.boolean(),
|
|
22
|
+
result: z.object({
|
|
23
|
+
message_id: z.number()
|
|
24
|
+
}).optional(),
|
|
25
|
+
description: z.string().optional()
|
|
26
|
+
});
|
|
27
|
+
var telegramMessageOutputSchema = z.object({
|
|
28
|
+
ok: z.literal(true),
|
|
29
|
+
chatId: z.string(),
|
|
30
|
+
messageId: z.number()
|
|
31
|
+
});
|
|
32
|
+
async function sendTelegramMessage(options) {
|
|
33
|
+
const parsedOptions = telegramMessageOptionsSchema.safeParse(options);
|
|
34
|
+
if (!parsedOptions.success) {
|
|
35
|
+
return {
|
|
36
|
+
success: false,
|
|
37
|
+
error: parsedOptions.error.issues[0]?.message ?? "Invalid input"
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const { chatId, message, botToken } = parsedOptions.data;
|
|
41
|
+
const parsedRequestBody = telegramSendMessageRequestSchema.safeParse({
|
|
42
|
+
chat_id: chatId,
|
|
43
|
+
text: message
|
|
44
|
+
});
|
|
45
|
+
if (!parsedRequestBody.success) {
|
|
46
|
+
return {
|
|
47
|
+
success: false,
|
|
48
|
+
error: parsedRequestBody.error.issues[0]?.message ?? "Invalid request body"
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const res = await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: { "Content-Type": "application/json" },
|
|
55
|
+
body: JSON.stringify(parsedRequestBody.data)
|
|
56
|
+
});
|
|
57
|
+
let rawData;
|
|
58
|
+
try {
|
|
59
|
+
rawData = await res.json();
|
|
60
|
+
} catch {
|
|
61
|
+
return {
|
|
62
|
+
success: false,
|
|
63
|
+
error: "Failed to parse Telegram API response (invalid JSON)."
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const parsedResponse = telegramSendMessageResponseSchema.safeParse(rawData);
|
|
67
|
+
if (!parsedResponse.success) {
|
|
68
|
+
return {
|
|
69
|
+
success: false,
|
|
70
|
+
error: "Unexpected response shape from Telegram API."
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const data = parsedResponse.data;
|
|
74
|
+
if (!res.ok || !data.ok || !data.result) {
|
|
75
|
+
return {
|
|
76
|
+
success: false,
|
|
77
|
+
error: data.description ?? `Telegram API error (HTTP ${res.status})`
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const output = telegramMessageOutputSchema.parse({
|
|
81
|
+
ok: true,
|
|
82
|
+
chatId,
|
|
83
|
+
messageId: data.result.message_id
|
|
84
|
+
});
|
|
85
|
+
return { success: true, data: output };
|
|
86
|
+
} catch (err) {
|
|
87
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
88
|
+
return { success: false, error: `Network error: ${msg}` };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/index.ts
|
|
93
|
+
import { z as z2 } from "zod";
|
|
94
|
+
var server = new McpServer({
|
|
95
|
+
name: "sendkit",
|
|
96
|
+
version: "1.0.0"
|
|
97
|
+
});
|
|
98
|
+
function getTelegramBotToken() {
|
|
99
|
+
const token = process.env.TELEGRAM_BOT_TOKEN;
|
|
100
|
+
if (!token) {
|
|
101
|
+
throw new Error("TELEGRAM_BOT_TOKEN is required. Configure it in your MCP client environment.");
|
|
102
|
+
}
|
|
103
|
+
return token;
|
|
104
|
+
}
|
|
105
|
+
server.tool("send_telegram_message", "Send a message to a Telegram chat using a bot", {
|
|
106
|
+
chatId: z2.string().describe("The Telegram chat ID to send the message to"),
|
|
107
|
+
message: z2.string().describe("The message text to send")
|
|
108
|
+
}, async ({ chatId, message }) => {
|
|
109
|
+
const botToken = getTelegramBotToken();
|
|
110
|
+
const result = await sendTelegramMessage({ chatId, message, botToken });
|
|
111
|
+
if (!result.success) {
|
|
112
|
+
return {
|
|
113
|
+
content: [
|
|
114
|
+
{
|
|
115
|
+
type: "text",
|
|
116
|
+
text: `Failed to send message`
|
|
117
|
+
}
|
|
118
|
+
],
|
|
119
|
+
isError: true
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
content: [
|
|
124
|
+
{
|
|
125
|
+
type: "text",
|
|
126
|
+
text: `Message sent successfully! Message ID: ${result.data.messageId}`
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
var transport = new StdioServerTransport;
|
|
132
|
+
await server.connect(transport);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare function createAuthorizationCode(params: {
|
|
2
|
+
userId: string;
|
|
3
|
+
codeChallenge: string;
|
|
4
|
+
redirectUri: string;
|
|
5
|
+
}): string;
|
|
6
|
+
export declare function consumeAuthorizationCode(code: string): {
|
|
7
|
+
userId: string;
|
|
8
|
+
codeChallenge: string;
|
|
9
|
+
redirectUri: string;
|
|
10
|
+
} | null;
|
|
11
|
+
export declare function createAccessToken(userId: string): {
|
|
12
|
+
token: string;
|
|
13
|
+
expiresIn: number;
|
|
14
|
+
};
|
|
15
|
+
export declare function validateAccessToken(token: string): {
|
|
16
|
+
userId: string;
|
|
17
|
+
} | null;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// src/oauth-store.ts
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
|
|
4
|
+
// src/db.ts
|
|
5
|
+
import { Database } from "bun:sqlite";
|
|
6
|
+
var db = new Database("sendkit.db", { create: true });
|
|
7
|
+
db.exec("PRAGMA journal_mode = WAL;");
|
|
8
|
+
db.exec(`
|
|
9
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
10
|
+
id TEXT PRIMARY KEY,
|
|
11
|
+
username TEXT UNIQUE NOT NULL,
|
|
12
|
+
password_hash TEXT NOT NULL,
|
|
13
|
+
telegram_bot_token TEXT,
|
|
14
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
15
|
+
);
|
|
16
|
+
`);
|
|
17
|
+
db.exec(`
|
|
18
|
+
CREATE TABLE IF NOT EXISTS authorization_codes (
|
|
19
|
+
code TEXT PRIMARY KEY,
|
|
20
|
+
user_id TEXT NOT NULL,
|
|
21
|
+
code_challenge TEXT NOT NULL,
|
|
22
|
+
redirect_uri TEXT NOT NULL,
|
|
23
|
+
expires_at INTEGER NOT NULL,
|
|
24
|
+
used INTEGER NOT NULL DEFAULT 0,
|
|
25
|
+
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
26
|
+
);
|
|
27
|
+
`);
|
|
28
|
+
db.exec(`
|
|
29
|
+
CREATE TABLE IF NOT EXISTS access_tokens (
|
|
30
|
+
token TEXT PRIMARY KEY,
|
|
31
|
+
user_id TEXT NOT NULL,
|
|
32
|
+
expires_at INTEGER NOT NULL,
|
|
33
|
+
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
34
|
+
);
|
|
35
|
+
`);
|
|
36
|
+
|
|
37
|
+
// src/oauth-store.ts
|
|
38
|
+
var AUTH_CODE_TTL_MS = 5 * 60 * 1000;
|
|
39
|
+
var ACCESS_TOKEN_TTL_MS = 60 * 60 * 1000;
|
|
40
|
+
function generateSecureToken() {
|
|
41
|
+
return randomBytes(32).toString("hex");
|
|
42
|
+
}
|
|
43
|
+
function createAuthorizationCode(params) {
|
|
44
|
+
const code = generateSecureToken();
|
|
45
|
+
const expiresAt = Date.now() + AUTH_CODE_TTL_MS;
|
|
46
|
+
db.query(`INSERT INTO authorization_codes (code, user_id, code_challenge, redirect_uri, expires_at)
|
|
47
|
+
VALUES (?, ?, ?, ?, ?)`).run(code, params.userId, params.codeChallenge, params.redirectUri, expiresAt);
|
|
48
|
+
return code;
|
|
49
|
+
}
|
|
50
|
+
function consumeAuthorizationCode(code) {
|
|
51
|
+
const row = db.query(`SELECT user_id, code_challenge, redirect_uri, expires_at, used
|
|
52
|
+
FROM authorization_codes WHERE code = ?`).get(code);
|
|
53
|
+
if (!row)
|
|
54
|
+
return null;
|
|
55
|
+
if (row.used)
|
|
56
|
+
return null;
|
|
57
|
+
if (Date.now() > row.expires_at)
|
|
58
|
+
return null;
|
|
59
|
+
db.query("UPDATE authorization_codes SET used = 1 WHERE code = ?").run(code);
|
|
60
|
+
return {
|
|
61
|
+
userId: row.user_id,
|
|
62
|
+
codeChallenge: row.code_challenge,
|
|
63
|
+
redirectUri: row.redirect_uri
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function createAccessToken(userId) {
|
|
67
|
+
const token = generateSecureToken();
|
|
68
|
+
const expiresAt = Date.now() + ACCESS_TOKEN_TTL_MS;
|
|
69
|
+
db.query("INSERT INTO access_tokens (token, user_id, expires_at) VALUES (?, ?, ?)").run(token, userId, expiresAt);
|
|
70
|
+
return { token, expiresIn: ACCESS_TOKEN_TTL_MS / 1000 };
|
|
71
|
+
}
|
|
72
|
+
function validateAccessToken(token) {
|
|
73
|
+
const row = db.query("SELECT user_id, expires_at FROM access_tokens WHERE token = ?").get(token);
|
|
74
|
+
if (!row)
|
|
75
|
+
return null;
|
|
76
|
+
if (Date.now() > row.expires_at)
|
|
77
|
+
return null;
|
|
78
|
+
return { userId: row.user_id };
|
|
79
|
+
}
|
|
80
|
+
export {
|
|
81
|
+
validateAccessToken,
|
|
82
|
+
createAuthorizationCode,
|
|
83
|
+
createAccessToken,
|
|
84
|
+
consumeAuthorizationCode
|
|
85
|
+
};
|
package/dist/pkce.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hashes a code_verifier the same way a spec-compliant client does,
|
|
3
|
+
* so we can compare it against the code_challenge stored earlier.
|
|
4
|
+
*
|
|
5
|
+
* Used in: /token endpoint, right before issuing an access token.
|
|
6
|
+
*/
|
|
7
|
+
export declare function verifyPkce(codeVerifier: string, storedCodeChallenge: string): Promise<boolean>;
|