MCP: api_get — read-only GET к API CRM для проверки эндпоинтов и структур ответов
ИИ-агент может до написания виджета проверить, что эндпоинт существует и сверить точные имена полей (раньше выдумывал URL и shape — страницы не работали). Same-origin /api/*, GET only, доступен в любом режиме ключа; запрос идёт от пользователя-владельца ключа.
This commit is contained in:
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user