diff --git a/server/mcp.ts b/server/mcp.ts index 6301be8..414827f 100644 --- a/server/mcp.ts +++ b/server/mcp.ts @@ -51,6 +51,7 @@ import { buildDataTableTree } from "./utils/data-table-tree"; import { sendTaskMessage, SendTaskMessageError } from "./services/task-message.service"; import { tasksMinimalCache, formsCache } from "./utils/cache"; import { evaluateAutoTransitions } from "./utils/auto-transitions"; +import { signAccessToken } from "./utils/jwt"; import { notifyTaskAssigned } from "./utils/notifyAssignee"; import { eventBus, publishNotificationSSE, buildSystemFieldValues, buildTableRowMap, formatFieldValueForTitle, resolveTaskFieldTitles, type FieldTitleContext } from "./routes/shared"; import { indexFormAsync, indexTaskAsync } from "./routes/task-helpers"; @@ -134,6 +135,7 @@ const READ_TOOLS: readonly string[] = [ 'get_custom_page', 'get_js_coding_reference', 'validate_custom_page_code', + 'api_get', 'semantic_search', 'get_organization_context', 'list_directories', @@ -1900,6 +1902,46 @@ RULES for tab component code: } ); + // api_get — read-only GET к API CRM: проверка существования эндпоинта и точной структуры + // ответа ДО написания виджета (предотвращает выдуманные URL и поля). Доступен в любом режиме ключа. + register( + "api_get", + { + title: "API GET (read-only)", + description: `Perform an authenticated read-only GET against the CRM API and return { status, json }. +USE BEFORE WRITING WIDGET CODE: verify the endpoint EXISTS and inspect the EXACT JSON response shape — +do not invent URLs or field names. status 404 means the URL is wrong (check the spec); +a JSON shape different from your assumption means your parsing code is wrong. +url must start with /api/ (same-origin only, no external hosts). GET only.`, + inputSchema: { + url: z.string().regex(/^\/api\/[A-Za-z0-9\-/_]+(\?[^\s]*)?$/).describe("Path starting with /api/, e.g. /api/di2/nds/summary or /api/debt/analytics?page=1"), + }, + }, + async ({ url }) => { + const actor = await getActor(); + const token = signAccessToken({ + userId: actor.user.id, + organizationId, + appRole: actor.user.appRole ?? "user", + }); + const port = process.env.PORT || 5000; + const r = await fetch(`http://127.0.0.1:${port}${url}`, { + headers: { Authorization: `Bearer ${token}`, Accept: "application/json" }, + }); + const text = await r.text(); + let json: unknown; + try { + json = JSON.parse(text); + } catch { + json = text.slice(0, 500); + } + const body = JSON.stringify({ status: r.status, json }, null, 2); + return { + content: [{ type: "text" as const, text: body.length > 30000 ? body.slice(0, 30000) + "\n…(truncated)" : body }], + }; + } + ); + // validate_custom_page_code — статическая проверка кода ДО сохранения (read-скоуп, без мутаций) register( "validate_custom_page_code",