@hubex/mcp 0.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.
- package/.env.example +54 -0
- package/CONNECTING.md +260 -0
- package/README.md +266 -0
- package/dist/auth.js +226 -0
- package/dist/config.js +120 -0
- package/dist/generated/manifest.js +3643 -0
- package/dist/guides/field-notes.json +27 -0
- package/dist/guides/loader.js +32 -0
- package/dist/guides/service-map.js +6 -0
- package/dist/http.js +93 -0
- package/dist/index/store.js +134 -0
- package/dist/index/types.js +1 -0
- package/dist/index.js +20 -0
- package/dist/paths.js +20 -0
- package/dist/pii/fields.js +100 -0
- package/dist/pii/mask.js +50 -0
- package/dist/pii/strategies.js +88 -0
- package/dist/schema/build-index.js +67 -0
- package/dist/schema/deref.js +87 -0
- package/dist/schema/describe.js +65 -0
- package/dist/server.js +62 -0
- package/dist/token-prompt.js +41 -0
- package/dist/tools/curated.js +151 -0
- package/dist/tools/discovery.js +187 -0
- package/dist/tools/guides.js +126 -0
- package/dist/tools/registry.js +37 -0
- package/dist/tools/request.js +170 -0
- package/dist/tools/types.js +1 -0
- package/docs/guides/assets.md +334 -0
- package/docs/guides/attributes.md +125 -0
- package/docs/guides/checklisttemplates.md +154 -0
- package/docs/guides/companies.md +57 -0
- package/docs/guides/dictionaries.md +64 -0
- package/docs/guides/lifecycle.md +263 -0
- package/docs/guides/materials.md +135 -0
- package/docs/guides/notifications.md +184 -0
- package/docs/guides/roles.md +125 -0
- package/docs/guides/sla.md +130 -0
- package/docs/guides/start.md +67 -0
- package/docs/guides/taskchecklists.md +149 -0
- package/docs/guides/taskcreate.md +260 -0
- package/docs/guides/taskedit.md +277 -0
- package/docs/guides/tasktypes.md +156 -0
- package/docs/guides/users.md +71 -0
- package/generated/index.dev.json +18776 -0
- package/generated/index.prod.json +18858 -0
- package/package.json +48 -0
- package/swagger/dev/ADM.json +27777 -0
- package/swagger/dev/AUTH.json +1739 -0
- package/swagger/dev/AUTHN.json +1250 -0
- package/swagger/dev/AUTHZ.json +1404 -0
- package/swagger/dev/CM.json +309 -0
- package/swagger/dev/COMMON.json +6543 -0
- package/swagger/dev/ES.json +28029 -0
- package/swagger/dev/EXPORT.json +4575 -0
- package/swagger/dev/IMPORT.json +1479 -0
- package/swagger/dev/LIC.json +224 -0
- package/swagger/dev/MSG.json +7883 -0
- package/swagger/dev/NEWS.json +348 -0
- package/swagger/dev/PA.json +5981 -0
- package/swagger/dev/PMP.json +3196 -0
- package/swagger/dev/PROXY.json +416 -0
- package/swagger/dev/REPORT.json +3921 -0
- package/swagger/dev/SC.json +3771 -0
- package/swagger/dev/SLA.json +2837 -0
- package/swagger/dev/TSTG.json +4981 -0
- package/swagger/dev/UI.json +4720 -0
- package/swagger/dev/WH.json +16796 -0
- package/swagger/dev/WORK.json +36024 -0
- package/swagger/dev/WSP.json +1612 -0
- package/swagger/prod/ADM.json +27777 -0
- package/swagger/prod/AUTH.json +1308 -0
- package/swagger/prod/AUTHN.json +2710 -0
- package/swagger/prod/AUTHZ.json +896 -0
- package/swagger/prod/CM.json +162 -0
- package/swagger/prod/COMMON.json +4910 -0
- package/swagger/prod/ES.json +28029 -0
- package/swagger/prod/EXPORT.json +3091 -0
- package/swagger/prod/LIC.json +123 -0
- package/swagger/prod/MSG.json +6239 -0
- package/swagger/prod/NEWS.json +295 -0
- package/swagger/prod/PA.json +5123 -0
- package/swagger/prod/PMP.json +2978 -0
- package/swagger/prod/PROXY.json +250 -0
- package/swagger/prod/REPORT.json +3729 -0
- package/swagger/prod/SC.json +3771 -0
- package/swagger/prod/SLA.json +2201 -0
- package/swagger/prod/TSTG.json +4220 -0
- package/swagger/prod/UI.json +3879 -0
- package/swagger/prod/WH.json +16730 -0
- package/swagger/prod/WORK.json +35994 -0
- package/swagger/prod/WSP.json +1468 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Знание о бизнес-процессах HubEx в двух формах: markdown-гайды под явный
|
|
3
|
+
* запрос и аннотации полей, подмешиваемые в вывод describe_endpoint.
|
|
4
|
+
*/
|
|
5
|
+
import { loadFieldNotes, loadGuide } from "../guides/loader.js";
|
|
6
|
+
import { okResult, failResult } from "./registry.js";
|
|
7
|
+
/** Топики сгруппированы по областям: группировка попадает в описание инструмента,
|
|
8
|
+
* чтобы выбор топика не требовал отдельного «хаб»-гайда и лишнего вызова. */
|
|
9
|
+
const TOPIC_GROUPS = [
|
|
10
|
+
{
|
|
11
|
+
title: "База",
|
|
12
|
+
topics: {
|
|
13
|
+
start: "URL, заголовки, авторизация, формат ответов, порядок работы с этим сервером",
|
|
14
|
+
dictionaries: "справочники: где брать идентификаторы для полей",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
title: "Заявки",
|
|
19
|
+
topics: {
|
|
20
|
+
taskcreate: "создание заявки: метаданные формы, обязательные поля, справочники, порядок вызовов",
|
|
21
|
+
taskedit: "редактирование заявки: правка полей, стадии и статусы, работы и материалы, удаление",
|
|
22
|
+
taskchecklists: "чек-листы в заявке: заполнение пунктов, вложения, сохранение и переход стадии",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
title: "Объекты, контрагенты, склад",
|
|
27
|
+
topics: {
|
|
28
|
+
assets: "объекты обслуживания: справочники, создание с локацией и связями, редактирование",
|
|
29
|
+
companies: "компании-заказчики",
|
|
30
|
+
users: "пользователи, роли и навыки исполнителей",
|
|
31
|
+
materials: "материалы склада: создание и изменение, валюта цены, единицы измерения",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
title: "Настройка тенанта",
|
|
36
|
+
topics: {
|
|
37
|
+
lifecycle: "жизненный цикл заявки: стадии, маршрут типа заявки, переходы, переопределения для ролей, статусы",
|
|
38
|
+
tasktypes: "типы заявок: маска номера, срок закрытия, виды работ, участки, критичности, актуальности, форма заявки",
|
|
39
|
+
roles: "роли и полномочия: API/UI/расширенные, приложения, плагины, доступ к полям заявки",
|
|
40
|
+
notifications: "оповещения: шаблоны уведомлений, правила выбора получателей, триггеры, webhooks, приём заявок с почты",
|
|
41
|
+
sla: "правила расчёта крайнего срока закрытия и графики работ",
|
|
42
|
+
attributes: "дополнительные поля: типы, списки значений, привязка к сущностям",
|
|
43
|
+
checklisttemplates: "шаблоны чек-листов: пункты, атрибуты и привязки к объектам и видам работ",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
export const GUIDE_TOPICS = TOPIC_GROUPS.flatMap((g) => Object.keys(g.topics));
|
|
48
|
+
function guideTool() {
|
|
49
|
+
const topicList = TOPIC_GROUPS.map((g) => `${g.title}: ` +
|
|
50
|
+
Object.entries(g.topics)
|
|
51
|
+
.map(([topic, summary]) => `${topic} — ${summary}`)
|
|
52
|
+
.join("; ")).join(". ");
|
|
53
|
+
return {
|
|
54
|
+
definition: {
|
|
55
|
+
name: "hubex_get_guide",
|
|
56
|
+
description: "Короткий гайд по бизнес-процессу HubEx: порядок вызовов, обязательные поля, подводные камни. " +
|
|
57
|
+
`Читай перед первой работой с областью. Топики по областям. ${topicList}.`,
|
|
58
|
+
inputSchema: {
|
|
59
|
+
type: "object",
|
|
60
|
+
properties: {
|
|
61
|
+
topic: {
|
|
62
|
+
type: "string",
|
|
63
|
+
enum: GUIDE_TOPICS,
|
|
64
|
+
description: "Какой процесс описать.",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
required: ["topic"],
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
async execute(args) {
|
|
71
|
+
try {
|
|
72
|
+
const topic = String(args.topic ?? "");
|
|
73
|
+
if (!GUIDE_TOPICS.includes(topic)) {
|
|
74
|
+
throw new Error(`Неизвестный топик "${topic}". Доступны: ${GUIDE_TOPICS.join(", ")}.`);
|
|
75
|
+
}
|
|
76
|
+
return okResult(loadGuide(topic));
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
return failResult(err);
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export function buildGuideTools() {
|
|
85
|
+
return [guideTool()];
|
|
86
|
+
}
|
|
87
|
+
function noteText(note) {
|
|
88
|
+
const parts = [note.description];
|
|
89
|
+
if (note.required)
|
|
90
|
+
parts.push("Обязателен.");
|
|
91
|
+
if (note.lookup)
|
|
92
|
+
parts.push(`Справочник: ${note.lookup}`);
|
|
93
|
+
return parts.join(" ");
|
|
94
|
+
}
|
|
95
|
+
function annotateProperties(properties, notes) {
|
|
96
|
+
if (!properties)
|
|
97
|
+
return;
|
|
98
|
+
for (const [name, schema] of Object.entries(properties)) {
|
|
99
|
+
if (!schema || typeof schema !== "object")
|
|
100
|
+
continue;
|
|
101
|
+
const note = notes[name];
|
|
102
|
+
if (note) {
|
|
103
|
+
const existing = typeof schema.description === "string" ? schema.description.trim() : "";
|
|
104
|
+
schema.description = existing ? `${existing} ${noteText(note)}` : noteText(note);
|
|
105
|
+
}
|
|
106
|
+
if (schema.properties)
|
|
107
|
+
annotateProperties(schema.properties, notes);
|
|
108
|
+
if (schema.items?.properties)
|
|
109
|
+
annotateProperties(schema.items.properties, notes);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** Подмешать ручные аннотации в схему, не затирая описания из swagger. */
|
|
113
|
+
export function annotateDescription(description) {
|
|
114
|
+
const notes = loadFieldNotes();
|
|
115
|
+
const out = {
|
|
116
|
+
...description,
|
|
117
|
+
pathParams: structuredClone(description.pathParams),
|
|
118
|
+
queryParams: structuredClone(description.queryParams),
|
|
119
|
+
body: description.body ? structuredClone(description.body) : undefined,
|
|
120
|
+
};
|
|
121
|
+
annotateProperties(out.pathParams, notes);
|
|
122
|
+
annotateProperties(out.queryParams, notes);
|
|
123
|
+
if (out.body?.properties)
|
|
124
|
+
annotateProperties(out.body.properties, notes);
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Собирает реестр из 13 фиксированных инструментов: курируемых, гайдов, discovery и исполнения.
|
|
3
|
+
*/
|
|
4
|
+
import { HubexApiError } from "../http.js";
|
|
5
|
+
import { buildDiscoveryTools } from "./discovery.js";
|
|
6
|
+
import { buildRequestTools } from "./request.js";
|
|
7
|
+
import { buildCuratedTools } from "./curated.js";
|
|
8
|
+
import { buildGuideTools, annotateDescription } from "./guides.js";
|
|
9
|
+
function ok(data) {
|
|
10
|
+
if (typeof data === "string")
|
|
11
|
+
return { text: data };
|
|
12
|
+
// JSON.stringify(undefined) === undefined, а MCP требует строку в content[0].text.
|
|
13
|
+
return { text: JSON.stringify(data ?? null, null, 2) };
|
|
14
|
+
}
|
|
15
|
+
function fail(err) {
|
|
16
|
+
const message = err instanceof HubexApiError ? err.message : err instanceof Error ? err.message : String(err);
|
|
17
|
+
return { text: message, isError: true };
|
|
18
|
+
}
|
|
19
|
+
/** Собрать все 13 инструментов: курируемые, гайды, discovery и исполнение. */
|
|
20
|
+
export function buildRegistry(deps) {
|
|
21
|
+
const tools = [
|
|
22
|
+
...buildCuratedTools(deps.client, deps.config, deps.auth),
|
|
23
|
+
...buildGuideTools(),
|
|
24
|
+
...buildDiscoveryTools({
|
|
25
|
+
index: deps.index,
|
|
26
|
+
schemas: deps.schemas,
|
|
27
|
+
methods: deps.config.methods,
|
|
28
|
+
annotate: annotateDescription,
|
|
29
|
+
}),
|
|
30
|
+
...buildRequestTools({ client: deps.client, index: deps.index, config: deps.config }),
|
|
31
|
+
];
|
|
32
|
+
const registry = new Map();
|
|
33
|
+
for (const tool of tools)
|
|
34
|
+
registry.set(tool.definition.name, tool);
|
|
35
|
+
return registry;
|
|
36
|
+
}
|
|
37
|
+
export { ok as okResult, fail as failResult };
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Исполнение произвольного эндпоинта HubEx. Инструменты разделены по группам
|
|
3
|
+
* HTTP-методов и принимают только endpointId: метод выводится из него, поэтому
|
|
4
|
+
* инструмент не может выполнить операцию чужой группы.
|
|
5
|
+
*/
|
|
6
|
+
import { METHOD_GROUPS, enabledGroups } from "./discovery.js";
|
|
7
|
+
import { okResult, failResult } from "./registry.js";
|
|
8
|
+
const GROUP_DESCRIPTIONS = {
|
|
9
|
+
read: "Выполнить GET-запрос к HubEx по идентификатору эндпоинта.",
|
|
10
|
+
write: "Выполнить POST/PUT/PATCH-запрос к HubEx по идентификатору эндпоинта. ИЗМЕНЯЕТ ДАННЫЕ. " +
|
|
11
|
+
"Схему тела бери из hubex_describe_endpoint.",
|
|
12
|
+
delete: "Выполнить DELETE-запрос к HubEx по идентификатору эндпоинта. УДАЛЯЕТ ДАННЫЕ. " +
|
|
13
|
+
"Часть эндпоинтов удаляет пачкой и требует список в body — схему бери из hubex_describe_endpoint.",
|
|
14
|
+
head: "Выполнить HEAD-запрос к HubEx: проверить существование ресурса без загрузки тела ответа.",
|
|
15
|
+
};
|
|
16
|
+
/** Группа, к которой относится метод — для подсказки «зови другой инструмент». */
|
|
17
|
+
function groupOf(method) {
|
|
18
|
+
for (const [group, methods] of Object.entries(METHOD_GROUPS)) {
|
|
19
|
+
if (methods.includes(method))
|
|
20
|
+
return group;
|
|
21
|
+
}
|
|
22
|
+
throw new Error(`Метод ${method} не принадлежит ни одной группе`);
|
|
23
|
+
}
|
|
24
|
+
export function applyDefaults(body, defaults) {
|
|
25
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) {
|
|
26
|
+
return { body, applied: [] };
|
|
27
|
+
}
|
|
28
|
+
const out = { ...body };
|
|
29
|
+
const applied = [];
|
|
30
|
+
for (const [key, value] of Object.entries(defaults)) {
|
|
31
|
+
if (value === undefined)
|
|
32
|
+
continue;
|
|
33
|
+
if (out[key] === undefined) {
|
|
34
|
+
out[key] = value;
|
|
35
|
+
applied.push(key);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return { body: out, applied };
|
|
39
|
+
}
|
|
40
|
+
function resolvePath(entry, pathParams) {
|
|
41
|
+
const missing = entry.pathParams.filter((name) => {
|
|
42
|
+
const v = pathParams[name];
|
|
43
|
+
return v === undefined || v === null || v === "";
|
|
44
|
+
});
|
|
45
|
+
if (missing.length > 0) {
|
|
46
|
+
throw new Error(`Не хватает обязательных path-параметров для ${entry.id}: ${missing.join(", ")}. ` +
|
|
47
|
+
`Передай их в объекте pathParams.`);
|
|
48
|
+
}
|
|
49
|
+
let path = entry.path;
|
|
50
|
+
for (const name of entry.pathParams) {
|
|
51
|
+
path = path.replace(`{${name}}`, encodeURIComponent(String(pathParams[name])));
|
|
52
|
+
}
|
|
53
|
+
const unresolved = path.match(/\{[^}]+\}/g);
|
|
54
|
+
if (unresolved) {
|
|
55
|
+
throw new Error(`Не удалось подставить параметры пути для ${entry.id}: остались незаменённые ${unresolved.join(", ")}. ` +
|
|
56
|
+
`Объявленные параметры: ${entry.pathParams.join(", ")}. Проверь точное написание имён (регистр может отличаться от текста в пути) через hubex_describe_endpoint.`);
|
|
57
|
+
}
|
|
58
|
+
return path;
|
|
59
|
+
}
|
|
60
|
+
function requestTool(group, deps) {
|
|
61
|
+
const isWrite = group === "write";
|
|
62
|
+
// DELETE в HubEx нередко удаляет пачкой и принимает список в теле запроса.
|
|
63
|
+
const acceptsBody = group === "write" || group === "delete";
|
|
64
|
+
const properties = {
|
|
65
|
+
endpointId: {
|
|
66
|
+
type: "string",
|
|
67
|
+
description: "Идентификатор вида SERVICE:METHOD:/path из hubex_search_*_endpoints или гайда.",
|
|
68
|
+
},
|
|
69
|
+
pathParams: {
|
|
70
|
+
type: "object",
|
|
71
|
+
description: "Значения параметров пути, например { \"taskID\": 42 }.",
|
|
72
|
+
},
|
|
73
|
+
query: { type: "object", description: "Query-параметры запроса." },
|
|
74
|
+
};
|
|
75
|
+
if (acceptsBody) {
|
|
76
|
+
properties.body = {
|
|
77
|
+
type: ["object", "array"],
|
|
78
|
+
description: "Тело запроса по схеме из hubex_describe_endpoint. Если в схеме body.type = array, " +
|
|
79
|
+
"передавай именно массив: обёртка вида { \"data\": [...] } уйдёт на сервер как объект, " +
|
|
80
|
+
"и HubEx ответит «Параметр [data] не может быть пустым».",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const defaultKeys = Object.keys(deps.config.defaults);
|
|
84
|
+
const defaultsNote = isWrite && defaultKeys.length > 0
|
|
85
|
+
? ` Если поля ${defaultKeys.join(", ")} не заданы в body, подставляются значения из настроек сервера.`
|
|
86
|
+
: "";
|
|
87
|
+
// HEAD не возвращает тела, предупреждать там не о чем.
|
|
88
|
+
const maskNote = deps.config.maskPii && group !== "head"
|
|
89
|
+
? " Персональные данные в ответах (ФИО, телефоны, email, адреса, логины, токены) " +
|
|
90
|
+
"маскируются звёздочками: звёздочки не являются частью реальных данных, " +
|
|
91
|
+
"и замаскированное значение нельзя использовать как аргумент следующего запроса."
|
|
92
|
+
: "";
|
|
93
|
+
return {
|
|
94
|
+
definition: {
|
|
95
|
+
name: `hubex_request_${group}`,
|
|
96
|
+
description: GROUP_DESCRIPTIONS[group] + defaultsNote + maskNote,
|
|
97
|
+
inputSchema: { type: "object", properties, required: ["endpointId"] },
|
|
98
|
+
},
|
|
99
|
+
async execute(args) {
|
|
100
|
+
try {
|
|
101
|
+
const id = String(args.endpointId ?? "").trim();
|
|
102
|
+
const entry = deps.index.get(id);
|
|
103
|
+
if (!entry) {
|
|
104
|
+
throw new Error(`Эндпоинт ${id || "(пусто)"} не найден. Вызови hubex_search_${group}_endpoints ` +
|
|
105
|
+
`со scope, чтобы получить корректный идентификатор.`);
|
|
106
|
+
}
|
|
107
|
+
// Рубеж 1: метод принадлежит группе этого инструмента.
|
|
108
|
+
if (!METHOD_GROUPS[group].includes(entry.method)) {
|
|
109
|
+
throw new Error(`${entry.id} использует метод ${entry.method}, а этот инструмент выполняет только ` +
|
|
110
|
+
`${METHOD_GROUPS[group].join("/")}. Нужный инструмент: hubex_request_${groupOf(entry.method)}.`);
|
|
111
|
+
}
|
|
112
|
+
// Рубеж 2: метод и сервис разрешены настройками сервера.
|
|
113
|
+
if (!deps.config.methods.includes(entry.method)) {
|
|
114
|
+
throw new Error(`Метод ${entry.method} отключён настройкой HUBEX_METHODS=${deps.config.methods.join(",")} ` +
|
|
115
|
+
`сервера (разрешены только: ${deps.config.methods.join(", ")}). Вызови hubex_search_${group}_endpoints ` +
|
|
116
|
+
`или hubex_list_scopes, чтобы найти доступный эндпоинт с разрешённым методом.`);
|
|
117
|
+
}
|
|
118
|
+
if (!deps.index.isAllowed(entry)) {
|
|
119
|
+
throw new Error(`Сервис ${entry.service} отключён настройкой HUBEX_SERVICES=` +
|
|
120
|
+
`${(deps.config.services ?? []).join(",")}. Вызови hubex_list_scopes, чтобы увидеть разрешённые сервисы.`);
|
|
121
|
+
}
|
|
122
|
+
const pathParams = (args.pathParams ?? {});
|
|
123
|
+
const path = resolvePath(entry, pathParams);
|
|
124
|
+
const query = (args.query ?? undefined);
|
|
125
|
+
let body = acceptsBody ? args.body : undefined;
|
|
126
|
+
let applied = [];
|
|
127
|
+
if (acceptsBody && body === undefined && entry.bodyRequired) {
|
|
128
|
+
throw new Error(`${entry.id} требует тело запроса. Вызови hubex_describe_endpoint с этим ` +
|
|
129
|
+
`идентификатором, чтобы получить схему body.`);
|
|
130
|
+
}
|
|
131
|
+
// Значения по умолчанию — про создание сущностей: в теле DELETE они
|
|
132
|
+
// только исказили бы список удаляемых записей.
|
|
133
|
+
if (isWrite && body !== undefined) {
|
|
134
|
+
const r = applyDefaults(body, deps.config.defaults);
|
|
135
|
+
body = r.body;
|
|
136
|
+
applied = r.applied;
|
|
137
|
+
}
|
|
138
|
+
const res = await deps.client.request({
|
|
139
|
+
service: entry.service,
|
|
140
|
+
method: entry.method,
|
|
141
|
+
path,
|
|
142
|
+
query,
|
|
143
|
+
body,
|
|
144
|
+
});
|
|
145
|
+
if (group === "head") {
|
|
146
|
+
return okResult({ status: res.status, endpointId: entry.id });
|
|
147
|
+
}
|
|
148
|
+
if (applied.length > 0) {
|
|
149
|
+
return okResult({
|
|
150
|
+
appliedDefaults: applied,
|
|
151
|
+
status: res.status,
|
|
152
|
+
data: res.data ?? null,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
// Пустое тело ответа (204/200 без содержимого) — валидный успех:
|
|
156
|
+
// отдаём статус, иначе результат инструмента оказался бы пустым.
|
|
157
|
+
if (res.data === undefined) {
|
|
158
|
+
return okResult({ status: res.status, endpointId: entry.id, data: null });
|
|
159
|
+
}
|
|
160
|
+
return okResult(res.data);
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
return failResult(err);
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
export function buildRequestTools(deps) {
|
|
169
|
+
return enabledGroups(deps.config.methods).map((group) => requestTool(group, deps));
|
|
170
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|