teletype-mcp-server 0.1.0 → 0.1.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.
Files changed (64) hide show
  1. package/.env.example +7 -0
  2. package/LAUNCHGUIDE.md +51 -0
  3. package/README-ru.md +49 -11
  4. package/README.md +68 -30
  5. package/dist/config.d.ts +4 -0
  6. package/dist/config.js +17 -0
  7. package/dist/config.js.map +1 -1
  8. package/dist/http.js +69 -1
  9. package/dist/http.js.map +1 -1
  10. package/dist/landing-page.d.ts +1 -0
  11. package/dist/landing-page.js +166 -0
  12. package/dist/landing-page.js.map +1 -0
  13. package/dist/oauth-page.d.ts +1 -0
  14. package/dist/oauth-page.js +242 -0
  15. package/dist/oauth-page.js.map +1 -0
  16. package/dist/oauth-store.d.ts +48 -0
  17. package/dist/oauth-store.js +131 -0
  18. package/dist/oauth-store.js.map +1 -0
  19. package/dist/oauth.d.ts +4 -0
  20. package/dist/oauth.js +382 -0
  21. package/dist/oauth.js.map +1 -0
  22. package/dist/request-context.d.ts +2 -0
  23. package/dist/request-context.js.map +1 -1
  24. package/dist/tool-policy.js +7 -0
  25. package/dist/tool-policy.js.map +1 -1
  26. package/docs/CLIENTS.md +651 -27
  27. package/docs/CONTRIBUTING.md +4 -2
  28. package/docs/OAUTH.md +54 -0
  29. package/docs/SECURITY.md +3 -1
  30. package/docs/ru/CLIENTS.md +651 -27
  31. package/docs/ru/CONTRIBUTING.md +4 -2
  32. package/docs/ru/OAUTH.md +54 -0
  33. package/docs/ru/SECURITY.md +3 -1
  34. package/examples/clients/goose-stdio.yaml +13 -0
  35. package/examples/clients/openclaw.json +13 -0
  36. package/gemini-extension.json +22 -0
  37. package/lhm.plugin.json +1408 -0
  38. package/llms-install.md +17 -0
  39. package/package.json +9 -4
  40. package/plugin/.claude-plugin/plugin.json +37 -0
  41. package/plugin/.cursor-plugin/plugin.json +34 -0
  42. package/plugin/LICENSE +21 -0
  43. package/plugin/README.md +48 -0
  44. package/plugin/assets/README.md +12 -0
  45. package/plugin/assets/consent.js +32 -0
  46. package/plugin/assets/fonts/OFL.txt +93 -0
  47. package/plugin/assets/fonts/manrope.woff2 +0 -0
  48. package/plugin/assets/icon-400.png +0 -0
  49. package/plugin/assets/icon.png +0 -0
  50. package/plugin/assets/icon.svg +15 -0
  51. package/plugin/assets/landing.css +442 -0
  52. package/plugin/assets/landing.js +85 -0
  53. package/plugin/assets/logo.svg +16 -0
  54. package/plugin/commands/client-summary.md +14 -0
  55. package/plugin/commands/draft-reply.md +13 -0
  56. package/plugin/commands/escalate-issue.md +15 -0
  57. package/plugin/commands/shift-handover.md +10 -0
  58. package/plugin/commands/triage-inbox.md +13 -0
  59. package/plugin/mcp.json +10 -0
  60. package/plugin/plugin.json +15 -0
  61. package/plugin/skills/teletype-support/SKILL.md +3 -1
  62. package/plugin/skills/teletype-support/references/installation.md +31 -0
  63. package/server.json +18 -4
  64. package/smithery.yaml +3 -2
@@ -0,0 +1,166 @@
1
+ const repository = "https://github.com/Teletype-App/teletype-mcp-server";
2
+ const messages = {
3
+ ru: {
4
+ description: "Подключите Teletype к Claude, Cursor или Codex. Работайте с переписками и данными клиентов обычными запросами.",
5
+ examplesTitle: "Что можно поручить ассистенту",
6
+ examples: [
7
+ {
8
+ title: "Разобрать очередь",
9
+ prompt: "Найди диалоги без ответа и кратко опиши, что нужно каждому клиенту.",
10
+ },
11
+ {
12
+ title: "Собрать контекст",
13
+ prompt: "Посмотри последние обращения клиента и составь резюме переписки.",
14
+ },
15
+ {
16
+ title: "Подготовить ответ",
17
+ prompt: "Подготовь ответ клиенту. Перед отправкой покажи мне текст.",
18
+ },
19
+ ],
20
+ setupTitle: "Начните с вашего проекта",
21
+ setupDescription: "Нужен проект Teletype с доступом к Public API.",
22
+ setupToken: "Возьмите токен в настройках проекта.",
23
+ setupGuide: "Откройте инструкцию для своего приложения на GitHub.",
24
+ setupConnect: "Добавьте MCP-сервер в приложение и подключите проект по инструкции.",
25
+ support: "Помощь",
26
+ privacy: "Политика конфиденциальности",
27
+ privacyUrl: "https://teletype.app/files/policy.pdf",
28
+ address: "Адрес MCP-сервера",
29
+ copy: "Скопировать",
30
+ copied: "Скопировано",
31
+ copying: "Копирование…",
32
+ copyFallback: "Скопируйте выделенный адрес.",
33
+ connect: "Как подключить",
34
+ website: "Сайт Teletype",
35
+ links: "Подключение и исходный код",
36
+ newTab: " (откроется в новой вкладке)",
37
+ languageChanged: "Язык страницы: русский.",
38
+ guide: `${repository}/blob/main/docs/ru/CLIENTS.md`,
39
+ },
40
+ en: {
41
+ description: "Connect Teletype to Claude, Cursor, or Codex. Work with conversations and customer records using everyday requests.",
42
+ examplesTitle: "What you can ask your assistant",
43
+ examples: [
44
+ {
45
+ title: "Triage the inbox",
46
+ prompt: "Find unanswered conversations and summarize what each customer needs.",
47
+ },
48
+ {
49
+ title: "Gather context",
50
+ prompt: "Read the customer’s recent conversations and summarize their history.",
51
+ },
52
+ {
53
+ title: "Draft a reply",
54
+ prompt: "Draft a reply to the customer. Show me the text before sending it.",
55
+ },
56
+ ],
57
+ setupTitle: "Start with your project",
58
+ setupDescription: "You need a Teletype project with Public API access.",
59
+ setupToken: "Get a token from your project settings.",
60
+ setupGuide: "Open the GitHub guide for your app.",
61
+ setupConnect: "Add the MCP server to your app and connect your project using the guide.",
62
+ support: "Support",
63
+ privacy: "Privacy policy",
64
+ privacyUrl: "https://teletype.app/android/policy.html",
65
+ address: "MCP server URL",
66
+ copy: "Copy URL",
67
+ copied: "Copied",
68
+ copying: "Copying…",
69
+ copyFallback: "Copy the selected URL.",
70
+ connect: "How to connect",
71
+ website: "Teletype website",
72
+ links: "Connection guide and source code",
73
+ newTab: " (opens in a new tab)",
74
+ languageChanged: "Page language: English.",
75
+ guide: `${repository}/blob/main/docs/CLIENTS.md`,
76
+ },
77
+ };
78
+ function escapeHtml(value) {
79
+ return value.replace(/[&<>"']/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[char] ?? char);
80
+ }
81
+ export function landingPage(publicBaseUrl, locale) {
82
+ const copy = messages[locale];
83
+ const endpoint = escapeHtml(new URL("/mcp", publicBaseUrl).href);
84
+ const canonical = escapeHtml(new URL("/", publicBaseUrl).href);
85
+ const external = (href, label, className = "", key) => `<a class="${className}" href="${href}"${href === copy.guide ? ' data-i18n-href="guide"' : href === copy.privacyUrl ? ' data-i18n-href="privacyUrl"' : ""} target="_blank" rel="noopener noreferrer">${key ? `<span data-i18n="${key}">${escapeHtml(label)}</span>` : escapeHtml(label)}<span class="sr-only" data-i18n="newTab">${copy.newTab}</span></a>`;
86
+ return `<!doctype html>
87
+ <html lang="${locale}">
88
+ <head>
89
+ <meta charset="utf-8">
90
+ <meta name="viewport" content="width=device-width,initial-scale=1">
91
+ <title>Teletype MCP</title>
92
+ <meta name="description" content="${copy.description}">
93
+ <meta property="og:title" content="Teletype MCP">
94
+ <meta property="og:description" content="${copy.description}">
95
+ <meta property="og:type" content="website">
96
+ <meta property="og:url" content="${canonical}">
97
+ <meta property="og:image" content="${escapeHtml(new URL("/assets/icon.png", publicBaseUrl).href)}">
98
+ <link rel="canonical" href="${canonical}">
99
+ <link rel="icon" href="/assets/icon.svg" type="image/svg+xml">
100
+ <link rel="stylesheet" href="/assets/landing.css">
101
+ <link rel="preload" href="/assets/fonts/manrope.woff2" as="font" type="font/woff2" crossorigin>
102
+ <script src="/assets/landing.js" defer></script>
103
+ </head>
104
+ <body>
105
+ <div class="page" data-translations="${escapeHtml(JSON.stringify(messages))}">
106
+ <header class="page-header">
107
+ <a class="brand" href="https://teletype.app" target="_blank" rel="noopener noreferrer" aria-label="${copy.website}${copy.newTab}">
108
+ <img src="/assets/logo.svg" width="184" height="42" alt="Teletype App">
109
+ </a>
110
+ <nav class="languages" aria-label="Язык / Language" hidden>
111
+ <button type="button" lang="ru" data-locale="ru" aria-pressed="${locale === "ru"}">Русский</button>
112
+ <button type="button" lang="en" data-locale="en" aria-pressed="${locale === "en"}">English</button>
113
+ </nav>
114
+ </header>
115
+ <span class="sr-only" role="status" id="language-status"></span>
116
+ <main class="landing" aria-labelledby="page-title">
117
+ <h1 id="page-title">Teletype MCP</h1>
118
+ <p class="description" data-i18n="description">${copy.description}</p>
119
+ <div class="connection">
120
+ <label for="mcp-url" data-i18n="address">${copy.address}</label>
121
+ <div class="address">
122
+ <input id="mcp-url" type="text" value="${endpoint}" readonly spellcheck="false" autocapitalize="none">
123
+ <button type="button" id="copy-url" title="${copy.copy}" data-copy="${copy.copy}" data-copied="${copy.copied}" data-copying="${copy.copying}" data-fallback="${copy.copyFallback}" hidden>
124
+ <svg class="copy-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
125
+ <g class="copy-document"><rect x="8" y="8" width="12" height="12" rx="2"></rect><path d="M16 8V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h3"></path></g>
126
+ <path class="copy-check" d="m5 12 4 4L19 6"></path>
127
+ </svg>
128
+ <span class="copy-label">${copy.copy}</span>
129
+ </button>
130
+ </div>
131
+ <span class="sr-only" role="status" id="copy-status"></span>
132
+ </div>
133
+ <nav class="actions" aria-label="${copy.links}">
134
+ ${external(copy.guide, copy.connect, "button", "connect")}
135
+ ${external(repository, "GitHub", "source")}
136
+ </nav>
137
+ <section class="examples" aria-labelledby="examples-title">
138
+ <h2 id="examples-title" data-i18n="examplesTitle">${copy.examplesTitle}</h2>
139
+ <dl>
140
+ ${copy.examples
141
+ .map((example, index) => `<div class="example">
142
+ <dt data-i18n="examples.${index}.title">${example.title}</dt>
143
+ <dd><q data-i18n="examples.${index}.prompt">${example.prompt}</q></dd>
144
+ </div>`)
145
+ .join("")}
146
+ </dl>
147
+ </section>
148
+ <section class="setup" aria-labelledby="setup-title">
149
+ <h2 id="setup-title" data-i18n="setupTitle">${copy.setupTitle}</h2>
150
+ <p data-i18n="setupDescription">${copy.setupDescription}</p>
151
+ <ol>
152
+ <li>${external("https://panel.teletype.app/settings/public-api", copy.setupToken, "", "setupToken")}</li>
153
+ <li>${external(copy.guide, copy.setupGuide, "", "setupGuide")}</li>
154
+ <li data-i18n="setupConnect">${copy.setupConnect}</li>
155
+ </ol>
156
+ </section>
157
+ </main>
158
+ <footer>
159
+ ${external("https://help.teletype.app", copy.support, "", "support")}
160
+ ${external(copy.privacyUrl, copy.privacy, "", "privacy")}
161
+ </footer>
162
+ </div>
163
+ </body>
164
+ </html>`;
165
+ }
166
+ //# sourceMappingURL=landing-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"landing-page.js","sourceRoot":"","sources":["../src/landing-page.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function consentPage(clientName: string, destination: string, id: string, scopes: string[], locale?: "ru" | "en"): string;
@@ -0,0 +1,242 @@
1
+ const externalLinkIcon = `<svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M14 4h6v6M20 4 10 14M10 4H5a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-5"/></svg>`;
2
+ function escapeHtml(value) {
3
+ return value.replace(/[&<>"']/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[char] ?? char);
4
+ }
5
+ const messages = {
6
+ en: {
7
+ title: "Connect Teletype",
8
+ clientDefault: "MCP client",
9
+ website: "Teletype website (opens in a new tab)",
10
+ requester: "wants to connect to your Teletype project.",
11
+ destination: "After connecting, you’ll return to",
12
+ access: "The app can read conversations and customer profiles.",
13
+ tokenLabel: "Project token",
14
+ tokenPlaceholder: "Paste your Public API token",
15
+ tokenHint: "Copy the token from your project’s Public API settings.",
16
+ tokenHelp: "Where to get a token",
17
+ permission: "Also allow the app to send messages and edit customer records and project settings",
18
+ lifetimeOffline: "Access renews automatically. Disconnect Teletype in the app to revoke access.",
19
+ lifetime: "This connection lasts up to one hour.",
20
+ connect: "Connect project",
21
+ cancel: "Cancel",
22
+ helpNavigation: "Help and privacy",
23
+ support: "Support",
24
+ privacy: "Privacy policy",
25
+ privacyUrl: "https://teletype.app/android/policy.html",
26
+ newTab: " (opens in a new tab)",
27
+ languageChanged: "Page language: English.",
28
+ },
29
+ ru: {
30
+ title: "Подключить Teletype",
31
+ clientDefault: "MCP-клиент",
32
+ website: "Сайт Teletype (откроется в новой вкладке)",
33
+ requester: "хочет подключиться к вашему проекту Teletype.",
34
+ destination: "После подключения вы вернётесь на",
35
+ access: "Приложение сможет читать переписки и данные клиентов.",
36
+ tokenLabel: "Токен проекта",
37
+ tokenPlaceholder: "Вставьте токен Public API",
38
+ tokenHint: "Скопируйте токен из настроек Public API вашего проекта.",
39
+ tokenHelp: "Где взять токен",
40
+ permission: "Также разрешить отправлять сообщения, менять данные клиентов и настройки проекта",
41
+ lifetimeOffline: "Доступ продлевается автоматически. Чтобы отозвать его, отключите Teletype в приложении.",
42
+ lifetime: "Подключение действует до часа.",
43
+ connect: "Подключить проект",
44
+ cancel: "Отмена",
45
+ helpNavigation: "Помощь и конфиденциальность",
46
+ support: "Помощь",
47
+ privacy: "Политика конфиденциальности",
48
+ privacyUrl: "https://teletype.app/files/policy.pdf",
49
+ newTab: " (откроется в новой вкладке)",
50
+ languageChanged: "Язык страницы: русский.",
51
+ },
52
+ };
53
+ const styles = `
54
+ @font-face {
55
+ font-family: Manrope;
56
+ src: url('/oauth/assets/fonts/manrope.woff2') format('woff2');
57
+ font-weight: 200 800;
58
+ font-style: normal;
59
+ font-display: swap;
60
+ }
61
+ :root {
62
+ color-scheme: light;
63
+ --surface: #fff;
64
+ --background: #f8f8f8;
65
+ --ink: #1c1c1c;
66
+ --muted: #555;
67
+ --line: #d8d8d8;
68
+ --blue: #006de5;
69
+ --blue-hover: #005fc9;
70
+ --focus: #0079ff;
71
+ font-family: Manrope, sans-serif;
72
+ color: var(--ink);
73
+ background: var(--background);
74
+ font-synthesis: none;
75
+ }
76
+ * { box-sizing: border-box; }
77
+ body { margin: 0; font-size: 15px; line-height: 1.6; }
78
+ ::selection { color: var(--ink); background: #dcecff; }
79
+ button, input { font: inherit; }
80
+ button, a, input { -webkit-tap-highlight-color: transparent; }
81
+ :focus-visible { outline: 3px solid var(--focus); outline-offset: 4px; }
82
+ .page {
83
+ min-height: 100svh;
84
+ display: flex;
85
+ flex-direction: column;
86
+ align-items: center;
87
+ justify-content: center;
88
+ padding: 40px 24px 24px;
89
+ }
90
+ .page-header { display: flex; align-items: center; justify-content: space-between; gap: 16px; width: 100%; max-width: 520px; margin-bottom: 24px; }
91
+ .brand { display: inline-flex; align-items: center; min-height: 44px; }
92
+ .brand img { display: block; width: 184px; height: 42px; }
93
+ .languages { display: inline-flex; gap: 2px; padding: 3px; border: 1px solid var(--line); border-radius: 10px; }
94
+ .languages button { min-height: 44px; padding: 8px 10px; border: 0; border-radius: 6px; background: transparent; color: var(--muted); font-size: 13px; cursor: pointer; }
95
+ .languages button:hover { color: var(--ink); background: #eee; }
96
+ .languages button[aria-pressed="true"] { color: var(--blue-hover); background: var(--surface); font-weight: 700; box-shadow: 0 1px 4px rgb(0 0 0 / 8%); }
97
+ .languages button:disabled { cursor: default; }
98
+ .consent {
99
+ width: 100%;
100
+ max-width: 520px;
101
+ padding: 36px 40px;
102
+ background: var(--surface);
103
+ border-radius: 16px;
104
+ box-shadow: 0 12px 48px rgb(0 0 0 / 6%);
105
+ }
106
+ h1 { font-size: 28px; font-weight: 750; letter-spacing: -.035em; line-height: 1.25; margin: 0 0 14px; }
107
+ p { margin: 0; overflow-wrap: anywhere; }
108
+ strong { font-weight: 700; }
109
+ .requester { margin-bottom: 6px; }
110
+ .destination { color: var(--muted); font-size: 13px; margin-bottom: 24px; }
111
+ .access { margin-bottom: 24px; }
112
+ .token-heading { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0 12px; margin-bottom: 8px; }
113
+ .token-label { font-size: 14px; font-weight: 700; }
114
+ .token-help { display: inline-flex; align-items: center; gap: 5px; min-height: 44px; color: var(--blue-hover); font-size: 13px; text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; }
115
+ .token-help:hover { color: #004ca3; }
116
+ .token-help svg { flex: 0 0 14px; }
117
+ .token-input {
118
+ display: block;
119
+ width: 100%;
120
+ min-height: 50px;
121
+ font-size: 16px;
122
+ padding: 12px 14px;
123
+ border: 1px solid #a8a8a8;
124
+ border-radius: 8px;
125
+ color: var(--ink);
126
+ background: var(--surface);
127
+ caret-color: var(--blue);
128
+ transition: border-color 160ms ease-out;
129
+ }
130
+ .token-input::placeholder { color: #666; opacity: 1; }
131
+ .token-input:hover { border-color: #777; }
132
+ .token-input:focus-visible { border-color: var(--blue); outline-offset: 3px; }
133
+ .token-input:user-invalid { border-color: #b42318; }
134
+ .token-hint { color: var(--muted); font-size: 13px; line-height: 1.65; margin-top: 8px; }
135
+ .permission {
136
+ display: flex;
137
+ align-items: flex-start;
138
+ gap: 12px;
139
+ padding: 18px 0;
140
+ margin-top: 24px;
141
+ border-top: 1px solid var(--line);
142
+ border-bottom: 1px solid var(--line);
143
+ cursor: pointer;
144
+ font-size: 14px;
145
+ line-height: 1.65;
146
+ }
147
+ .permission input { width: 19px; height: 19px; flex: 0 0 19px; margin: 2px 0 0; accent-color: var(--blue); cursor: pointer; }
148
+ .lifetime { font-size: 13px; color: var(--muted); line-height: 1.65; margin-top: 20px; }
149
+ .actions { display: grid; grid-template-columns: 1fr; gap: 8px; margin-top: 28px; }
150
+ .button {
151
+ min-height: 48px;
152
+ padding: 12px 20px;
153
+ border: 0;
154
+ border-radius: 8px;
155
+ cursor: pointer;
156
+ font-size: 15px;
157
+ font-weight: 700;
158
+ transition: background-color 160ms ease-out, color 160ms ease-out;
159
+ }
160
+ .button-primary { background: var(--blue); color: #fff; }
161
+ .button-primary:hover { background: var(--blue-hover); }
162
+ .button-primary:active { background: #0052b0; }
163
+ .button-secondary { background: transparent; color: var(--muted); }
164
+ .button-secondary:hover { background: #f3f3f3; color: var(--ink); }
165
+ .button-secondary:active { background: #eaeaea; }
166
+ .button:disabled { background: #eaeaea; color: #666; cursor: not-allowed; }
167
+ .links { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px 24px; margin-top: 16px; }
168
+ .links a { display: inline-flex; align-items: center; gap: 6px; min-height: 44px; color: var(--muted); font-size: 13px; text-decoration: none; text-underline-offset: 4px; }
169
+ .links a:hover { color: var(--blue-hover); text-decoration: underline; }
170
+ .links svg { flex: 0 0 14px; }
171
+ .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
172
+ @media (max-width: 560px) {
173
+ body { font-size: 16px; }
174
+ .page { justify-content: flex-start; padding: 24px 16px 16px; }
175
+ .page-header { margin-bottom: 20px; gap: 8px; }
176
+ .consent { padding: 28px 24px; border-radius: 12px; }
177
+ .permission { font-size: 15px; }
178
+ h1 { font-size: 26px; }
179
+ }
180
+ @media (max-width: 380px) {
181
+ .page-header { flex-direction: column; gap: 12px; }
182
+ }
183
+ @media (prefers-reduced-motion: reduce) {
184
+ *, *::before, *::after { transition: none !important; scroll-behavior: auto !important; }
185
+ }
186
+ `;
187
+ export function consentPage(clientName, destination, id, scopes, locale = "en") {
188
+ const copy = messages[locale];
189
+ const text = (key) => `<span data-i18n="${key}">${escapeHtml(copy[key])}</span>`;
190
+ const lifetime = scopes.includes("offline_access") ? "lifetimeOffline" : "lifetime";
191
+ return `<!doctype html>
192
+ <html lang="${locale}">
193
+ <head>
194
+ <meta charset="utf-8">
195
+ <meta name="viewport" content="width=device-width,initial-scale=1">
196
+ <title data-i18n="title">${copy.title}</title>
197
+ <link rel="icon" href="/oauth/assets/icon.svg" type="image/svg+xml">
198
+ <style>${styles}</style>
199
+ <script src="/oauth/assets/consent.js" defer></script>
200
+ </head>
201
+ <body>
202
+ <div class="page" data-messages="${escapeHtml(JSON.stringify(messages))}">
203
+ <header class="page-header">
204
+ <a class="brand" href="https://teletype.app" target="_blank" rel="noopener noreferrer" data-i18n-label="website" aria-label="${escapeHtml(copy.website)}">
205
+ <img src="/oauth/assets/logo.svg" width="184" height="42" alt="Teletype App">
206
+ </a>
207
+ <div class="languages" role="group" aria-label="Язык / Language">
208
+ <button type="button" lang="ru" data-locale="ru" aria-pressed="${locale === "ru"}" disabled>Русский</button>
209
+ <button type="button" lang="en" data-locale="en" aria-pressed="${locale === "en"}" disabled>English</button>
210
+ </div>
211
+ <span class="sr-only" role="status" id="language-status"></span>
212
+ </header>
213
+ <main class="consent" aria-labelledby="connection-title">
214
+ <h1 id="connection-title" data-i18n="title">${copy.title}</h1>
215
+ <p class="requester"><strong>${clientName ? escapeHtml(clientName) : text("clientDefault")}</strong> ${text("requester")}</p>
216
+ <p class="destination">${text("destination")} ${escapeHtml(destination)}</p>
217
+ <p class="access">${text("access")}</p>
218
+ <form action="/oauth/consent" method="post">
219
+ <input type="hidden" name="request_id" value="${escapeHtml(id)}">
220
+ <div class="token-heading">
221
+ <label class="token-label" for="api-token">${text("tokenLabel")}</label>
222
+ <a class="token-help" href="https://panel.teletype.app/settings/public-api" target="_blank" rel="noopener noreferrer">${text("tokenHelp")} ${externalLinkIcon}<span class="sr-only" data-i18n="newTab">${copy.newTab}</span></a>
223
+ </div>
224
+ <input class="token-input" id="api-token" name="api_token" type="password" required maxlength="2048" autocomplete="off" spellcheck="false" autocapitalize="none" aria-describedby="token-hint" data-i18n-placeholder="tokenPlaceholder" placeholder="${escapeHtml(copy.tokenPlaceholder)}">
225
+ <p class="token-hint" id="token-hint">${text("tokenHint")}</p>
226
+ ${scopes.includes("write") ? `<label class="permission"><input type="checkbox" name="allow_write" value="yes">${text("permission")}</label>` : ""}
227
+ <p class="lifetime">${text(lifetime)}</p>
228
+ <div class="actions">
229
+ <button class="button button-primary" type="submit" name="action" value="connect">${text("connect")}</button>
230
+ <button class="button button-secondary" type="submit" name="action" value="cancel" formnovalidate>${text("cancel")}</button>
231
+ </div>
232
+ </form>
233
+ </main>
234
+ <nav class="links" data-i18n-label="helpNavigation" aria-label="${escapeHtml(copy.helpNavigation)}">
235
+ <a href="https://help.teletype.app" target="_blank" rel="noopener noreferrer">${text("support")} ${externalLinkIcon}<span class="sr-only" data-i18n="newTab">${copy.newTab}</span></a>
236
+ <a href="${copy.privacyUrl}" data-i18n-href="privacyUrl" target="_blank" rel="noopener noreferrer">${text("privacy")} ${externalLinkIcon}<span class="sr-only" data-i18n="newTab">${copy.newTab}</span></a>
237
+ </nav>
238
+ </div>
239
+ </body>
240
+ </html>`;
241
+ }
242
+ //# sourceMappingURL=oauth-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-page.js","sourceRoot":"","sources":["../src/oauth-page.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ import type { OAuthClientInformationFull } from "@modelcontextprotocol/sdk/shared/auth.js";
2
+ interface Consent {
3
+ clientId: string;
4
+ redirectUri: string;
5
+ challenge: string;
6
+ state?: string;
7
+ scopes: string[];
8
+ cookieHash: string;
9
+ expiresAt: number;
10
+ }
11
+ interface Grant {
12
+ clientId: string;
13
+ apiToken: string;
14
+ scopes: string[];
15
+ expiresAt: number;
16
+ currentRefresh?: string;
17
+ }
18
+ interface Records {
19
+ client: OAuthClientInformationFull;
20
+ consent: Consent;
21
+ code: {
22
+ consent: Consent;
23
+ grantId: string;
24
+ expiresAt: number;
25
+ };
26
+ grant: Grant;
27
+ access: {
28
+ grantId: string;
29
+ expiresAt: number;
30
+ };
31
+ }
32
+ type Kind = keyof Records;
33
+ export declare class OAuthStore {
34
+ private readonly db;
35
+ private readonly key;
36
+ constructor(file: string, encryptionKey: string, resource: string);
37
+ private encrypt;
38
+ private decrypt;
39
+ get<K extends Kind>(kind: K, id: string): Records[K] | undefined;
40
+ put<K extends Kind>(kind: K, id: string, value: Records[K], expiresAt: number, grantId?: string): void;
41
+ remove(kind: Kind, id: string): void;
42
+ revokeGrant(id: string): void;
43
+ hasPersistentGrant(clientId: string): boolean;
44
+ count(kind: Kind): number;
45
+ transaction<T>(operation: () => T): T;
46
+ close(): void;
47
+ }
48
+ export {};
@@ -0,0 +1,131 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
2
+ import { chmodSync, mkdirSync } from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import { dirname, resolve } from "node:path";
5
+ // Short synchronous transactions serialize code exchange, refresh rotation and
6
+ // revocation across connections. No transaction remains open during an API call.
7
+ export class OAuthStore {
8
+ db;
9
+ key;
10
+ constructor(file, encryptionKey, resource) {
11
+ this.key = Buffer.from(encryptionKey, "hex");
12
+ if (this.key.length !== 32)
13
+ throw new Error("OAuth encryption key must contain 32 bytes.");
14
+ let sqlite;
15
+ try {
16
+ sqlite = createRequire(import.meta.url)("node:sqlite");
17
+ }
18
+ catch {
19
+ throw new Error("OAuth requires Node.js 22.13+ or 24.x with node:sqlite support.");
20
+ }
21
+ const path = resolve(file);
22
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
23
+ this.db = new sqlite.DatabaseSync(path);
24
+ try {
25
+ chmodSync(path, 0o600);
26
+ this.db.exec("PRAGMA busy_timeout=5000; PRAGMA journal_mode=DELETE; PRAGMA synchronous=FULL;");
27
+ const version = this.db.prepare("PRAGMA user_version").get()?.user_version;
28
+ if (version !== 0 && version !== 1)
29
+ throw new Error("Unsupported OAuth database version.");
30
+ this.db.exec(`
31
+ CREATE TABLE IF NOT EXISTS oauth_records (
32
+ kind TEXT NOT NULL, id TEXT NOT NULL, payload BLOB NOT NULL,
33
+ expires_at INTEGER NOT NULL, grant_id TEXT,
34
+ PRIMARY KEY (kind, id)
35
+ );
36
+ CREATE INDEX IF NOT EXISTS oauth_records_expiry ON oauth_records(expires_at);
37
+ CREATE INDEX IF NOT EXISTS oauth_records_grant ON oauth_records(grant_id);
38
+ CREATE TABLE IF NOT EXISTS oauth_meta (id TEXT PRIMARY KEY, payload BLOB NOT NULL);
39
+ PRAGMA user_version=1;
40
+ `);
41
+ this.transaction(() => {
42
+ const sentinel = this.db
43
+ .prepare("SELECT payload FROM oauth_meta WHERE id='key-check'")
44
+ .get();
45
+ if (sentinel) {
46
+ if (this.decrypt(sentinel.payload, "key-check") !==
47
+ "teletype-oauth-v1:" + resource)
48
+ throw new Error("Invalid OAuth key.");
49
+ }
50
+ else {
51
+ this.db
52
+ .prepare("INSERT INTO oauth_meta(id,payload) VALUES('key-check',?)")
53
+ .run(this.encrypt("teletype-oauth-v1:" + resource, "key-check"));
54
+ }
55
+ });
56
+ }
57
+ catch {
58
+ this.db.close();
59
+ throw new Error("Cannot open OAuth database. Check its format, encryption key and public URL.");
60
+ }
61
+ }
62
+ encrypt(value, identity) {
63
+ const iv = randomBytes(12);
64
+ const cipher = createCipheriv("aes-256-gcm", this.key, iv);
65
+ cipher.setAAD(Buffer.from(identity));
66
+ const ciphertext = Buffer.concat([
67
+ cipher.update(JSON.stringify(value), "utf8"),
68
+ cipher.final(),
69
+ ]);
70
+ return Buffer.concat([iv, cipher.getAuthTag(), ciphertext]);
71
+ }
72
+ decrypt(value, identity) {
73
+ const bytes = Buffer.from(value);
74
+ const decipher = createDecipheriv("aes-256-gcm", this.key, bytes.subarray(0, 12));
75
+ decipher.setAAD(Buffer.from(identity));
76
+ decipher.setAuthTag(bytes.subarray(12, 28));
77
+ return JSON.parse(Buffer.concat([decipher.update(bytes.subarray(28)), decipher.final()]).toString("utf8"));
78
+ }
79
+ get(kind, id) {
80
+ const row = this.db
81
+ .prepare("SELECT payload, expires_at, grant_id FROM oauth_records WHERE kind=? AND id=? AND (expires_at=0 OR expires_at>?)")
82
+ .get(kind, id, Math.floor(Date.now() / 1000));
83
+ if (!row)
84
+ return undefined;
85
+ return this.decrypt(row.payload, JSON.stringify([kind, id, row.expires_at, row.grant_id]));
86
+ }
87
+ put(kind, id, value, expiresAt, grantId) {
88
+ const payload = this.encrypt(value, JSON.stringify([kind, id, expiresAt, grantId ?? null]));
89
+ this.db
90
+ .prepare("INSERT INTO oauth_records(kind,id,payload,expires_at,grant_id) VALUES(?,?,?,?,?) ON CONFLICT(kind,id) DO UPDATE SET payload=excluded.payload, expires_at=excluded.expires_at, grant_id=excluded.grant_id")
91
+ .run(kind, id, payload, expiresAt, grantId ?? null);
92
+ }
93
+ remove(kind, id) {
94
+ this.db.prepare("DELETE FROM oauth_records WHERE kind=? AND id=?").run(kind, id);
95
+ }
96
+ revokeGrant(id) {
97
+ this.db
98
+ .prepare("DELETE FROM oauth_records WHERE (kind='grant' AND id=?) OR grant_id=?")
99
+ .run(id, id);
100
+ }
101
+ hasPersistentGrant(clientId) {
102
+ const rows = this.db
103
+ .prepare("SELECT id FROM oauth_records WHERE kind='grant' AND expires_at=0")
104
+ .all();
105
+ return rows.some((row) => this.get("grant", String(row.id))?.clientId === clientId);
106
+ }
107
+ count(kind) {
108
+ return Number(this.db
109
+ .prepare("SELECT COUNT(*) AS n FROM oauth_records WHERE kind=? AND (expires_at=0 OR expires_at>?)")
110
+ .get(kind, Math.floor(Date.now() / 1000))?.n);
111
+ }
112
+ transaction(operation) {
113
+ this.db.exec("BEGIN IMMEDIATE");
114
+ try {
115
+ this.db
116
+ .prepare("DELETE FROM oauth_records WHERE expires_at>0 AND expires_at<=?")
117
+ .run(Math.floor(Date.now() / 1000));
118
+ const result = operation();
119
+ this.db.exec("COMMIT");
120
+ return result;
121
+ }
122
+ catch (error) {
123
+ this.db.exec("ROLLBACK");
124
+ throw error;
125
+ }
126
+ }
127
+ close() {
128
+ this.db.close();
129
+ }
130
+ }
131
+ //# sourceMappingURL=oauth-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-store.js","sourceRoot":"","sources":["../src/oauth-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { type Express } from "express";
2
+ import type { OAuthServerProvider } from "@modelcontextprotocol/sdk/server/auth/provider.js";
3
+ import type { Config } from "./config.js";
4
+ export declare function mountProjectOAuth(app: Express, cfg: Config): OAuthServerProvider | undefined;