@redcollar/trck-mcp 1.0.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/README.md +108 -0
- package/dist/api/client.d.ts +12 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +66 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/projects.d.ts +20 -0
- package/dist/api/projects.d.ts.map +1 -0
- package/dist/api/projects.js +14 -0
- package/dist/api/projects.js.map +1 -0
- package/dist/api/requests.d.ts +17 -0
- package/dist/api/requests.d.ts.map +1 -0
- package/dist/api/requests.js +8 -0
- package/dist/api/requests.js.map +1 -0
- package/dist/api/timesheets.d.ts +38 -0
- package/dist/api/timesheets.d.ts.map +1 -0
- package/dist/api/timesheets.js +20 -0
- package/dist/api/timesheets.js.map +1 -0
- package/dist/auth/oauth.d.ts +13 -0
- package/dist/auth/oauth.d.ts.map +1 -0
- package/dist/auth/oauth.js +193 -0
- package/dist/auth/oauth.js.map +1 -0
- package/dist/auth/token-storage.d.ts +10 -0
- package/dist/auth/token-storage.d.ts.map +1 -0
- package/dist/auth/token-storage.js +42 -0
- package/dist/auth/token-storage.js.map +1 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +18 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/tool-result.d.ts +5 -0
- package/dist/lib/tool-result.d.ts.map +1 -0
- package/dist/lib/tool-result.js +14 -0
- package/dist/lib/tool-result.js.map +1 -0
- package/dist/tools/projects.d.ts +4 -0
- package/dist/tools/projects.d.ts.map +1 -0
- package/dist/tools/projects.js +23 -0
- package/dist/tools/projects.js.map +1 -0
- package/dist/tools/requests.d.ts +4 -0
- package/dist/tools/requests.d.ts.map +1 -0
- package/dist/tools/requests.js +40 -0
- package/dist/tools/requests.js.map +1 -0
- package/dist/tools/timesheets.d.ts +4 -0
- package/dist/tools/timesheets.d.ts.map +1 -0
- package/dist/tools/timesheets.js +123 -0
- package/dist/tools/timesheets.js.map +1 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# trck-mcp
|
|
2
|
+
|
|
3
|
+
MCP-сервер для внутреннего сервиса трекинга времени. Позволяет LLM-агентам трекать время через естественный язык.
|
|
4
|
+
|
|
5
|
+
Пример: *«Затрекай в проект PRJ, задачу Dev, 2 часа на сегодня»*
|
|
6
|
+
|
|
7
|
+
## Возможности
|
|
8
|
+
|
|
9
|
+
- **Таймшиты** — создание, просмотр, обновление, удаление записей времени
|
|
10
|
+
- **Проекты и задачи** — список проектов пользователя с задачами (для маппинга имён → ID)
|
|
11
|
+
- **Запросы на открытие дат** — создание и просмотр запросов на разблокировку прошлых дат
|
|
12
|
+
- **Сумма часов** — сводка по часам за период
|
|
13
|
+
|
|
14
|
+
## Требования
|
|
15
|
+
|
|
16
|
+
- Node.js >= 18
|
|
17
|
+
- npm
|
|
18
|
+
- Доступ к Keycloak (SSO) для авторизации
|
|
19
|
+
|
|
20
|
+
## Установка
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @redcollar/trck-mcp
|
|
24
|
+
# или через npx
|
|
25
|
+
npx @redcollar/trck-mcp
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Настройка Keycloak
|
|
29
|
+
|
|
30
|
+
В Keycloak для клиента `<CLIENT_ID>` (realm `<REALM>`) необходимо добавить в **Valid Redirect URIs**:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
http://localhost:9876/callback
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Сервер при авторизации поднимает HTTP-сервер на фиксированном порту (по умолчанию `9876`) и использует redirect URI `http://localhost:9876/callback`. Порт настраивается через переменную `TRCK_CALLBACK_PORT` — если меняете порт, не забудьте обновить Valid Redirect URIs в Keycloak.
|
|
37
|
+
|
|
38
|
+
> **Важно**: Keycloak поддерживает wildcard только в конце path (`http://host/path/*`), но не в порту. Поэтому используется фиксированный порт, а не случайный.
|
|
39
|
+
|
|
40
|
+
### Где настроить
|
|
41
|
+
|
|
42
|
+
1. Открыть Keycloak Admin Console → Realm `<REALM>` → Clients → `<CLIENT_ID>`
|
|
43
|
+
2. В разделе **Valid Redirect URIs** добавить `http://localhost:9876/callback`
|
|
44
|
+
3. Сохранить
|
|
45
|
+
|
|
46
|
+
## Подключение к MCP-клиенту
|
|
47
|
+
|
|
48
|
+
### OpenCode / Claude Desktop / Cursor
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"mcpServers": {
|
|
53
|
+
"trck": {
|
|
54
|
+
"command": "npx",
|
|
55
|
+
"args": ["@redcollar/trck-mcp"],
|
|
56
|
+
"env": {
|
|
57
|
+
"TRCK_API_URL": "<API_URL>",
|
|
58
|
+
"TRCK_KEYCLOAK_URL": "<KEYCLOAK_URL>",
|
|
59
|
+
"TRCK_KEYCLOAK_REALM": "<REALM>",
|
|
60
|
+
"TRCK_CLIENT_ID": "<CLIENT_ID>"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Переменные окружения
|
|
68
|
+
|
|
69
|
+
| Переменная | По умолчанию | Описание |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `TRCK_API_URL` | `<API_URL>` | Base URL API трекера |
|
|
72
|
+
| `TRCK_KEYCLOAK_URL` | `<KEYCLOAK_URL>` | Base URL Keycloak |
|
|
73
|
+
| `TRCK_KEYCLOAK_REALM` | `<REALM>` | Realm в Keycloak |
|
|
74
|
+
| `TRCK_CLIENT_ID` | `<CLIENT_ID>` | Client ID в Keycloak |
|
|
75
|
+
| `TRCK_CALLBACK_PORT` | `9876` | Порт для OAuth callback сервера |
|
|
76
|
+
|
|
77
|
+
Для dev-окружения можно не указывать `env` — все дефолты уже настроены.
|
|
78
|
+
|
|
79
|
+
## Авторизация
|
|
80
|
+
|
|
81
|
+
При первом вызове любого инструмента:
|
|
82
|
+
|
|
83
|
+
1. Сервер откроет браузер со страницей логина Keycloak
|
|
84
|
+
2. После входа Keycloak редиректит на `http://localhost:9876/callback`
|
|
85
|
+
3. Токены сохраняются в `~/.trck-mcp/tokens.json`
|
|
86
|
+
4. При следующих запусках повторный логин не нужен (используется refresh token)
|
|
87
|
+
|
|
88
|
+
## MCP Tools
|
|
89
|
+
|
|
90
|
+
| Tool | Описание | Чтение/Запись |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| `get_my_timesheets` | Таймшиты за неделю (weekNumber, year) | read |
|
|
93
|
+
| `get_my_month_hours` | Сумма часов за период (dateFrom, dateTo) | read |
|
|
94
|
+
| `create_timesheet` | Создать запись (projectId, taskId, date, time) | write |
|
|
95
|
+
| `update_timesheet` | Обновить запись (id, time, description?) | write |
|
|
96
|
+
| `delete_timesheet` | Удалить запись (id) | write (destructive) |
|
|
97
|
+
| `get_approved_dates` | Одобренные даты для прошлых периодов | read |
|
|
98
|
+
| `get_my_projects` | Проекты с задачами (для маппинга имён → ID) | read |
|
|
99
|
+
| `get_my_requests` | Мои запросы на открытие дат | read |
|
|
100
|
+
| `create_request` | Запрос на открытие даты (projectId, dateToOpen, comment) | write |
|
|
101
|
+
|
|
102
|
+
## Разработка
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
npm run dev # TypeScript watch mode
|
|
106
|
+
npm run build # Сборка
|
|
107
|
+
npm start # Запуск (stdio)
|
|
108
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Config } from "../config.js";
|
|
2
|
+
export declare class ApiClient {
|
|
3
|
+
private config;
|
|
4
|
+
constructor(config: Config);
|
|
5
|
+
request<T>(method: string, path: string, body?: unknown, query?: Record<string, string>): Promise<T>;
|
|
6
|
+
private doRequest;
|
|
7
|
+
}
|
|
8
|
+
export declare class ApiError extends Error {
|
|
9
|
+
status: number;
|
|
10
|
+
constructor(status: number, body: string);
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAG3C,qBAAa,SAAS;IAClB,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,MAAM;IAIpB,OAAO,CAAC,CAAC,EACX,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,EACd,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,OAAO,CAAC,CAAC,CAAC;YAoCC,SAAS;CAkD1B;AAED,qBAAa,QAAS,SAAQ,KAAK;IAC/B,MAAM,EAAE,MAAM,CAAC;gBAEH,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAK3C"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { getAccessToken, forceRefresh } from "../auth/oauth.js";
|
|
2
|
+
export class ApiClient {
|
|
3
|
+
config;
|
|
4
|
+
constructor(config) {
|
|
5
|
+
this.config = config;
|
|
6
|
+
}
|
|
7
|
+
async request(method, path, body, query) {
|
|
8
|
+
const token = await getAccessToken(this.config);
|
|
9
|
+
const result = await this.doRequest(method, path, token, body, query);
|
|
10
|
+
// If 401, try refresh and retry once
|
|
11
|
+
if (result.status === 401) {
|
|
12
|
+
console.error(`[api] Got 401 on ${method} ${path}, refreshing token...`);
|
|
13
|
+
const newToken = await forceRefresh(this.config);
|
|
14
|
+
const retry = await this.doRequest(method, path, newToken, body, query);
|
|
15
|
+
if (!retry.ok) {
|
|
16
|
+
throw new ApiError(retry.status, retry.errorText);
|
|
17
|
+
}
|
|
18
|
+
return retry.data;
|
|
19
|
+
}
|
|
20
|
+
if (!result.ok) {
|
|
21
|
+
throw new ApiError(result.status, result.errorText);
|
|
22
|
+
}
|
|
23
|
+
return result.data;
|
|
24
|
+
}
|
|
25
|
+
async doRequest(method, path, token, body, query) {
|
|
26
|
+
let url = `${this.config.apiUrl}${path}`;
|
|
27
|
+
if (query) {
|
|
28
|
+
const params = new URLSearchParams(query);
|
|
29
|
+
url += `?${params.toString()}`;
|
|
30
|
+
}
|
|
31
|
+
const headers = {
|
|
32
|
+
Authorization: `Bearer ${token}`,
|
|
33
|
+
Accept: "application/json",
|
|
34
|
+
};
|
|
35
|
+
const init = { method, headers };
|
|
36
|
+
if (body !== undefined) {
|
|
37
|
+
headers["Content-Type"] = "application/json";
|
|
38
|
+
init.body = JSON.stringify(body);
|
|
39
|
+
}
|
|
40
|
+
const resp = await fetch(url, init);
|
|
41
|
+
if (!resp.ok) {
|
|
42
|
+
const errorText = await resp.text();
|
|
43
|
+
return { ok: false, status: resp.status, errorText };
|
|
44
|
+
}
|
|
45
|
+
// Some endpoints return 204 No Content
|
|
46
|
+
if (resp.status === 204) {
|
|
47
|
+
return {
|
|
48
|
+
ok: true,
|
|
49
|
+
status: 204,
|
|
50
|
+
data: undefined,
|
|
51
|
+
errorText: "",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const data = (await resp.json());
|
|
55
|
+
return { ok: true, status: resp.status, data, errorText: "" };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export class ApiError extends Error {
|
|
59
|
+
status;
|
|
60
|
+
constructor(status, body) {
|
|
61
|
+
super(`API error ${status}: ${body}`);
|
|
62
|
+
this.name = "ApiError";
|
|
63
|
+
this.status = status;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ApiClient } from "./client.js";
|
|
2
|
+
export interface TaskProjectResponse {
|
|
3
|
+
id: number;
|
|
4
|
+
name: string;
|
|
5
|
+
projectId: number;
|
|
6
|
+
described: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface ProjectUserResponse {
|
|
9
|
+
id: number;
|
|
10
|
+
name: string;
|
|
11
|
+
status: "ACTIVE" | "ARCHIVE";
|
|
12
|
+
tasks?: TaskProjectResponse[];
|
|
13
|
+
managers: number[];
|
|
14
|
+
icon?: string;
|
|
15
|
+
firstDay: string;
|
|
16
|
+
lastDay: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function clearProjectsCache(): void;
|
|
19
|
+
export declare function getMyProjects(client: ApiClient): Promise<ProjectUserResponse[]>;
|
|
20
|
+
//# sourceMappingURL=projects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/api/projects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI7C,MAAM,WAAW,mBAAmB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC7B,KAAK,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAC9B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACnB;AAMD,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAID,wBAAsB,aAAa,CAC/B,MAAM,EAAE,SAAS,GAClB,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAShC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// --- Cache ---
|
|
2
|
+
let cachedProjects = null;
|
|
3
|
+
export function clearProjectsCache() {
|
|
4
|
+
cachedProjects = null;
|
|
5
|
+
}
|
|
6
|
+
// --- API functions ---
|
|
7
|
+
export async function getMyProjects(client) {
|
|
8
|
+
if (cachedProjects)
|
|
9
|
+
return cachedProjects;
|
|
10
|
+
const projects = await client.request("GET", "/api/users/me/projects");
|
|
11
|
+
cachedProjects = projects;
|
|
12
|
+
return projects;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=projects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projects.js","sourceRoot":"","sources":["../../src/api/projects.ts"],"names":[],"mappings":"AAsBA,gBAAgB;AAEhB,IAAI,cAAc,GAAiC,IAAI,CAAC;AAExD,MAAM,UAAU,kBAAkB;IAC9B,cAAc,GAAG,IAAI,CAAC;AAC1B,CAAC;AAED,wBAAwB;AAExB,MAAM,CAAC,KAAK,UAAU,aAAa,CAC/B,MAAiB;IAEjB,IAAI,cAAc;QAAE,OAAO,cAAc,CAAC;IAE1C,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,CACjC,KAAK,EACL,wBAAwB,CAC3B,CAAC;IACF,cAAc,GAAG,QAAQ,CAAC;IAC1B,OAAO,QAAQ,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ApiClient } from "./client.js";
|
|
2
|
+
export interface RequestCreate {
|
|
3
|
+
projectId: number;
|
|
4
|
+
dateToOpen: string;
|
|
5
|
+
comment: string;
|
|
6
|
+
}
|
|
7
|
+
export interface RequestResponse {
|
|
8
|
+
id: number;
|
|
9
|
+
projectId: number;
|
|
10
|
+
userId: number;
|
|
11
|
+
date: string;
|
|
12
|
+
comment: string;
|
|
13
|
+
status: "NEW" | "DECLINED" | "ACCEPTED" | "CANCELLED" | "HR" | "CLOSED";
|
|
14
|
+
}
|
|
15
|
+
export declare function getMyRequests(client: ApiClient): Promise<RequestResponse[]>;
|
|
16
|
+
export declare function createRequest(client: ApiClient, data: RequestCreate): Promise<RequestResponse>;
|
|
17
|
+
//# sourceMappingURL=requests.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requests.d.ts","sourceRoot":"","sources":["../../src/api/requests.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI7C,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,KAAK,GAAG,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,IAAI,GAAG,QAAQ,CAAC;CACzE;AAID,wBAAsB,aAAa,CACjC,MAAM,EAAE,SAAS,GAChB,OAAO,CAAC,eAAe,EAAE,CAAC,CAE5B;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,eAAe,CAAC,CAM1B"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// --- API functions ---
|
|
2
|
+
export async function getMyRequests(client) {
|
|
3
|
+
return client.request("GET", "/api/users/me/requests");
|
|
4
|
+
}
|
|
5
|
+
export async function createRequest(client, data) {
|
|
6
|
+
return client.request("POST", "/api/users/me/requests", data);
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=requests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requests.js","sourceRoot":"","sources":["../../src/api/requests.ts"],"names":[],"mappings":"AAmBA,wBAAwB;AAExB,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAAiB;IAEjB,OAAO,MAAM,CAAC,OAAO,CAAoB,KAAK,EAAE,wBAAwB,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAAiB,EACjB,IAAmB;IAEnB,OAAO,MAAM,CAAC,OAAO,CACnB,MAAM,EACN,wBAAwB,EACxB,IAAI,CACL,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ApiClient } from "./client.js";
|
|
2
|
+
export interface TimeSheetCreateRequest {
|
|
3
|
+
taskId: number;
|
|
4
|
+
projectId: number;
|
|
5
|
+
date: string;
|
|
6
|
+
time: number;
|
|
7
|
+
description?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface TimeSheetUpdateRequest {
|
|
10
|
+
time: number;
|
|
11
|
+
description?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface TimeSheetUserResponse {
|
|
14
|
+
id: number;
|
|
15
|
+
taskId?: number;
|
|
16
|
+
projectId: number;
|
|
17
|
+
date: string;
|
|
18
|
+
time: number;
|
|
19
|
+
description: string;
|
|
20
|
+
isIdle: boolean;
|
|
21
|
+
isOvertime: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface MonthHoursResponse {
|
|
24
|
+
date: string;
|
|
25
|
+
hours: number;
|
|
26
|
+
}
|
|
27
|
+
export interface ApprovedDatesResponse {
|
|
28
|
+
projectId: number;
|
|
29
|
+
dateFrom: string;
|
|
30
|
+
dateTo: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function getMyTimesheets(client: ApiClient, week: string): Promise<TimeSheetUserResponse[]>;
|
|
33
|
+
export declare function getMyMonthHours(client: ApiClient, dateFrom: string, dateTo: string): Promise<MonthHoursResponse[]>;
|
|
34
|
+
export declare function createTimesheet(client: ApiClient, data: TimeSheetCreateRequest): Promise<TimeSheetUserResponse[]>;
|
|
35
|
+
export declare function updateTimesheet(client: ApiClient, id: number, data: TimeSheetUpdateRequest): Promise<TimeSheetUserResponse>;
|
|
36
|
+
export declare function deleteTimesheet(client: ApiClient, id: number): Promise<void>;
|
|
37
|
+
export declare function getApprovedDates(client: ApiClient): Promise<ApprovedDatesResponse[]>;
|
|
38
|
+
//# sourceMappingURL=timesheets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timesheets.d.ts","sourceRoot":"","sources":["../../src/api/timesheets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI7C,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAID,wBAAsB,eAAe,CACnC,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAOlC;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,SAAS,EACjB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAO/B;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAMlC;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,SAAS,EACjB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,qBAAqB,CAAC,CAMhC;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,SAAS,EACjB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,SAAS,GAChB,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAKlC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// --- API functions ---
|
|
2
|
+
export async function getMyTimesheets(client, week) {
|
|
3
|
+
return client.request("GET", "/api/users/me/timesheets", undefined, { week });
|
|
4
|
+
}
|
|
5
|
+
export async function getMyMonthHours(client, dateFrom, dateTo) {
|
|
6
|
+
return client.request("GET", "/api/users/me/timesheets/month-hours", undefined, { dateFrom, dateTo });
|
|
7
|
+
}
|
|
8
|
+
export async function createTimesheet(client, data) {
|
|
9
|
+
return client.request("POST", "/api/timesheets", [data]);
|
|
10
|
+
}
|
|
11
|
+
export async function updateTimesheet(client, id, data) {
|
|
12
|
+
return client.request("PUT", `/api/timesheets/${id}`, data);
|
|
13
|
+
}
|
|
14
|
+
export async function deleteTimesheet(client, id) {
|
|
15
|
+
await client.request("DELETE", `/api/timesheets/${id}`);
|
|
16
|
+
}
|
|
17
|
+
export async function getApprovedDates(client) {
|
|
18
|
+
return client.request("GET", "/api/users/me/requests/approved");
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=timesheets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timesheets.js","sourceRoot":"","sources":["../../src/api/timesheets.ts"],"names":[],"mappings":"AAuCA,wBAAwB;AAExB,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAAiB,EACjB,IAAY;IAEZ,OAAO,MAAM,CAAC,OAAO,CACnB,KAAK,EACL,0BAA0B,EAC1B,SAAS,EACT,EAAE,IAAI,EAAE,CACT,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAAiB,EACjB,QAAgB,EAChB,MAAc;IAEd,OAAO,MAAM,CAAC,OAAO,CACnB,KAAK,EACL,sCAAsC,EACtC,SAAS,EACT,EAAE,QAAQ,EAAE,MAAM,EAAE,CACrB,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAAiB,EACjB,IAA4B;IAE5B,OAAO,MAAM,CAAC,OAAO,CACnB,MAAM,EACN,iBAAiB,EACjB,CAAC,IAAI,CAAC,CACP,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAAiB,EACjB,EAAU,EACV,IAA4B;IAE5B,OAAO,MAAM,CAAC,OAAO,CACnB,KAAK,EACL,mBAAmB,EAAE,EAAE,EACvB,IAAI,CACL,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAAiB,EACjB,EAAU;IAEV,MAAM,MAAM,CAAC,OAAO,CAAO,QAAQ,EAAE,mBAAmB,EAAE,EAAE,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,MAAiB;IAEjB,OAAO,MAAM,CAAC,OAAO,CACnB,KAAK,EACL,iCAAiC,CAClC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Config } from "../config.js";
|
|
2
|
+
/**
|
|
3
|
+
* Get a valid access token. Tries in order:
|
|
4
|
+
* 1. Cached in-memory token (if not expired)
|
|
5
|
+
* 2. Stored tokens on disk (refresh if access expired)
|
|
6
|
+
* 3. Full interactive OAuth2 Authorization Code flow
|
|
7
|
+
*/
|
|
8
|
+
export declare function getAccessToken(config: Config): Promise<string>;
|
|
9
|
+
/**
|
|
10
|
+
* Force token refresh. Called on 401 responses.
|
|
11
|
+
*/
|
|
12
|
+
export declare function forceRefresh(config: Config): Promise<string>;
|
|
13
|
+
//# sourceMappingURL=oauth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAM3C;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA+BpE;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAiBlE"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import * as http from "node:http";
|
|
2
|
+
import * as crypto from "node:crypto";
|
|
3
|
+
import { loadTokens, saveTokens, clearTokens } from "./token-storage.js";
|
|
4
|
+
let cachedTokens = null;
|
|
5
|
+
/**
|
|
6
|
+
* Get a valid access token. Tries in order:
|
|
7
|
+
* 1. Cached in-memory token (if not expired)
|
|
8
|
+
* 2. Stored tokens on disk (refresh if access expired)
|
|
9
|
+
* 3. Full interactive OAuth2 Authorization Code flow
|
|
10
|
+
*/
|
|
11
|
+
export async function getAccessToken(config) {
|
|
12
|
+
// 1. Check in-memory cache
|
|
13
|
+
if (cachedTokens && !isAccessExpired(cachedTokens)) {
|
|
14
|
+
return cachedTokens.access_token;
|
|
15
|
+
}
|
|
16
|
+
// 2. Check disk
|
|
17
|
+
const stored = loadTokens();
|
|
18
|
+
if (stored) {
|
|
19
|
+
if (!isAccessExpired(stored)) {
|
|
20
|
+
cachedTokens = stored;
|
|
21
|
+
return stored.access_token;
|
|
22
|
+
}
|
|
23
|
+
// Try refresh
|
|
24
|
+
if (!isRefreshExpired(stored)) {
|
|
25
|
+
try {
|
|
26
|
+
const refreshed = await refreshTokens(config, stored.refresh_token);
|
|
27
|
+
cachedTokens = refreshed;
|
|
28
|
+
saveTokens(refreshed);
|
|
29
|
+
return refreshed.access_token;
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
console.error("[auth] Refresh failed, will re-authenticate:", err);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// 3. Full OAuth2 flow
|
|
37
|
+
const tokens = await interactiveLogin(config);
|
|
38
|
+
cachedTokens = tokens;
|
|
39
|
+
saveTokens(tokens);
|
|
40
|
+
return tokens.access_token;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Force token refresh. Called on 401 responses.
|
|
44
|
+
*/
|
|
45
|
+
export async function forceRefresh(config) {
|
|
46
|
+
const stored = cachedTokens ?? loadTokens();
|
|
47
|
+
if (stored && !isRefreshExpired(stored)) {
|
|
48
|
+
try {
|
|
49
|
+
const refreshed = await refreshTokens(config, stored.refresh_token);
|
|
50
|
+
cachedTokens = refreshed;
|
|
51
|
+
saveTokens(refreshed);
|
|
52
|
+
return refreshed.access_token;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// fall through to interactive login
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
clearTokens();
|
|
59
|
+
const tokens = await interactiveLogin(config);
|
|
60
|
+
cachedTokens = tokens;
|
|
61
|
+
saveTokens(tokens);
|
|
62
|
+
return tokens.access_token;
|
|
63
|
+
}
|
|
64
|
+
function isAccessExpired(tokens) {
|
|
65
|
+
// Consider expired 30 seconds early
|
|
66
|
+
return Date.now() / 1000 >= tokens.expires_at - 30;
|
|
67
|
+
}
|
|
68
|
+
function isRefreshExpired(tokens) {
|
|
69
|
+
// Consider expired 60 seconds early
|
|
70
|
+
return Date.now() / 1000 >= tokens.refresh_expires_at - 60;
|
|
71
|
+
}
|
|
72
|
+
async function refreshTokens(config, refreshToken) {
|
|
73
|
+
const body = new URLSearchParams({
|
|
74
|
+
grant_type: "refresh_token",
|
|
75
|
+
client_id: config.clientId,
|
|
76
|
+
refresh_token: refreshToken,
|
|
77
|
+
});
|
|
78
|
+
const resp = await fetch(config.tokenEndpoint, {
|
|
79
|
+
method: "POST",
|
|
80
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
81
|
+
body: body.toString(),
|
|
82
|
+
});
|
|
83
|
+
if (!resp.ok) {
|
|
84
|
+
const text = await resp.text();
|
|
85
|
+
throw new Error(`Token refresh failed (${resp.status}): ${text}`);
|
|
86
|
+
}
|
|
87
|
+
const data = (await resp.json());
|
|
88
|
+
const now = Date.now() / 1000;
|
|
89
|
+
return {
|
|
90
|
+
access_token: data.access_token,
|
|
91
|
+
refresh_token: data.refresh_token,
|
|
92
|
+
expires_at: now + data.expires_in,
|
|
93
|
+
refresh_expires_at: now + data.refresh_expires_in,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
async function interactiveLogin(config) {
|
|
97
|
+
const state = crypto.randomBytes(16).toString("hex");
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
const server = http.createServer(async (req, res) => {
|
|
100
|
+
try {
|
|
101
|
+
const url = new URL(req.url, `http://localhost`);
|
|
102
|
+
if (url.pathname !== "/callback") {
|
|
103
|
+
res.writeHead(404);
|
|
104
|
+
res.end("Not found");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const code = url.searchParams.get("code");
|
|
108
|
+
const returnedState = url.searchParams.get("state");
|
|
109
|
+
if (returnedState !== state) {
|
|
110
|
+
res.writeHead(400);
|
|
111
|
+
res.end("State mismatch");
|
|
112
|
+
reject(new Error("OAuth state mismatch"));
|
|
113
|
+
server.close();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (!code) {
|
|
117
|
+
const error = url.searchParams.get("error_description") ?? "No code";
|
|
118
|
+
res.writeHead(400);
|
|
119
|
+
res.end(`Auth error: ${error}`);
|
|
120
|
+
reject(new Error(`OAuth error: ${error}`));
|
|
121
|
+
server.close();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// Exchange code for tokens
|
|
125
|
+
const address = server.address();
|
|
126
|
+
const port = typeof address === "object" && address ? address.port : 0;
|
|
127
|
+
const redirectUri = `http://localhost:${port}/callback`;
|
|
128
|
+
const body = new URLSearchParams({
|
|
129
|
+
grant_type: "authorization_code",
|
|
130
|
+
client_id: config.clientId,
|
|
131
|
+
code,
|
|
132
|
+
redirect_uri: redirectUri,
|
|
133
|
+
});
|
|
134
|
+
const tokenResp = await fetch(config.tokenEndpoint, {
|
|
135
|
+
method: "POST",
|
|
136
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
137
|
+
body: body.toString(),
|
|
138
|
+
});
|
|
139
|
+
if (!tokenResp.ok) {
|
|
140
|
+
const text = await tokenResp.text();
|
|
141
|
+
res.writeHead(500);
|
|
142
|
+
res.end(`Token exchange failed: ${text}`);
|
|
143
|
+
reject(new Error(`Token exchange failed (${tokenResp.status}): ${text}`));
|
|
144
|
+
server.close();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const data = (await tokenResp.json());
|
|
148
|
+
const now = Date.now() / 1000;
|
|
149
|
+
const tokens = {
|
|
150
|
+
access_token: data.access_token,
|
|
151
|
+
refresh_token: data.refresh_token,
|
|
152
|
+
expires_at: now + data.expires_in,
|
|
153
|
+
refresh_expires_at: now + data.refresh_expires_in,
|
|
154
|
+
};
|
|
155
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
156
|
+
res.end("<html><body><h1>Авторизация успешна!</h1><p>Можете закрыть это окно и вернуться к MCP-клиенту.</p></body></html>");
|
|
157
|
+
server.close();
|
|
158
|
+
resolve(tokens);
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
res.writeHead(500);
|
|
162
|
+
res.end("Internal error");
|
|
163
|
+
server.close();
|
|
164
|
+
reject(err);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
server.listen(config.callbackPort, "127.0.0.1", async () => {
|
|
168
|
+
const address = server.address();
|
|
169
|
+
const port = typeof address === "object" && address ? address.port : 0;
|
|
170
|
+
const redirectUri = `http://localhost:${port}/callback`;
|
|
171
|
+
const authUrl = new URL(config.authEndpoint);
|
|
172
|
+
authUrl.searchParams.set("response_type", "code");
|
|
173
|
+
authUrl.searchParams.set("client_id", config.clientId);
|
|
174
|
+
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
175
|
+
authUrl.searchParams.set("scope", "openid profile email");
|
|
176
|
+
authUrl.searchParams.set("state", state);
|
|
177
|
+
console.error(`[auth] Opening browser for login: ${authUrl.toString()}`);
|
|
178
|
+
try {
|
|
179
|
+
const open = (await import("open")).default;
|
|
180
|
+
await open(authUrl.toString());
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
console.error(`[auth] Could not open browser. Please open this URL manually:\n${authUrl.toString()}`);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
// Timeout after 2 minutes
|
|
187
|
+
setTimeout(() => {
|
|
188
|
+
server.close();
|
|
189
|
+
reject(new Error("OAuth login timed out after 120 seconds"));
|
|
190
|
+
}, 120_000);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=oauth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface StoredTokens {
|
|
2
|
+
access_token: string;
|
|
3
|
+
refresh_token: string;
|
|
4
|
+
expires_at: number;
|
|
5
|
+
refresh_expires_at: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function loadTokens(): StoredTokens | null;
|
|
8
|
+
export declare function saveTokens(tokens: StoredTokens): void;
|
|
9
|
+
export declare function clearTokens(): void;
|
|
10
|
+
//# sourceMappingURL=token-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-storage.d.ts","sourceRoot":"","sources":["../../src/auth/token-storage.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;CAC9B;AAYD,wBAAgB,UAAU,IAAI,YAAY,GAAG,IAAI,CAahD;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAKrD;AAED,wBAAgB,WAAW,IAAI,IAAI,CAQlC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
function getTokenDir() {
|
|
5
|
+
if (process.platform === "win32" && process.env.APPDATA) {
|
|
6
|
+
return path.join(process.env.APPDATA, "trck-mcp");
|
|
7
|
+
}
|
|
8
|
+
return path.join(os.homedir(), ".trck-mcp");
|
|
9
|
+
}
|
|
10
|
+
const TOKEN_DIR = getTokenDir();
|
|
11
|
+
const TOKEN_FILE = path.join(TOKEN_DIR, "tokens.json");
|
|
12
|
+
export function loadTokens() {
|
|
13
|
+
try {
|
|
14
|
+
if (!fs.existsSync(TOKEN_FILE))
|
|
15
|
+
return null;
|
|
16
|
+
const data = fs.readFileSync(TOKEN_FILE, "utf-8");
|
|
17
|
+
const tokens = JSON.parse(data);
|
|
18
|
+
if (!tokens.access_token || !tokens.refresh_token)
|
|
19
|
+
return null;
|
|
20
|
+
return tokens;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function saveTokens(tokens) {
|
|
27
|
+
if (!fs.existsSync(TOKEN_DIR)) {
|
|
28
|
+
fs.mkdirSync(TOKEN_DIR, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
fs.writeFileSync(TOKEN_FILE, JSON.stringify(tokens, null, 2), "utf-8");
|
|
31
|
+
}
|
|
32
|
+
export function clearTokens() {
|
|
33
|
+
try {
|
|
34
|
+
if (fs.existsSync(TOKEN_FILE)) {
|
|
35
|
+
fs.unlinkSync(TOKEN_FILE);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// ignore
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=token-storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-storage.js","sourceRoot":"","sources":["../../src/auth/token-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAS9B,SAAS,WAAW;IAChB,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QACtD,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,SAAS,GAAG,WAAW,EAAE,CAAC;AAChC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;AAEvD,MAAM,UAAU,UAAU;IACtB,IAAI,CAAC;QACD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,OAAO,IAAI,CAAC;QAE5C,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAClD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAiB,CAAC;QAEhD,IAAI,CAAC,MAAM,CAAC,YAAY,IAAI,CAAC,MAAM,CAAC,aAAa;YAAE,OAAO,IAAI,CAAC;QAE/D,OAAO,MAAM,CAAC;IAClB,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAoB;IAC3C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5B,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACjD,CAAC;IACD,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AAC3E,CAAC;AAED,MAAM,UAAU,WAAW;IACvB,IAAI,CAAC;QACD,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5B,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;QAC9B,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACL,SAAS;IACb,CAAC;AACL,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface Config {
|
|
2
|
+
apiUrl: string;
|
|
3
|
+
keycloakUrl: string;
|
|
4
|
+
keycloakRealm: string;
|
|
5
|
+
clientId: string;
|
|
6
|
+
callbackPort: number;
|
|
7
|
+
tokenEndpoint: string;
|
|
8
|
+
authEndpoint: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function loadConfig(): Config;
|
|
11
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,MAAM;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,UAAU,IAAI,MAAM,CAmBnC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function loadConfig() {
|
|
2
|
+
const apiUrl = process.env.TRCK_API_URL ?? "https://trck-dev.rdclr.ru";
|
|
3
|
+
const keycloakUrl = process.env.TRCK_KEYCLOAK_URL ?? "https://hello.rdclr.ru";
|
|
4
|
+
const keycloakRealm = process.env.TRCK_KEYCLOAK_REALM ?? "rc_realm";
|
|
5
|
+
const clientId = process.env.TRCK_CLIENT_ID ?? "web_tracker";
|
|
6
|
+
const callbackPort = parseInt(process.env.TRCK_CALLBACK_PORT ?? "9876", 10);
|
|
7
|
+
const realmBase = `${keycloakUrl}/realms/${keycloakRealm}/protocol/openid-connect`;
|
|
8
|
+
return {
|
|
9
|
+
apiUrl,
|
|
10
|
+
keycloakUrl,
|
|
11
|
+
keycloakRealm,
|
|
12
|
+
clientId,
|
|
13
|
+
callbackPort,
|
|
14
|
+
tokenEndpoint: `${realmBase}/token`,
|
|
15
|
+
authEndpoint: `${realmBase}/auth`,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,UAAU;IACxB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,2BAA2B,CAAC;IACvE,MAAM,WAAW,GACf,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,wBAAwB,CAAC;IAC5D,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,UAAU,CAAC;IACpE,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,aAAa,CAAC;IAC7D,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,MAAM,EAAE,EAAE,CAAC,CAAC;IAE5E,MAAM,SAAS,GAAG,GAAG,WAAW,WAAW,aAAa,0BAA0B,CAAC;IAEnF,OAAO;QACL,MAAM;QACN,WAAW;QACX,aAAa;QACb,QAAQ;QACR,YAAY;QACZ,aAAa,EAAE,GAAG,SAAS,QAAQ;QACnC,YAAY,EAAE,GAAG,SAAS,OAAO;KAClC,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -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,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { loadConfig } from "./config.js";
|
|
5
|
+
import { ApiClient } from "./api/client.js";
|
|
6
|
+
import { registerTimesheetTools } from "./tools/timesheets.js";
|
|
7
|
+
import { registerProjectTools } from "./tools/projects.js";
|
|
8
|
+
import { registerRequestTools } from "./tools/requests.js";
|
|
9
|
+
async function main() {
|
|
10
|
+
console.error("[trck-mcp] Starting server...");
|
|
11
|
+
const config = loadConfig();
|
|
12
|
+
console.error(`[trck-mcp] API URL: ${config.apiUrl}`);
|
|
13
|
+
const apiClient = new ApiClient(config);
|
|
14
|
+
const server = new McpServer({
|
|
15
|
+
name: "trck",
|
|
16
|
+
version: "1.0.0",
|
|
17
|
+
});
|
|
18
|
+
// Register all tools
|
|
19
|
+
registerTimesheetTools(server, apiClient);
|
|
20
|
+
registerProjectTools(server, apiClient);
|
|
21
|
+
registerRequestTools(server, apiClient);
|
|
22
|
+
console.error("[trck-mcp] Tools registered: 9 tools");
|
|
23
|
+
// Connect via stdio transport
|
|
24
|
+
const transport = new StdioServerTransport();
|
|
25
|
+
await server.connect(transport);
|
|
26
|
+
console.error("[trck-mcp] Server running on stdio");
|
|
27
|
+
}
|
|
28
|
+
main().catch((err) => {
|
|
29
|
+
console.error("[trck-mcp] Fatal error:", err);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAE3D,KAAK,UAAU,IAAI;IACf,OAAO,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;IAE/C,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,OAAO,CAAC,KAAK,CAAC,uBAAuB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAEtD,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC,MAAM,CAAC,CAAC;IAExC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QACzB,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,OAAO;KACnB,CAAC,CAAC;IAEH,qBAAqB;IACrB,sBAAsB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAC1C,oBAAoB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACxC,oBAAoB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAExC,OAAO,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAEtD,8BAA8B;IAC9B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,OAAO,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;AACxD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACjB,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC;IAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
export declare function toolResult(text: string): CallToolResult;
|
|
3
|
+
export declare function toolError(err: unknown): CallToolResult;
|
|
4
|
+
export declare function toolJson(data: unknown, prefix?: string): CallToolResult;
|
|
5
|
+
//# sourceMappingURL=tool-result.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-result.d.ts","sourceRoot":"","sources":["../../src/lib/tool-result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAEvD;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,CAKtD;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,cAAc,CAGvE"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function toolResult(text) {
|
|
2
|
+
return { content: [{ type: "text", text }] };
|
|
3
|
+
}
|
|
4
|
+
export function toolError(err) {
|
|
5
|
+
return {
|
|
6
|
+
isError: true,
|
|
7
|
+
content: [{ type: "text", text: `Ошибка: ${String(err)}` }],
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function toolJson(data, prefix) {
|
|
11
|
+
const json = JSON.stringify(data, null, 2);
|
|
12
|
+
return toolResult(prefix ? `${prefix}\n${json}` : json);
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=tool-result.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-result.js","sourceRoot":"","sources":["../../src/lib/tool-result.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,UAAU,CAAC,IAAY;IACnC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,GAAY;IAClC,OAAO;QACH,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;KAC9D,CAAC;AACN,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAa,EAAE,MAAe;IACnD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC3C,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAC5D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/tools/projects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAIlD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,SAAS,GAChB,IAAI,CAwBN"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { getMyProjects } from "../api/projects.js";
|
|
2
|
+
import { toolJson, toolError } from "../lib/tool-result.js";
|
|
3
|
+
export function registerProjectTools(server, client) {
|
|
4
|
+
server.tool("get_my_projects", "Получить список проектов текущего пользователя вместе с задачами. Возвращает для каждого проекта: id, name, status, tasks (с id и name). Используй этот tool чтобы найти правильные projectId и taskId перед созданием записи времени (create_timesheet). Результат кешируется на время сессии.", {}, { readOnlyHint: true, destructiveHint: false, idempotentHint: true }, async () => {
|
|
5
|
+
try {
|
|
6
|
+
const projects = await getMyProjects(client);
|
|
7
|
+
const formatted = projects.map((p) => ({
|
|
8
|
+
projectId: p.id,
|
|
9
|
+
projectName: p.name,
|
|
10
|
+
status: p.status,
|
|
11
|
+
tasks: (p.tasks ?? []).map((t) => ({
|
|
12
|
+
taskId: t.id,
|
|
13
|
+
taskName: t.name,
|
|
14
|
+
})),
|
|
15
|
+
}));
|
|
16
|
+
return toolJson(formatted);
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
return toolError(err);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=projects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projects.js","sourceRoot":"","sources":["../../src/tools/projects.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAE5D,MAAM,UAAU,oBAAoB,CAClC,MAAiB,EACjB,MAAiB;IAEjB,MAAM,CAAC,IAAI,CACT,iBAAiB,EACjB,iSAAiS,EACjS,EAAE,EACF,EAAE,YAAY,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,EACpE,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC;YAC7C,MAAM,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACrC,SAAS,EAAE,CAAC,CAAC,EAAE;gBACf,WAAW,EAAE,CAAC,CAAC,IAAI;gBACnB,MAAM,EAAE,CAAC,CAAC,MAAM;gBAChB,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;oBACjC,MAAM,EAAE,CAAC,CAAC,EAAE;oBACZ,QAAQ,EAAE,CAAC,CAAC,IAAI;iBACjB,CAAC,CAAC;aACJ,CAAC,CAAC,CAAC;YACJ,OAAO,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC7B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC;QACxB,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requests.d.ts","sourceRoot":"","sources":["../../src/tools/requests.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAwBlD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,SAAS,GAChB,IAAI,CA6BN"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getMyRequests, createRequest } from "../api/requests.js";
|
|
3
|
+
import { toolJson, toolError } from "../lib/tool-result.js";
|
|
4
|
+
// --- Schemas ---
|
|
5
|
+
const CreateRequestSchema = {
|
|
6
|
+
projectId: z
|
|
7
|
+
.number()
|
|
8
|
+
.int()
|
|
9
|
+
.min(1)
|
|
10
|
+
.describe("ID проекта, для которого нужно открыть дату"),
|
|
11
|
+
dateToOpen: z
|
|
12
|
+
.string()
|
|
13
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
14
|
+
.describe("Дата, которую нужно открыть (YYYY-MM-DD)"),
|
|
15
|
+
comment: z
|
|
16
|
+
.string()
|
|
17
|
+
.min(1)
|
|
18
|
+
.describe("Причина / комментарий к запросу (обязательно)"),
|
|
19
|
+
};
|
|
20
|
+
// --- Registration ---
|
|
21
|
+
export function registerRequestTools(server, client) {
|
|
22
|
+
server.tool("get_my_requests", "Получить список запросов текущего пользователя на открытие дат. Показывает статус каждого запроса (NEW, ACCEPTED, DECLINED и т.д.). Используй для проверки, был ли уже отправлен запрос на нужную дату.", {}, { readOnlyHint: true, destructiveHint: false, idempotentHint: true }, async () => {
|
|
23
|
+
try {
|
|
24
|
+
return toolJson(await getMyRequests(client));
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
return toolError(err);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
server.tool("create_request", "Создать запрос на открытие даты для трекинга времени за прошлый период. Необходим когда пользователь хочет залогировать время за дату, которая уже закрыта. После одобрения запроса менеджером, дата станет доступна для трекинга.", CreateRequestSchema, { readOnlyHint: false, destructiveHint: false, idempotentHint: false }, async ({ projectId, dateToOpen, comment }) => {
|
|
31
|
+
try {
|
|
32
|
+
const result = await createRequest(client, { projectId, dateToOpen, comment });
|
|
33
|
+
return toolJson(result, "Запрос создан:");
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
return toolError(err);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=requests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requests.js","sourceRoot":"","sources":["../../src/tools/requests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAE5D,kBAAkB;AAElB,MAAM,mBAAmB,GAAG;IAC1B,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CAAC,6CAA6C,CAAC;IAC1D,UAAU,EAAE,CAAC;SACV,MAAM,EAAE;SACR,KAAK,CAAC,qBAAqB,CAAC;SAC5B,QAAQ,CAAC,0CAA0C,CAAC;IACvD,OAAO,EAAE,CAAC;SACP,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CAAC,+CAA+C,CAAC;CAC7D,CAAC;AAEF,uBAAuB;AAEvB,MAAM,UAAU,oBAAoB,CAClC,MAAiB,EACjB,MAAiB;IAEjB,MAAM,CAAC,IAAI,CACT,iBAAiB,EACjB,yMAAyM,EACzM,EAAE,EACF,EAAE,YAAY,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,EACpE,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,OAAO,QAAQ,CAAC,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;QAC/C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC;QACxB,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,gBAAgB,EAChB,oOAAoO,EACpO,mBAAmB,EACnB,EAAE,YAAY,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,EACtE,KAAK,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,EAAE;QAC3C,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;YAC/E,OAAO,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC;QACxB,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timesheets.d.ts","sourceRoot":"","sources":["../../src/tools/timesheets.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAoFlD,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,SAAS,GAChB,IAAI,CAuFN"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getMyTimesheets, getMyMonthHours, createTimesheet, updateTimesheet, deleteTimesheet, getApprovedDates, } from "../api/timesheets.js";
|
|
3
|
+
import { toolJson, toolResult, toolError } from "../lib/tool-result.js";
|
|
4
|
+
// --- Schemas ---
|
|
5
|
+
const GetMyTimesheetsSchema = {
|
|
6
|
+
week: z
|
|
7
|
+
.string()
|
|
8
|
+
.regex(/^\d{4}-W(0[1-9]|[1-4]\d|5[0-3])$/)
|
|
9
|
+
.describe("Неделя в формате ISO 8601: YYYY-Www (например '2026-W11')"),
|
|
10
|
+
};
|
|
11
|
+
const GetMyMonthHoursSchema = {
|
|
12
|
+
dateFrom: z
|
|
13
|
+
.string()
|
|
14
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
15
|
+
.describe("Дата начала периода (YYYY-MM-DD)"),
|
|
16
|
+
dateTo: z
|
|
17
|
+
.string()
|
|
18
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
19
|
+
.describe("Дата конца периода (YYYY-MM-DD)"),
|
|
20
|
+
};
|
|
21
|
+
const CreateTimesheetSchema = {
|
|
22
|
+
projectId: z
|
|
23
|
+
.number()
|
|
24
|
+
.int()
|
|
25
|
+
.min(1)
|
|
26
|
+
.describe("ID проекта (получи через get_my_projects)"),
|
|
27
|
+
taskId: z
|
|
28
|
+
.number()
|
|
29
|
+
.int()
|
|
30
|
+
.min(1)
|
|
31
|
+
.describe("ID задачи в проекте (получи через get_my_projects)"),
|
|
32
|
+
date: z
|
|
33
|
+
.string()
|
|
34
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
35
|
+
.describe("Дата, на которую трекать время (YYYY-MM-DD)"),
|
|
36
|
+
time: z
|
|
37
|
+
.number()
|
|
38
|
+
.int()
|
|
39
|
+
.min(1)
|
|
40
|
+
.describe("Количество часов (целое число, минимум 1)"),
|
|
41
|
+
description: z
|
|
42
|
+
.string()
|
|
43
|
+
.optional()
|
|
44
|
+
.describe("Описание проделанной работы (необязательно)"),
|
|
45
|
+
};
|
|
46
|
+
const UpdateTimesheetSchema = {
|
|
47
|
+
id: z
|
|
48
|
+
.number()
|
|
49
|
+
.int()
|
|
50
|
+
.min(1)
|
|
51
|
+
.describe("ID записи таймшита (получи через get_my_timesheets)"),
|
|
52
|
+
time: z
|
|
53
|
+
.number()
|
|
54
|
+
.int()
|
|
55
|
+
.min(1)
|
|
56
|
+
.describe("Новое количество часов (целое число, минимум 1)"),
|
|
57
|
+
description: z
|
|
58
|
+
.string()
|
|
59
|
+
.optional()
|
|
60
|
+
.describe("Новое описание (необязательно)"),
|
|
61
|
+
};
|
|
62
|
+
const DeleteTimesheetSchema = {
|
|
63
|
+
id: z
|
|
64
|
+
.number()
|
|
65
|
+
.int()
|
|
66
|
+
.min(1)
|
|
67
|
+
.describe("ID записи таймшита для удаления"),
|
|
68
|
+
};
|
|
69
|
+
// --- Registration ---
|
|
70
|
+
export function registerTimesheetTools(server, client) {
|
|
71
|
+
server.tool("get_my_timesheets", "Получить таймшиты (записи времени) текущего пользователя за указанную неделю. Возвращает массив записей с projectId, taskId, date, time (часы), description. Параметр week в формате ISO 8601: YYYY-Www (например '2026-W11').", GetMyTimesheetsSchema, { readOnlyHint: true, destructiveHint: false, idempotentHint: true }, async ({ week }) => {
|
|
72
|
+
try {
|
|
73
|
+
return toolJson(await getMyTimesheets(client, week));
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
return toolError(err);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
server.tool("get_my_month_hours", "Получить суммарное количество часов по дням за указанный период. Полезно для понимания общей картины — сколько часов залогировано за месяц.", GetMyMonthHoursSchema, { readOnlyHint: true, destructiveHint: false, idempotentHint: true }, async ({ dateFrom, dateTo }) => {
|
|
80
|
+
try {
|
|
81
|
+
return toolJson(await getMyMonthHours(client, dateFrom, dateTo));
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
return toolError(err);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
server.tool("create_timesheet", "Создать новую запись времени (таймшит). Перед вызовом используй get_my_projects чтобы узнать корректные projectId и taskId. Время указывается в целых часах (минимум 1). Дата в формате YYYY-MM-DD.", CreateTimesheetSchema, { readOnlyHint: false, destructiveHint: false, idempotentHint: false }, async ({ projectId, taskId, date, time, description }) => {
|
|
88
|
+
try {
|
|
89
|
+
const result = await createTimesheet(client, { projectId, taskId, date, time, description });
|
|
90
|
+
return toolJson(result[0], "Запись создана:");
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
return toolError(err);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
server.tool("update_timesheet", "Обновить существующую запись времени. Можно изменить количество часов и/или описание. ID записи можно узнать через get_my_timesheets.", UpdateTimesheetSchema, { readOnlyHint: false, destructiveHint: false, idempotentHint: true }, async ({ id, time, description }) => {
|
|
97
|
+
try {
|
|
98
|
+
const result = await updateTimesheet(client, id, { time, description });
|
|
99
|
+
return toolJson(result, "Запись обновлена:");
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
return toolError(err);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
server.tool("delete_timesheet", "Удалить запись времени по ID. Это мягкое удаление. ID записи можно узнать через get_my_timesheets.", DeleteTimesheetSchema, { readOnlyHint: false, destructiveHint: true, idempotentHint: true }, async ({ id }) => {
|
|
106
|
+
try {
|
|
107
|
+
await deleteTimesheet(client, id);
|
|
108
|
+
return toolResult(`Запись #${id} удалена.`);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
return toolError(err);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
server.tool("get_approved_dates", "Получить список одобренных дат, в которые разрешено трекать время за прошлые периоды. Используй перед create_timesheet на прошлые даты, чтобы проверить, открыта ли дата.", {}, { readOnlyHint: true, destructiveHint: false, idempotentHint: true }, async () => {
|
|
115
|
+
try {
|
|
116
|
+
return toolJson(await getApprovedDates(client));
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
return toolError(err);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=timesheets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timesheets.js","sourceRoot":"","sources":["../../src/tools/timesheets.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@redcollar/trck-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for trck time-tracking service",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=18"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"mcp",
|
|
12
|
+
"modelcontextprotocol",
|
|
13
|
+
"trck",
|
|
14
|
+
"time-tracking"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"start": "node dist/index.js",
|
|
19
|
+
"dev": "tsc --watch",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"test:watch": "vitest"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
25
|
+
"open": "^10.1.0",
|
|
26
|
+
"zod": "^3.24.2"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^22.13.10",
|
|
30
|
+
"typescript": "^5.8.2",
|
|
31
|
+
"vitest": "^4.1.0"
|
|
32
|
+
},
|
|
33
|
+
"bin": {
|
|
34
|
+
"trck-mcp": "dist/index.js"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist"
|
|
38
|
+
]
|
|
39
|
+
}
|