@grigoreo-dev/otask-mcp 1.2.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.
Files changed (142) hide show
  1. package/README.md +168 -0
  2. package/dist/constants.d.ts +3 -0
  3. package/dist/constants.d.ts.map +1 -0
  4. package/dist/constants.js +3 -0
  5. package/dist/constants.js.map +1 -0
  6. package/dist/docs/parser.d.ts +3 -0
  7. package/dist/docs/parser.d.ts.map +1 -0
  8. package/dist/docs/parser.js +123 -0
  9. package/dist/docs/parser.js.map +1 -0
  10. package/dist/docs/types.d.ts +29 -0
  11. package/dist/docs/types.d.ts.map +1 -0
  12. package/dist/docs/types.js +2 -0
  13. package/dist/docs/types.js.map +1 -0
  14. package/dist/http-server.d.ts +3 -0
  15. package/dist/http-server.d.ts.map +1 -0
  16. package/dist/http-server.js +92 -0
  17. package/dist/http-server.js.map +1 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +23 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/mcp-http.d.ts +3 -0
  23. package/dist/mcp-http.d.ts.map +1 -0
  24. package/dist/mcp-http.js +98 -0
  25. package/dist/mcp-http.js.map +1 -0
  26. package/dist/proxy/headers.d.ts +3 -0
  27. package/dist/proxy/headers.d.ts.map +1 -0
  28. package/dist/proxy/headers.js +37 -0
  29. package/dist/proxy/headers.js.map +1 -0
  30. package/dist/proxy/read-body.d.ts +3 -0
  31. package/dist/proxy/read-body.d.ts.map +1 -0
  32. package/dist/proxy/read-body.js +18 -0
  33. package/dist/proxy/read-body.js.map +1 -0
  34. package/dist/schemas/task.d.ts +241 -0
  35. package/dist/schemas/task.d.ts.map +1 -0
  36. package/dist/schemas/task.js +95 -0
  37. package/dist/schemas/task.js.map +1 -0
  38. package/dist/schemas/workspace.d.ts +66 -0
  39. package/dist/schemas/workspace.d.ts.map +1 -0
  40. package/dist/schemas/workspace.js +30 -0
  41. package/dist/schemas/workspace.js.map +1 -0
  42. package/dist/server.d.ts +5 -0
  43. package/dist/server.d.ts.map +1 -0
  44. package/dist/server.js +13 -0
  45. package/dist/server.js.map +1 -0
  46. package/dist/services/api.d.ts +23 -0
  47. package/dist/services/api.d.ts.map +1 -0
  48. package/dist/services/api.js +215 -0
  49. package/dist/services/api.js.map +1 -0
  50. package/dist/services/auth.d.ts +23 -0
  51. package/dist/services/auth.d.ts.map +1 -0
  52. package/dist/services/auth.js +122 -0
  53. package/dist/services/auth.js.map +1 -0
  54. package/dist/services/client.d.ts +21 -0
  55. package/dist/services/client.d.ts.map +1 -0
  56. package/dist/services/client.js +17 -0
  57. package/dist/services/client.js.map +1 -0
  58. package/dist/services/format.d.ts +6 -0
  59. package/dist/services/format.d.ts.map +1 -0
  60. package/dist/services/format.js +8 -0
  61. package/dist/services/format.js.map +1 -0
  62. package/dist/services/http-auth-context.d.ts +7 -0
  63. package/dist/services/http-auth-context.d.ts.map +1 -0
  64. package/dist/services/http-auth-context.js +9 -0
  65. package/dist/services/http-auth-context.js.map +1 -0
  66. package/dist/services/project-guard.d.ts +35 -0
  67. package/dist/services/project-guard.d.ts.map +1 -0
  68. package/dist/services/project-guard.js +85 -0
  69. package/dist/services/project-guard.js.map +1 -0
  70. package/dist/services/task-mapper.d.ts +90 -0
  71. package/dist/services/task-mapper.d.ts.map +1 -0
  72. package/dist/services/task-mapper.js +148 -0
  73. package/dist/services/task-mapper.js.map +1 -0
  74. package/dist/tools/add-comment.d.ts +4 -0
  75. package/dist/tools/add-comment.d.ts.map +1 -0
  76. package/dist/tools/add-comment.js +43 -0
  77. package/dist/tools/add-comment.js.map +1 -0
  78. package/dist/tools/archive-task.d.ts +4 -0
  79. package/dist/tools/archive-task.d.ts.map +1 -0
  80. package/dist/tools/archive-task.js +43 -0
  81. package/dist/tools/archive-task.js.map +1 -0
  82. package/dist/tools/create-task.d.ts +4 -0
  83. package/dist/tools/create-task.d.ts.map +1 -0
  84. package/dist/tools/create-task.js +45 -0
  85. package/dist/tools/create-task.js.map +1 -0
  86. package/dist/tools/get-task.d.ts +4 -0
  87. package/dist/tools/get-task.d.ts.map +1 -0
  88. package/dist/tools/get-task.js +44 -0
  89. package/dist/tools/get-task.js.map +1 -0
  90. package/dist/tools/helpers.d.ts +15 -0
  91. package/dist/tools/helpers.d.ts.map +1 -0
  92. package/dist/tools/helpers.js +19 -0
  93. package/dist/tools/helpers.js.map +1 -0
  94. package/dist/tools/list-board.d.ts +4 -0
  95. package/dist/tools/list-board.d.ts.map +1 -0
  96. package/dist/tools/list-board.js +52 -0
  97. package/dist/tools/list-board.js.map +1 -0
  98. package/dist/tools/list-comments.d.ts +4 -0
  99. package/dist/tools/list-comments.d.ts.map +1 -0
  100. package/dist/tools/list-comments.js +41 -0
  101. package/dist/tools/list-comments.js.map +1 -0
  102. package/dist/tools/list-members.d.ts +4 -0
  103. package/dist/tools/list-members.d.ts.map +1 -0
  104. package/dist/tools/list-members.js +39 -0
  105. package/dist/tools/list-members.js.map +1 -0
  106. package/dist/tools/list-project-tasks.d.ts +4 -0
  107. package/dist/tools/list-project-tasks.d.ts.map +1 -0
  108. package/dist/tools/list-project-tasks.js +52 -0
  109. package/dist/tools/list-project-tasks.js.map +1 -0
  110. package/dist/tools/list-projects.d.ts +4 -0
  111. package/dist/tools/list-projects.d.ts.map +1 -0
  112. package/dist/tools/list-projects.js +40 -0
  113. package/dist/tools/list-projects.js.map +1 -0
  114. package/dist/tools/list-tags.d.ts +4 -0
  115. package/dist/tools/list-tags.d.ts.map +1 -0
  116. package/dist/tools/list-tags.js +39 -0
  117. package/dist/tools/list-tags.js.map +1 -0
  118. package/dist/tools/move-task.d.ts +4 -0
  119. package/dist/tools/move-task.d.ts.map +1 -0
  120. package/dist/tools/move-task.js +59 -0
  121. package/dist/tools/move-task.js.map +1 -0
  122. package/dist/tools/register.d.ts +4 -0
  123. package/dist/tools/register.d.ts.map +1 -0
  124. package/dist/tools/register.js +8 -0
  125. package/dist/tools/register.js.map +1 -0
  126. package/dist/tools/registry.d.ts +15 -0
  127. package/dist/tools/registry.d.ts.map +1 -0
  128. package/dist/tools/registry.js +28 -0
  129. package/dist/tools/registry.js.map +1 -0
  130. package/dist/tools/types.d.ts +29 -0
  131. package/dist/tools/types.d.ts.map +1 -0
  132. package/dist/tools/types.js +2 -0
  133. package/dist/tools/types.js.map +1 -0
  134. package/dist/tools/update-task.d.ts +4 -0
  135. package/dist/tools/update-task.d.ts.map +1 -0
  136. package/dist/tools/update-task.js +96 -0
  137. package/dist/tools/update-task.js.map +1 -0
  138. package/dist/types.d.ts +92 -0
  139. package/dist/types.d.ts.map +1 -0
  140. package/dist/types.js +2 -0
  141. package/dist/types.js.map +1 -0
  142. package/package.json +44 -0
package/README.md ADDED
@@ -0,0 +1,168 @@
1
+ # otask-mcp-server
2
+
3
+ [MCP-сервер](https://modelcontextprotocol.io) для [O!task API](https://api.otask.ru/docs). Отдаёт операции O!task (воркспейс/задачи) как MCP-инструменты для агентов (Cursor, OpenCode, n8n MCP Client Tool).
4
+
5
+ ## Установка
6
+
7
+ ```bash
8
+ npm i -g @grigoreo-dev/otask-mcp
9
+ # или: npx @grigoreo-dev/otask-mcp
10
+ # HTTP: npx otask-mcp-http # bin name stays unscoped
11
+ ```
12
+
13
+ Публикация на npm: push tag `vX.Y.Z` (версия в tag = `package.json`). CI: `.github/workflows/publish.yml` (OIDC Trusted Publisher, без `NPM_TOKEN`).
14
+
15
+ ## Режимы
16
+
17
+ | Режим | Команда | Как выбирается | Авторизация клиента |
18
+ |-------|---------|----------------|---------------------|
19
+ | **stdio** | `bun start` | Локальный процесс; всегда берёт credentials из env сервера | Без HTTP; задайте `OTASK_*` в env |
20
+ | **HTTP gateway** | `bun run start:http` | Есть `OTASK_AUTH_KEY` **или** `OTASK_EMAIL`+`OTASK_PASSWORD` | `Authorization: Bearer <MCP_AUTH_TOKEN>` |
21
+ | **HTTP passthrough** | `bun run start:http` | Нет credentials `OTASK_*` в env | `Authorization: Bearer <токен O!task>` (проксируется в API) |
22
+
23
+ Определение: `hasServerOtaskCredentials()` — gateway, если задан статический ключ или email+password; иначе passthrough. Проверка: `GET /health` → `authMode: "gateway" | "passthrough"`.
24
+
25
+ stdio требует `OTASK_*` (без них падает при старте). HTTP gateway требует `MCP_AUTH_TOKEN`, если заданы `OTASK_*` (иначе падает при старте).
26
+
27
+ ## Переменные окружения
28
+
29
+ | Переменная | Где используется | Когда обязательна | Назначение |
30
+ |------------|------------------|-------------------|------------|
31
+ | `OTASK_AUTH_KEY` | stdio, HTTP gateway | stdio **или** gateway (альтернатива: email/password) | Статический O!task Bearer, которым пользуется сервер |
32
+ | `OTASK_EMAIL` | stdio, HTTP gateway | вместе с `OTASK_PASSWORD` как альтернатива ключу | Логин для получения токена |
33
+ | `OTASK_PASSWORD` | stdio, HTTP gateway | вместе с `OTASK_EMAIL` | Пароль для логина |
34
+ | `MCP_AUTH_TOKEN` | HTTP gateway | режим gateway | Общий секрет, который должен отправлять клиент; **не** токен O!task |
35
+ | `OTASK_ALLOWED_PROJECTS` | stdio, HTTP gateway | опционально | Список slug и/или числовых ID проектов через запятую |
36
+ | `PORT` | HTTP | опционально (по умолчанию `3847`) | Порт прослушивания |
37
+ | `HOST` | HTTP | опционально (по умолчанию `0.0.0.0`) | Адрес привязки |
38
+
39
+ ## HTTP-заголовки
40
+
41
+ | Заголовок | Режим | Назначение |
42
+ |-----------|-------|------------|
43
+ | `Authorization: Bearer …` | gateway | Должен совпадать с `MCP_AUTH_TOKEN` |
44
+ | `Authorization: Bearer …` | passthrough | Токен O!task API; проксируется на каждый запрос к API |
45
+ | `X-Otask-Allowed-Projects` | **только passthrough** | Slug/ID через запятую; в gateway игнорируется (используйте env) |
46
+
47
+ ## Эндпоинты (HTTP)
48
+
49
+ | | |
50
+ |---|---|
51
+ | MCP | `POST`/`GET` `/mcp` (Streamable HTTP) |
52
+ | Health | `GET /health` → `{ ok, mode, authMode, projectGuard }`, где `projectGuard` — `"env" \| "header" \| "off"` |
53
+
54
+ Пример деплоя: `https://otask-mcp.grigoreo.dev/mcp` (порт `3847` в Docker).
55
+
56
+ ## Примеры для n8n
57
+
58
+ ### Gateway (credentials O!task хранит сервер)
59
+
60
+ Env сервера:
61
+
62
+ ```env
63
+ OTASK_AUTH_KEY=...
64
+ MCP_AUTH_TOKEN=super-secret-mcp-token
65
+ OTASK_ALLOWED_PROJECTS=my-project,42
66
+ ```
67
+
68
+ n8n **MCP Client Tool**:
69
+
70
+ - URL: `https://otask-mcp.example/mcp`
71
+ - Transport: **HTTP Streamable**
72
+ - Credential / header: `Authorization: Bearer super-secret-mcp-token`
73
+ - **Не** отправляйте `X-Otask-Allowed-Projects` (allow-list берётся из env сервера)
74
+
75
+ ### Passthrough (токен O!task у клиента)
76
+
77
+ Env сервера: без `OTASK_AUTH_KEY` / `OTASK_EMAIL` / `OTASK_PASSWORD`.
78
+
79
+ n8n **MCP Client Tool**:
80
+
81
+ - URL: `https://otask-mcp.example/mcp`
82
+ - Transport: **HTTP Streamable**
83
+ - Credential / header: `Authorization: Bearer <тот же токен, что и для api.otask.ru>`
84
+ - Опционально: `X-Otask-Allowed-Projects: eng-backlog,99`
85
+
86
+ ### Stdio (Cursor / OpenCode)
87
+
88
+ ```env
89
+ OTASK_AUTH_KEY=...
90
+ # или OTASK_EMAIL + OTASK_PASSWORD
91
+ OTASK_ALLOWED_PROJECTS=my-project
92
+ ```
93
+
94
+ ```bash
95
+ bun start
96
+ ```
97
+
98
+ ## Инструменты
99
+
100
+ Регистрируются в `src/tools/registry.ts` (краткие intent для агентов):
101
+
102
+ | Инструмент | Назначение |
103
+ |------------|------------|
104
+ | `otask_get_task` | Получить одну задачу по workspace + slug задачи (посмотреть поля перед обновлением) |
105
+ | `otask_update_task` | Обновить существующую задачу (name, board, performers, tags, description, …) |
106
+ | `otask_list_projects` | Список проектов воркспейса (с фильтром по allow-list, если задан) |
107
+ | `otask_list_project_tasks` | Список задач в проекте |
108
+ | `otask_list_board` | Список досок/колонок (статусов) проекта — узнать `board_id` / `board_column_id` |
109
+ | `otask_list_members` | Список участников воркспейса (ID исполнителей для назначения) |
110
+ | `otask_list_tags` | Список тегов воркспейса для меток |
111
+ | `otask_list_comments` | Список комментариев к задаче |
112
+ | `otask_add_comment` | Добавить комментарий (опционально `parent_id` для ответов) |
113
+ | `otask_create_task` | Создать задачу (обязательно: `ws_slug`, `project_id`, `name`, `board_id`, `board_column_id`, `end_at`) |
114
+ | `otask_move_task` | Переместить задачу в другую колонку доски (статус) |
115
+ | `otask_archive_task` | Архивировать задачу |
116
+
117
+ Если активен allow-list проектов, project-scoped инструменты проверяют членство (или фильтруют списки); пустой allow-list = доступны все проекты.
118
+
119
+ ## Allow-list проектов
120
+
121
+ Формат: **slug** и/или **числовые ID** через запятую (пробелы обрезаются). Пусто / не задано = выключено.
122
+
123
+ ### Gateway / stdio — env
124
+
125
+ ```env
126
+ OTASK_ALLOWED_PROJECTS=product-roadmap,eng-backlog,42
127
+ ```
128
+
129
+ `GET /health` → `projectGuard: "env"`, если значение непустое.
130
+
131
+ ### Passthrough — заголовок запроса
132
+
133
+ ```http
134
+ X-Otask-Allowed-Projects: product-roadmap,42
135
+ ```
136
+
137
+ `GET /health` (с этим заголовком) → `projectGuard: "header"`. Gateway этот заголовок игнорирует и всегда использует `OTASK_ALLOWED_PROJECTS`.
138
+
139
+ ## Снимок API-документации
140
+
141
+ Пересобрать локальный каталог API из HTML-доки O!task (Scribe):
142
+
143
+ ```bash
144
+ bun run docs:parse
145
+ ```
146
+
147
+ Пишет в `docs/catalog/` с живой HTML-страницы доки (или `bun run docs:parse --file path` для офлайн HTML).
148
+
149
+ ## Разработка
150
+
151
+ ```bash
152
+ bun install
153
+ bun run build # tsc → dist/
154
+ bun test # bun test
155
+ bun start # stdio MCP
156
+ bun run start:http # Streamable HTTP MCP
157
+ bun run dev # stdio hot reload
158
+ bun run dev:http # HTTP hot reload
159
+ ```
160
+
161
+ ### Добавление инструмента
162
+
163
+ 1. `src/services/api.ts` / `client.ts` — метод API при необходимости
164
+ 2. `src/schemas/` — Zod input schema
165
+ 3. `src/tools/my-tool.ts` — `createMyTool({ api, guard })` → `ToolDefinition`
166
+ 4. `src/tools/registry.ts` — добавить factory в `toolFactories`
167
+
168
+ `server.ts` / `register.ts` править не нужно — регистрация централизована.
@@ -0,0 +1,3 @@
1
+ export declare const API_BASE_URL = "https://api.otask.ru";
2
+ export declare const CHARACTER_LIMIT = 25000;
3
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,yBAAyB,CAAC;AACnD,eAAO,MAAM,eAAe,QAAS,CAAC"}
@@ -0,0 +1,3 @@
1
+ export const API_BASE_URL = "https://api.otask.ru";
2
+ export const CHARACTER_LIMIT = 25_000;
3
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAAG,sBAAsB,CAAC;AACnD,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { DocsCatalog } from "./types.ts";
2
+ export declare function parseOtaskDocsHtml(html: string): DocsCatalog;
3
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/docs/parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EAIZ,MAAM,YAAY,CAAC;AAsIpB,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CA0B5D"}
@@ -0,0 +1,123 @@
1
+ const METHODS = new Set(["GET", "POST", "PUT", "PATCH", "DELETE"]);
2
+ function stripTags(html) {
3
+ return html
4
+ .replace(/<script[\s\S]*?<\/script>/gi, " ")
5
+ .replace(/<style[\s\S]*?<\/style>/gi, " ")
6
+ .replace(/<[^>]+>/g, " ")
7
+ .replace(/&nbsp;/g, " ")
8
+ .replace(/&amp;/g, "&")
9
+ .replace(/&lt;/g, "<")
10
+ .replace(/&gt;/g, ">")
11
+ .replace(/&quot;/g, '"')
12
+ .replace(/&#39;/g, "'")
13
+ .replace(/\s+/g, " ")
14
+ .trim();
15
+ }
16
+ function parseSidebar(html) {
17
+ const scopes = [];
18
+ const parts = html.split(/(?=<li[^>]*class="[^"]*sidebar__menu-item[^"]*")/i);
19
+ for (const part of parts) {
20
+ if (!/sidebar__menu-item/i.test(part))
21
+ continue;
22
+ const scopeMatch = part.match(/href=["']#([^"']+)["'][^>]*class=["'][^"']*sidebar__menu-link[^"']*["'][^>]*>[\s\S]*?<span[^>]*class=["'][^"']*sidebar__menu-link-name[^"']*["'][^>]*>([\s\S]*?)<\/span>/i);
23
+ if (!scopeMatch)
24
+ continue;
25
+ const id = scopeMatch[1];
26
+ const title = stripTags(scopeMatch[2]);
27
+ const endpointIds = [];
28
+ const endpointTitles = new Map();
29
+ const subRe = /href=["']#([^"']+)["'][^>]*class=["'][^"']*sidebar__submenu-link[^"']*["'][^>]*>([\s\S]*?)<\/a>/gi;
30
+ let sm;
31
+ while ((sm = subRe.exec(part)) !== null) {
32
+ const epId = sm[1];
33
+ if (epId === id)
34
+ continue;
35
+ endpointIds.push(epId);
36
+ endpointTitles.set(epId, stripTags(sm[2]));
37
+ }
38
+ scopes.push({ id, title, endpointIds, endpointTitles });
39
+ }
40
+ return scopes;
41
+ }
42
+ function findIdIndex(html, id) {
43
+ const patterns = [`id="${id}"`, `id='${id}'`];
44
+ let best = -1;
45
+ for (const p of patterns) {
46
+ const idx = html.indexOf(p);
47
+ if (idx !== -1 && (best === -1 || idx < best))
48
+ best = idx;
49
+ }
50
+ return best;
51
+ }
52
+ function sliceForEndpoint(html, endpointId, allEndpointIds) {
53
+ const start = findIdIndex(html, endpointId);
54
+ if (start === -1)
55
+ return "";
56
+ let end = html.length;
57
+ for (const otherId of allEndpointIds) {
58
+ if (otherId === endpointId)
59
+ continue;
60
+ const idx = findIdIndex(html, otherId);
61
+ if (idx > start && idx < end)
62
+ end = idx;
63
+ }
64
+ return html.slice(start, end);
65
+ }
66
+ function parseEndpointSlice(slice, id, sidebarTitle) {
67
+ if (!slice)
68
+ return null;
69
+ const text = stripTags(slice);
70
+ const methodMatch = text.match(/\b(GET|POST|PUT|PATCH|DELETE)\b/);
71
+ const method = methodMatch?.[1] ?? "GET";
72
+ if (!METHODS.has(method))
73
+ return null;
74
+ const pathMatch = text.match(/\/api\/v1\/[^\s<"]+/);
75
+ const path = pathMatch?.[0]?.replace(/[.,;)]+$/, "") ?? "";
76
+ const authRequired = /Требуется аутентификация/i.test(text) ||
77
+ /required authentication/i.test(text);
78
+ let title = sidebarTitle ?? "";
79
+ if (!title) {
80
+ const escaped = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
81
+ const hMatch = slice.match(new RegExp(`id=["']${escaped}["'][^>]*>([\\s\\S]*?)<\\/h[1-6]>`, "i"));
82
+ if (hMatch)
83
+ title = stripTags(hMatch[1]);
84
+ }
85
+ const pathParams = [];
86
+ for (const m of path.matchAll(/\{([^}]+)\}/g)) {
87
+ pathParams.push({ name: m[1], required: true });
88
+ }
89
+ return {
90
+ id,
91
+ title,
92
+ method,
93
+ path,
94
+ authRequired,
95
+ pathParams,
96
+ queryParams: [],
97
+ bodyParams: [],
98
+ docsAnchor: `#${id}`,
99
+ };
100
+ }
101
+ export function parseOtaskDocsHtml(html) {
102
+ const sidebarScopes = parseSidebar(html);
103
+ const allEndpointIds = sidebarScopes.flatMap((s) => s.endpointIds);
104
+ const scopes = sidebarScopes.map((s) => {
105
+ const endpoints = [];
106
+ for (const epId of s.endpointIds) {
107
+ const slice = sliceForEndpoint(html, epId, allEndpointIds);
108
+ const record = parseEndpointSlice(slice, epId, s.endpointTitles.get(epId));
109
+ if (record)
110
+ endpoints.push(record);
111
+ }
112
+ return {
113
+ id: s.id,
114
+ title: s.title,
115
+ endpoints,
116
+ };
117
+ });
118
+ return {
119
+ generatedAt: new Date().toISOString(),
120
+ scopes,
121
+ };
122
+ }
123
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/docs/parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
2
+ export interface DocParam {
3
+ name: string;
4
+ type?: string;
5
+ required?: boolean;
6
+ description?: string;
7
+ }
8
+ export interface EndpointRecord {
9
+ id: string;
10
+ title: string;
11
+ method: HttpMethod;
12
+ path: string;
13
+ authRequired: boolean;
14
+ pathParams: DocParam[];
15
+ queryParams: DocParam[];
16
+ bodyParams: DocParam[];
17
+ responseExample?: unknown;
18
+ docsAnchor: string;
19
+ }
20
+ export interface ScopeCatalog {
21
+ id: string;
22
+ title: string;
23
+ endpoints: EndpointRecord[];
24
+ }
25
+ export interface DocsCatalog {
26
+ generatedAt: string;
27
+ scopes: ScopeCatalog[];
28
+ }
29
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/docs/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;AAErE,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,QAAQ,EAAE,CAAC;IACvB,WAAW,EAAE,QAAQ,EAAE,CAAC;IACxB,UAAU,EAAE,QAAQ,EAAE,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,cAAc,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,YAAY,EAAE,CAAC;CACxB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/docs/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=http-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":""}
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+ import http from "node:http";
3
+ import { API_BASE_URL } from "./constants.js";
4
+ import { pickForwardRequestHeaders, pickForwardResponseHeaders } from "./proxy/headers.js";
5
+ import { readBody } from "./proxy/read-body.js";
6
+ function getAuthToken() {
7
+ const token = process.env.AUTH_TOKEN?.trim();
8
+ if (!token) {
9
+ throw new Error("AUTH_TOKEN environment variable is required for HTTP proxy mode");
10
+ }
11
+ return token;
12
+ }
13
+ function getPort() {
14
+ const raw = process.env.PORT?.trim() || "3847";
15
+ const port = Number.parseInt(raw, 10);
16
+ if (!Number.isFinite(port) || port < 1 || port > 65535) {
17
+ throw new Error(`Invalid PORT: ${raw}`);
18
+ }
19
+ return port;
20
+ }
21
+ function extractBearerToken(req) {
22
+ const auth = req.headers.authorization;
23
+ if (!auth?.startsWith("Bearer ")) {
24
+ return undefined;
25
+ }
26
+ return auth.slice("Bearer ".length).trim();
27
+ }
28
+ function sendJson(res, status, body) {
29
+ res.writeHead(status, { "Content-Type": "application/json" });
30
+ res.end(JSON.stringify(body));
31
+ }
32
+ async function handleRequest(req, res, authToken) {
33
+ const method = req.method ?? "GET";
34
+ const requestUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
35
+ if (requestUrl.pathname === "/health") {
36
+ sendJson(res, 200, { ok: true });
37
+ return;
38
+ }
39
+ if (!requestUrl.pathname.startsWith("/api/")) {
40
+ sendJson(res, 404, {
41
+ error: "Not found",
42
+ hint: "Proxy only forwards /api/* to O!task API",
43
+ });
44
+ return;
45
+ }
46
+ const clientToken = extractBearerToken(req);
47
+ if (!clientToken || clientToken !== authToken) {
48
+ sendJson(res, 401, {
49
+ error: "Unauthorized",
50
+ hint: "Send Authorization: Bearer <AUTH_TOKEN>",
51
+ });
52
+ return;
53
+ }
54
+ const upstreamUrl = `${API_BASE_URL}${requestUrl.pathname}${requestUrl.search}`;
55
+ const forwardHeaders = pickForwardRequestHeaders(req.headers);
56
+ forwardHeaders.Authorization = `Bearer ${authToken}`;
57
+ const body = await readBody(req);
58
+ const upstream = await fetch(upstreamUrl, {
59
+ method,
60
+ headers: forwardHeaders,
61
+ body,
62
+ });
63
+ const responseHeaders = pickForwardResponseHeaders(upstream.headers);
64
+ res.writeHead(upstream.status, responseHeaders);
65
+ if (method === "HEAD") {
66
+ res.end();
67
+ return;
68
+ }
69
+ const responseBody = Buffer.from(await upstream.arrayBuffer());
70
+ res.end(responseBody);
71
+ }
72
+ function main() {
73
+ const authToken = getAuthToken();
74
+ const port = getPort();
75
+ const server = http.createServer((req, res) => {
76
+ handleRequest(req, res, authToken).catch((error) => {
77
+ const message = error instanceof Error ? error.message : String(error);
78
+ if (!res.headersSent) {
79
+ sendJson(res, 502, { error: "Upstream proxy error", message });
80
+ return;
81
+ }
82
+ res.end();
83
+ console.error("Proxy error after headers sent:", message);
84
+ });
85
+ });
86
+ server.listen(port, () => {
87
+ console.error(`O!task HTTP proxy listening on http://localhost:${port}`);
88
+ console.error(`Forwarding /api/* -> ${API_BASE_URL}/api/*`);
89
+ });
90
+ }
91
+ main();
92
+ //# sourceMappingURL=http-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server.js","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { createEnvAuthResolver, validateStdioAuthConfig } from "./services/auth.js";
4
+ import { projectGuardFromEnv } from "./services/project-guard.js";
5
+ import { createMcpServer } from "./server.js";
6
+ async function main() {
7
+ try {
8
+ validateStdioAuthConfig();
9
+ }
10
+ catch (error) {
11
+ const message = error instanceof Error ? error.message : String(error);
12
+ console.error(`ERROR: ${message}`);
13
+ process.exit(1);
14
+ }
15
+ const server = createMcpServer(createEnvAuthResolver(), projectGuardFromEnv());
16
+ const transport = new StdioServerTransport();
17
+ await server.connect(transport);
18
+ }
19
+ main().catch((error) => {
20
+ console.error("Server error:", error);
21
+ process.exit(1);
22
+ });
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AACpF,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC;QACH,uBAAuB,EAAE,CAAC;IAC5B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,OAAO,CAAC,KAAK,CAAC,UAAU,OAAO,EAAE,CAAC,CAAC;QACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,eAAe,CAC5B,qBAAqB,EAAE,EACvB,mBAAmB,EAAE,CACtB,CAAC;IACF,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC;IACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=mcp-http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-http.d.ts","sourceRoot":"","sources":["../src/mcp-http.ts"],"names":[],"mappings":""}
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env node
2
+ import http from "node:http";
3
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
+ import { createMcpServer } from "./server.js";
5
+ import { authorizeHttpMcpRequest, createEnvAuthResolver, createPassthroughAuthResolver, extractBearerToken, extractProjectAllowListHeader, getHttpAuthMode, validateHttpAuthConfig, } from "./services/auth.js";
6
+ import { projectGuardMode, resolveHttpProjectGuard, } from "./services/project-guard.js";
7
+ function getPort() {
8
+ const raw = process.env.PORT?.trim() || "3847";
9
+ const port = Number.parseInt(raw, 10);
10
+ if (!Number.isFinite(port) || port < 1 || port > 65535) {
11
+ throw new Error(`Invalid PORT: ${raw}`);
12
+ }
13
+ return port;
14
+ }
15
+ function sendJson(res, status, body) {
16
+ res.writeHead(status, { "Content-Type": "application/json" });
17
+ res.end(JSON.stringify(body));
18
+ }
19
+ async function handleMcpRequest(req, res) {
20
+ const clientBearer = extractBearerToken(req.headers.authorization);
21
+ const authResult = authorizeHttpMcpRequest(clientBearer);
22
+ if (!authResult.ok) {
23
+ sendJson(res, authResult.status, {
24
+ error: authResult.error,
25
+ hint: authResult.hint,
26
+ });
27
+ return;
28
+ }
29
+ const auth = authResult.otaskBearer
30
+ ? createPassthroughAuthResolver(authResult.otaskBearer)
31
+ : createEnvAuthResolver();
32
+ const authMode = getHttpAuthMode();
33
+ const headerRaw = extractProjectAllowListHeader(req.headers);
34
+ const guard = resolveHttpProjectGuard({
35
+ authMode,
36
+ env: process.env,
37
+ headerRaw,
38
+ });
39
+ const transport = new StreamableHTTPServerTransport({
40
+ sessionIdGenerator: undefined,
41
+ enableJsonResponse: true,
42
+ });
43
+ const server = createMcpServer(auth, guard);
44
+ res.on("close", () => {
45
+ void transport.close();
46
+ });
47
+ await server.connect(transport);
48
+ await transport.handleRequest(req, res);
49
+ }
50
+ async function handleRequest(req, res) {
51
+ const requestUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
52
+ if (requestUrl.pathname === "/health") {
53
+ const authMode = getHttpAuthMode();
54
+ const headerRaw = extractProjectAllowListHeader(req.headers);
55
+ const guard = resolveHttpProjectGuard({
56
+ authMode,
57
+ env: process.env,
58
+ headerRaw,
59
+ });
60
+ sendJson(res, 200, {
61
+ ok: true,
62
+ mode: "mcp-streamable-http",
63
+ authMode,
64
+ projectGuard: projectGuardMode(authMode, guard),
65
+ });
66
+ return;
67
+ }
68
+ if (requestUrl.pathname === "/mcp") {
69
+ await handleMcpRequest(req, res);
70
+ return;
71
+ }
72
+ sendJson(res, 404, {
73
+ error: "Not found",
74
+ hint: "MCP endpoint: POST/GET /mcp",
75
+ });
76
+ }
77
+ function main() {
78
+ validateHttpAuthConfig();
79
+ const port = getPort();
80
+ const host = process.env.HOST?.trim() || "0.0.0.0";
81
+ http
82
+ .createServer((req, res) => {
83
+ handleRequest(req, res).catch((error) => {
84
+ const message = error instanceof Error ? error.message : String(error);
85
+ if (!res.headersSent) {
86
+ sendJson(res, 500, { error: "MCP server error", message });
87
+ return;
88
+ }
89
+ res.end();
90
+ console.error("MCP HTTP error:", message);
91
+ });
92
+ })
93
+ .listen(port, host, () => {
94
+ console.error(`O!task MCP HTTP http://${host}:${port}/mcp [${getHttpAuthMode()}]`);
95
+ });
96
+ }
97
+ main();
98
+ //# sourceMappingURL=mcp-http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-http.js","sourceRoot":"","sources":["../src/mcp-http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare function pickForwardRequestHeaders(headers: Record<string, string | string[] | undefined>): Record<string, string>;
2
+ export declare function pickForwardResponseHeaders(headers: Headers): Record<string, string | string[]>;
3
+ //# sourceMappingURL=headers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../../src/proxy/headers.ts"],"names":[],"mappings":"AAWA,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GACrD,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgBxB;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,OAAO,GACf,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAWnC"}