@oneentry/mcp-platform-server 0.1.0 → 0.1.3

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 (113) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +135 -92
  3. package/data/permissions.json +411 -0
  4. package/dist/api/audit.d.ts +0 -11
  5. package/dist/api/audit.js +0 -10
  6. package/dist/api/auth.d.ts +0 -18
  7. package/dist/api/auth.js +0 -20
  8. package/dist/api/build-catalog.d.ts +10 -0
  9. package/dist/api/build-catalog.js +187 -0
  10. package/dist/api/catalog.d.ts +8 -15
  11. package/dist/api/catalog.js +54 -16
  12. package/dist/api/client.d.ts +0 -24
  13. package/dist/api/client.js +6 -28
  14. package/dist/api/normalize-schema.d.ts +3 -0
  15. package/dist/api/normalize-schema.js +133 -0
  16. package/dist/api/policy.d.ts +0 -23
  17. package/dist/api/policy.js +0 -0
  18. package/dist/api/shape.d.ts +0 -13
  19. package/dist/api/shape.js +0 -21
  20. package/dist/api/swagger-source.d.ts +11 -0
  21. package/dist/api/swagger-source.js +58 -0
  22. package/dist/api/types.d.ts +0 -34
  23. package/dist/api/types.js +0 -1
  24. package/dist/bin/cli.d.ts +0 -1
  25. package/dist/bin/cli.js +11 -7
  26. package/dist/config/config.d.ts +66 -46
  27. package/dist/config/config.js +47 -44
  28. package/dist/index.d.ts +9 -7
  29. package/dist/index.js +5 -6
  30. package/dist/knowledge/chunk.d.ts +0 -15
  31. package/dist/knowledge/chunk.js +0 -16
  32. package/dist/knowledge/github.d.ts +21 -0
  33. package/dist/knowledge/github.js +174 -0
  34. package/dist/knowledge/loader.d.ts +23 -24
  35. package/dist/knowledge/loader.js +69 -108
  36. package/dist/knowledge/search.d.ts +1 -12
  37. package/dist/knowledge/search.js +19 -20
  38. package/dist/knowledge/tar.d.ts +7 -0
  39. package/dist/knowledge/tar.js +87 -0
  40. package/dist/knowledge/types.d.ts +8 -22
  41. package/dist/knowledge/types.js +0 -1
  42. package/dist/server.d.ts +1 -12
  43. package/dist/server.js +21 -18
  44. package/dist/session.d.ts +1 -18
  45. package/dist/session.js +3 -13
  46. package/dist/tools/api-call.d.ts +0 -2
  47. package/dist/tools/api-call.js +0 -23
  48. package/dist/tools/api-discovery.d.ts +0 -2
  49. package/dist/tools/api-discovery.js +2 -5
  50. package/dist/tools/docs.d.ts +0 -2
  51. package/dist/tools/docs.js +1 -5
  52. package/dist/tools/guide.d.ts +0 -7
  53. package/dist/tools/guide.js +11 -11
  54. package/dist/tools/result.d.ts +0 -8
  55. package/dist/tools/result.js +0 -7
  56. package/dist/tools/whoami.d.ts +0 -2
  57. package/dist/tools/whoami.js +5 -3
  58. package/dist/transports/http.d.ts +0 -6
  59. package/dist/transports/http.js +17 -18
  60. package/dist/transports/stdio.d.ts +0 -6
  61. package/dist/transports/stdio.js +1 -7
  62. package/knowledge/operating-rules.md +121 -0
  63. package/package.json +20 -7
  64. package/data/catalog.json +0 -1
  65. package/data/knowledge.json +0 -1
  66. package/dist/api/audit.d.ts.map +0 -1
  67. package/dist/api/audit.js.map +0 -1
  68. package/dist/api/auth.d.ts.map +0 -1
  69. package/dist/api/auth.js.map +0 -1
  70. package/dist/api/catalog.d.ts.map +0 -1
  71. package/dist/api/catalog.js.map +0 -1
  72. package/dist/api/client.d.ts.map +0 -1
  73. package/dist/api/client.js.map +0 -1
  74. package/dist/api/policy.d.ts.map +0 -1
  75. package/dist/api/policy.js.map +0 -1
  76. package/dist/api/shape.d.ts.map +0 -1
  77. package/dist/api/shape.js.map +0 -1
  78. package/dist/api/types.d.ts.map +0 -1
  79. package/dist/api/types.js.map +0 -1
  80. package/dist/bin/cli.d.ts.map +0 -1
  81. package/dist/bin/cli.js.map +0 -1
  82. package/dist/config/config.d.ts.map +0 -1
  83. package/dist/config/config.js.map +0 -1
  84. package/dist/index.d.ts.map +0 -1
  85. package/dist/index.js.map +0 -1
  86. package/dist/knowledge/chunk.d.ts.map +0 -1
  87. package/dist/knowledge/chunk.js.map +0 -1
  88. package/dist/knowledge/loader.d.ts.map +0 -1
  89. package/dist/knowledge/loader.js.map +0 -1
  90. package/dist/knowledge/search.d.ts.map +0 -1
  91. package/dist/knowledge/search.js.map +0 -1
  92. package/dist/knowledge/types.d.ts.map +0 -1
  93. package/dist/knowledge/types.js.map +0 -1
  94. package/dist/server.d.ts.map +0 -1
  95. package/dist/server.js.map +0 -1
  96. package/dist/session.d.ts.map +0 -1
  97. package/dist/session.js.map +0 -1
  98. package/dist/tools/api-call.d.ts.map +0 -1
  99. package/dist/tools/api-call.js.map +0 -1
  100. package/dist/tools/api-discovery.d.ts.map +0 -1
  101. package/dist/tools/api-discovery.js.map +0 -1
  102. package/dist/tools/docs.d.ts.map +0 -1
  103. package/dist/tools/docs.js.map +0 -1
  104. package/dist/tools/guide.d.ts.map +0 -1
  105. package/dist/tools/guide.js.map +0 -1
  106. package/dist/tools/result.d.ts.map +0 -1
  107. package/dist/tools/result.js.map +0 -1
  108. package/dist/tools/whoami.d.ts.map +0 -1
  109. package/dist/tools/whoami.js.map +0 -1
  110. package/dist/transports/http.d.ts.map +0 -1
  111. package/dist/transports/http.js.map +0 -1
  112. package/dist/transports/stdio.d.ts.map +0 -1
  113. package/dist/transports/stdio.js.map +0 -1
@@ -0,0 +1,87 @@
1
+ import { gunzipSync } from 'node:zlib';
2
+ const BLOCK = 512;
3
+ const FIELD = {
4
+ name: [0, 100],
5
+ size: [124, 12],
6
+ typeflag: [156, 1],
7
+ prefix: [345, 155],
8
+ };
9
+ const readString = (block, [offset, length]) => {
10
+ const raw = block.subarray(offset, offset + length);
11
+ const end = raw.indexOf(0);
12
+ return raw.subarray(0, end === -1 ? raw.length : end).toString('utf8').trim();
13
+ };
14
+ const readSize = (block) => {
15
+ const [offset, length] = FIELD.size;
16
+ const first = block[offset] ?? 0;
17
+ if ((first & 0x80) !== 0) {
18
+ throw new Error('tar: base-256 size fields are not supported');
19
+ }
20
+ const text = readString(block, FIELD.size).replace(/\0/g, '').trim();
21
+ const parsed = Number.parseInt(text === '' ? '0' : text, 8);
22
+ if (!Number.isFinite(parsed) || parsed < 0) {
23
+ throw new Error(`tar: unreadable size field "${text}" at offset ${String(offset + length)}`);
24
+ }
25
+ return parsed;
26
+ };
27
+ const readPaxPath = (data) => {
28
+ const text = data.toString('utf8');
29
+ const match = /(?:^|\n)\d+ path=([^\n]*)\n/.exec(text);
30
+ return match?.[1];
31
+ };
32
+ const fullName = (block) => {
33
+ const name = readString(block, FIELD.name);
34
+ const prefix = readString(block, FIELD.prefix);
35
+ return prefix === '' ? name : `${prefix}/${name}`;
36
+ };
37
+ const isZeroBlock = (block) => {
38
+ for (const byte of block) {
39
+ if (byte !== 0) {
40
+ return false;
41
+ }
42
+ }
43
+ return true;
44
+ };
45
+ export const readTar = (buffer) => {
46
+ const entries = [];
47
+ let offset = 0;
48
+ let pendingName;
49
+ while (offset + BLOCK <= buffer.length) {
50
+ const header = buffer.subarray(offset, offset + BLOCK);
51
+ if (isZeroBlock(header)) {
52
+ break;
53
+ }
54
+ const size = readSize(header);
55
+ const typeflag = readString(header, FIELD.typeflag);
56
+ const dataStart = offset + BLOCK;
57
+ const dataEnd = dataStart + size;
58
+ if (dataEnd > buffer.length) {
59
+ throw new Error('tar: truncated archive');
60
+ }
61
+ const data = buffer.subarray(dataStart, dataEnd);
62
+ offset = dataStart + Math.ceil(size / BLOCK) * BLOCK;
63
+ if (typeflag === 'x' || typeflag === 'X') {
64
+ pendingName = readPaxPath(data) ?? pendingName;
65
+ continue;
66
+ }
67
+ if (typeflag === 'L') {
68
+ pendingName = data.toString('utf8').replace(/\0+$/, '');
69
+ continue;
70
+ }
71
+ if (typeflag === 'g') {
72
+ continue;
73
+ }
74
+ const name = pendingName ?? fullName(header);
75
+ pendingName = undefined;
76
+ if (typeflag !== '' && typeflag !== '0') {
77
+ continue;
78
+ }
79
+ entries.push({ path: name, content: data.toString('utf8') });
80
+ }
81
+ return entries;
82
+ };
83
+ export const stripRoot = (entries) => entries.map((entry) => {
84
+ const cut = entry.path.indexOf('/');
85
+ return cut === -1 ? entry : { path: entry.path.slice(cut + 1), content: entry.content };
86
+ });
87
+ export const readTarGz = (buffer) => stripRoot(readTar(gunzipSync(buffer)));
@@ -1,30 +1,16 @@
1
- /**
2
- * Один раздел документации — единица поиска и чтения.
3
- * Резать документы на разделы обязательно: отдельные файлы доходят до 56 КБ,
4
- * а модели нужен ровно тот раздел, который отвечает на её вопрос.
5
- */
6
1
  export interface KnowledgeChunk {
7
- /** Стабильный идентификатор `<docId>#<anchor>`. */
8
2
  id: string;
9
- /** Идентификатор документа, например `back/docs/menus-module`. */
10
3
  docId: string;
11
- /** Репозиторий-источник. */
12
- repo: 'back' | 'front' | 'mcp';
13
- /** Путь файла относительно корня репозитория-источника. */
4
+ repo: string;
14
5
  path: string;
15
- /** Слаг заголовка раздела; пустая строка — преамбула документа. */
16
6
  anchor: string;
17
- /** Текст заголовка раздела. */
18
7
  title: string;
19
- /** Заголовок документа (первый `#`), нужен для ранжирования и вывода. */
20
8
  docTitle: string;
21
- /** Тело раздела без заголовка. */
22
9
  text: string;
23
10
  }
24
- /** Документ целиком: список его разделов, чтобы агент мог листать соседние. */
25
11
  export interface KnowledgeDoc {
26
12
  docId: string;
27
- repo: 'back' | 'front' | 'mcp';
13
+ repo: string;
28
14
  path: string;
29
15
  title: string;
30
16
  anchors: Array<{
@@ -33,15 +19,15 @@ export interface KnowledgeDoc {
33
19
  bytes: number;
34
20
  }>;
35
21
  }
36
- /** Собранный пакет знаний: то, что build-knowledge.ts пишет в data/knowledge.json. */
22
+ export type KnowledgeOrigin = 'github' | 'cache' | 'local' | 'seed';
37
23
  export interface KnowledgePack {
38
- /** Версия формата — чтобы рантайм не пытался читать несовместимый снапшот. */
39
24
  version: 1;
40
- /** Когда собран (ISO). Показывается в cms_whoami. */
41
25
  builtAt: string;
42
- /** Из какого источника собран: снапшот или живой чекаут. */
43
- source: 'snapshot' | 'live';
26
+ source: KnowledgeOrigin;
27
+ repo?: string;
28
+ ref?: string;
29
+ sha?: string;
30
+ warning?: string;
44
31
  docs: KnowledgeDoc[];
45
32
  chunks: KnowledgeChunk[];
46
33
  }
47
- //# sourceMappingURL=types.d.ts.map
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=types.js.map
package/dist/server.d.ts CHANGED
@@ -1,16 +1,5 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { Config } from './config/config.js';
3
3
  import { Session, type SharedDeps } from './session.js';
4
- /**
5
- * Собирает разделяемые зависимости: каталог операций, индекс знаний, журнал.
6
- * Вынесено отдельно, чтобы remote-режим строил их один раз на процесс,
7
- * а не на каждую сессию.
8
- */
9
- export declare const createSharedDeps: (config: Config) => SharedDeps;
10
- /**
11
- * Создаёт MCP-сервер с полным набором инструментов.
12
- * Сессия передаётся геттером: в stdio-режиме она одна на процесс,
13
- * в HTTP-режиме — своя на каждое подключение, и код инструментов от этого не зависит.
14
- */
4
+ export declare const createSharedDeps: (config: Config) => Promise<SharedDeps>;
15
5
  export declare const createServer: (deps: SharedDeps, getSession: () => Session) => McpServer;
16
- //# sourceMappingURL=server.d.ts.map
package/dist/server.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { createRequire } from 'node:module';
1
2
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
3
  import { AuditLog } from './api/audit.js';
3
4
  import { OperationCatalog } from './api/catalog.js';
@@ -8,30 +9,33 @@ import { registerApiDiscovery } from './tools/api-discovery.js';
8
9
  import { registerDocs } from './tools/docs.js';
9
10
  import { registerGuide } from './tools/guide.js';
10
11
  import { registerWhoami } from './tools/whoami.js';
11
- /** Версия пакета, сообщаемая клиенту при handshake. */
12
- const SERVER_VERSION = '0.1.0';
13
- /**
14
- * Собирает разделяемые зависимости: каталог операций, индекс знаний, журнал.
15
- * Вынесено отдельно, чтобы remote-режим строил их один раз на процесс,
16
- * а не на каждую сессию.
17
- */
18
- export const createSharedDeps = (config) => ({
12
+ const SERVER_VERSION = (() => {
13
+ try {
14
+ const pkg = createRequire(import.meta.url)('../package.json');
15
+ return pkg.version ?? '0.0.0';
16
+ }
17
+ catch {
18
+ return '0.0.0';
19
+ }
20
+ })();
21
+ export const createSharedDeps = async (config) => ({
19
22
  config,
20
- catalog: OperationCatalog.load(),
21
- knowledge: new KnowledgeIndex(resolveKnowledge({
22
- ...(config.backPath ? { backPath: config.backPath } : {}),
23
- ...(config.frontPath ? { frontPath: config.frontPath } : {}),
23
+ catalog: await OperationCatalog.resolve(config),
24
+ knowledge: new KnowledgeIndex(await resolveKnowledge({
25
+ repo: config.knowledge.repo,
26
+ ref: config.knowledge.ref,
27
+ ...(config.knowledge.path ? { path: config.knowledge.path } : {}),
28
+ cacheDir: config.cacheDir,
29
+ refreshTtlMs: config.knowledge.refreshTtlMs,
30
+ offline: config.knowledge.offline,
31
+ ...(config.knowledge.token ? { token: config.knowledge.token } : {}),
32
+ timeoutMs: config.requestTimeoutMs,
24
33
  })),
25
34
  audit: new AuditLog({
26
35
  ...(config.auditPath ? { path: config.auditPath } : {}),
27
36
  mode: config.mode,
28
37
  }),
29
38
  });
30
- /**
31
- * Создаёт MCP-сервер с полным набором инструментов.
32
- * Сессия передаётся геттером: в stdio-режиме она одна на процесс,
33
- * в HTTP-режиме — своя на каждое подключение, и код инструментов от этого не зависит.
34
- */
35
39
  export const createServer = (deps, getSession) => {
36
40
  const server = new McpServer({ name: 'oneentry-mcp-platform', version: SERVER_VERSION }, {
37
41
  instructions: 'OneEntry CMS Admin API. Call cms_guide first, then cms_docs_read on "mcp/operating-rules" before any write. ' +
@@ -45,4 +49,3 @@ export const createServer = (deps, getSession) => {
45
49
  registerApiCall(server, getSession);
46
50
  return server;
47
51
  };
48
- //# sourceMappingURL=server.js.map
package/dist/session.d.ts CHANGED
@@ -6,21 +6,12 @@ import { ConfirmStore } from './api/policy.js';
6
6
  import type { Config } from './config/config.js';
7
7
  import { KnowledgeIndex } from './knowledge/search.js';
8
8
  import type { Operation } from './api/types.js';
9
- /**
10
- * Разделяемые между сессиями зависимости.
11
- * Каталог и индекс знаний строятся один раз на процесс — они неизменяемы,
12
- * и в remote-режиме держать их копию на сессию было бы чистой растратой.
13
- */
14
9
  export interface SharedDeps {
15
10
  config: Config;
16
11
  catalog: OperationCatalog;
17
12
  knowledge: KnowledgeIndex;
18
13
  audit: AuditLog;
19
14
  }
20
- /**
21
- * Состояние одной MCP-сессии: свои учётные данные, свои токены,
22
- * свои токены подтверждения. Именно эта изоляция делает remote-режим безопасным.
23
- */
24
15
  export declare class Session {
25
16
  readonly shared: SharedDeps;
26
17
  readonly confirms: ConfirmStore;
@@ -29,15 +20,7 @@ export declare class Session {
29
20
  private identityCache;
30
21
  constructor(shared: SharedDeps, credentials?: Credentials);
31
22
  get isConfigured(): boolean;
32
- /**
33
- * Кто мы. Кэшируется: набор прав нужен почти каждому изменяющему вызову,
34
- * а меняется он редко.
35
- */
23
+ accessToken(): Promise<string>;
36
24
  identity(): Promise<AdminIdentity | undefined>;
37
- /**
38
- * Пытается показать, что именно будет изменено: ищет GET по тому же пути
39
- * и читает объект. Без этого «подтверждение удаления» — подпись под пустым листом.
40
- */
41
25
  describeTarget(operation: Operation, args: CallArgs): Promise<unknown>;
42
26
  }
43
- //# sourceMappingURL=session.d.ts.map
package/dist/session.js CHANGED
@@ -1,10 +1,6 @@
1
1
  import { adminIdFromToken, credentialsFromConfig, TokenStore } from './api/auth.js';
2
2
  import { AdminApiClient, buildUrl } from './api/client.js';
3
3
  import { ConfirmStore } from './api/policy.js';
4
- /**
5
- * Состояние одной MCP-сессии: свои учётные данные, свои токены,
6
- * свои токены подтверждения. Именно эта изоляция делает remote-режим безопасным.
7
- */
8
4
  export class Session {
9
5
  shared;
10
6
  confirms = new ConfirmStore();
@@ -27,10 +23,9 @@ export class Session {
27
23
  get isConfigured() {
28
24
  return this.tokens.isConfigured;
29
25
  }
30
- /**
31
- * Кто мы. Кэшируется: набор прав нужен почти каждому изменяющему вызову,
32
- * а меняется он редко.
33
- */
26
+ async accessToken() {
27
+ return this.tokens.accessToken();
28
+ }
34
29
  async identity() {
35
30
  if (this.identityCache) {
36
31
  return this.identityCache;
@@ -49,10 +44,6 @@ export class Session {
49
44
  return undefined;
50
45
  }
51
46
  }
52
- /**
53
- * Пытается показать, что именно будет изменено: ищет GET по тому же пути
54
- * и читает объект. Без этого «подтверждение удаления» — подпись под пустым листом.
55
- */
56
47
  async describeTarget(operation, args) {
57
48
  const sibling = this.shared.catalog
58
49
  .operations()
@@ -72,4 +63,3 @@ export class Session {
72
63
  return result.ok ? result.body : undefined;
73
64
  }
74
65
  }
75
- //# sourceMappingURL=session.js.map
@@ -1,5 +1,3 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { Session } from '../session.js';
3
- /** Регистрирует `cms_api_call`. */
4
3
  export declare const registerApiCall: (server: McpServer, getSession: () => Session) => void;
5
- //# sourceMappingURL=api-call.d.ts.map
@@ -4,9 +4,7 @@ import { buildUrl, normalizeBody, RequestBuildError } from '../api/client.js';
4
4
  import { checkLevel, decide } from '../api/policy.js';
5
5
  import { shapeResponse, summarizeTarget } from '../api/shape.js';
6
6
  import { errorResult, jsonResult } from './result.js';
7
- /** Ограничение на показ тела в плане dry-run. */
8
7
  const PLAN_BODY_LIMIT = 2_000;
9
- /** Регистрирует `cms_api_call`. */
10
8
  export const registerApiCall = (server, getSession) => {
11
9
  server.registerTool('cms_api_call', {
12
10
  title: 'Call an Admin API operation',
@@ -42,11 +40,6 @@ export const registerApiCall = (server, getSession) => {
42
40
  hint: 'Operation ids come from cms_api_search — do not construct them by hand.',
43
41
  });
44
42
  }
45
- /**
46
- * Тело нормализуется до всего остального: и хэш аудита, и токен подтверждения
47
- * привязываются именно к отправляемым аргументам, иначе подтверждение,
48
- * выданное на dry-run, не совпало бы с реальным вызовом.
49
- */
50
43
  const normalizedBody = normalizeBody(body);
51
44
  const args = {
52
45
  ...(path ? { path } : {}),
@@ -60,24 +53,15 @@ export const registerApiCall = (server, getSession) => {
60
53
  path: operation.path,
61
54
  argsHash,
62
55
  };
63
- /**
64
- * Уровень доступа проверяется до аутентификации: иначе отказ read-only режима
65
- * всё равно сходил бы в сеть за токеном, и обещание «no request was sent» было бы ложью.
66
- */
67
56
  const levelDenial = checkLevel(operation, config.allow);
68
57
  if (levelDenial) {
69
58
  audit.record({ ...auditBase, outcome: 'denied' });
70
59
  return errorResult(levelDenial.reason, { opId, policy: config.allow, risk: operation.risk });
71
60
  }
72
- /** Личность нужна только там, где операция объявляет требуемое право. */
73
61
  const identity = operation.permission ? await session.identity() : undefined;
74
62
  if (identity) {
75
63
  Object.assign(auditBase, { adminId: identity.id });
76
64
  }
77
- /**
78
- * Негодный токен — отдельная ошибка, а не «просто нет подтверждения»:
79
- * иначе агент получал бы в ответ новый токен и не понимал, что предыдущий не подошёл.
80
- */
81
65
  const confirmValid = confirm !== undefined && session.confirms.verify(confirm, opId, args);
82
66
  if (confirm !== undefined && !confirmValid) {
83
67
  return errorResult('Confirm token is expired, already used, or does not match these arguments. Re-run with dryRun: true to get a fresh one.', { opId });
@@ -94,7 +78,6 @@ export const registerApiCall = (server, getSession) => {
94
78
  }
95
79
  return errorResult(decision.reason, { opId, policy: config.allow, risk: operation.risk });
96
80
  }
97
- /** Разрешение по уровню и правам получено — можно строить сам запрос. */
98
81
  let url;
99
82
  try {
100
83
  url = buildUrl(config.baseUrl, operation, args);
@@ -134,10 +117,6 @@ export const registerApiCall = (server, getSession) => {
134
117
  target: summarizeTarget(target),
135
118
  });
136
119
  }
137
- /**
138
- * Токен гасится только здесь, перед самой отправкой: если бы он расходовался
139
- * при проверке политики, отказ по другой причине заставлял бы подтверждать заново.
140
- */
141
120
  if (confirm !== undefined && !session.confirms.consume(confirm, opId, args)) {
142
121
  return errorResult('Confirm token was consumed concurrently. Re-run with dryRun: true to get a fresh one.', { opId });
143
122
  }
@@ -154,7 +133,6 @@ export const registerApiCall = (server, getSession) => {
154
133
  opId,
155
134
  status: result.error.status,
156
135
  ...(result.error.hint ? { hint: result.error.hint } : {}),
157
- docs: operation.docLinks,
158
136
  });
159
137
  }
160
138
  const shaped = shapeResponse(result.body, config.maxResponseBytes);
@@ -166,4 +144,3 @@ export const registerApiCall = (server, getSession) => {
166
144
  });
167
145
  });
168
146
  };
169
- //# sourceMappingURL=api-call.js.map
@@ -1,5 +1,3 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { SharedDeps } from '../session.js';
3
- /** Регистрирует `cms_api_search` и `cms_api_describe`. */
4
3
  export declare const registerApiDiscovery: (server: McpServer, deps: SharedDeps) => void;
5
- //# sourceMappingURL=api-discovery.d.ts.map
@@ -1,6 +1,5 @@
1
1
  import { z } from 'zod';
2
2
  import { errorResult, jsonResult } from './result.js';
3
- /** Регистрирует `cms_api_search` и `cms_api_describe`. */
4
3
  export const registerApiDiscovery = (server, deps) => {
5
4
  const { catalog } = deps;
6
5
  server.registerTool('cms_api_search', {
@@ -36,7 +35,7 @@ export const registerApiDiscovery = (server, deps) => {
36
35
  });
37
36
  server.registerTool('cms_api_describe', {
38
37
  title: 'Describe an Admin API operation',
39
- description: 'Full detail for one operation: path and query parameters, request-body schema, required permission, risk level, and the knowledge sections that explain the payload. Fields marked "x-loose": true have a TypeScript-shaped type in the source swagger — trust their example, not their type.',
38
+ description: 'Full detail for one operation: path and query parameters, request-body schema, required permission, risk level, and whether it is permanently confirm-gated. Fields marked "x-loose": true could not be converted to a JSON Schema type — trust their example, not their type.',
40
39
  inputSchema: {
41
40
  opId: z.string().min(1).describe('Operation id from cms_api_search, e.g. "AdminPagesController_findAllRoot".'),
42
41
  },
@@ -70,11 +69,9 @@ export const registerApiDiscovery = (server, deps) => {
70
69
  body: operation.body ?? null,
71
70
  looseFields: loose,
72
71
  responseSummary: operation.responseSummary ?? null,
73
- docs: operation.docLinks,
74
72
  next: operation.risk === 'read'
75
73
  ? 'Call it with cms_api_call { opId, path, query }.'
76
- : 'Read the linked docs, then call cms_api_call with dryRun: true first.',
74
+ : 'Search the knowledge base with cms_docs_search for this entity, then call cms_api_call with dryRun: true first.',
77
75
  });
78
76
  });
79
77
  };
80
- //# sourceMappingURL=api-discovery.js.map
@@ -1,5 +1,3 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { SharedDeps } from '../session.js';
3
- /** Регистрирует `cms_docs_search` и `cms_docs_read`. */
4
3
  export declare const registerDocs: (server: McpServer, deps: SharedDeps) => void;
5
- //# sourceMappingURL=docs.d.ts.map
@@ -1,8 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { errorResult, jsonResult } from './result.js';
3
- /** Предел размера одного раздела, отдаваемого модели. */
4
3
  const MAX_SECTION_BYTES = 12_288;
5
- /** Регистрирует `cms_docs_search` и `cms_docs_read`. */
6
4
  export const registerDocs = (server, deps) => {
7
5
  const { knowledge } = deps;
8
6
  server.registerTool('cms_docs_search', {
@@ -37,7 +35,7 @@ export const registerDocs = (server, deps) => {
37
35
  title: 'Read a CMS knowledge section',
38
36
  description: 'Read one section of a knowledge document. Omit the anchor to get the document preamble plus its section list, then page through the sections you need.',
39
37
  inputSchema: {
40
- docId: z.string().min(1).describe('Document id, e.g. "back/docs/menus-module" or "mcp/operating-rules".'),
38
+ docId: z.string().min(1).describe('Document id, e.g. "mcp/docs/api/orders" or "mcp/operating-rules".'),
41
39
  anchor: z.string().optional().describe('Section anchor from cms_docs_search; omit for the preamble.'),
42
40
  },
43
41
  annotations: { readOnlyHint: true, openWorldHint: false },
@@ -73,7 +71,6 @@ export const registerDocs = (server, deps) => {
73
71
  sections: doc.anchors.map((a) => ({ anchor: a.anchor, title: a.title, bytes: a.bytes })),
74
72
  });
75
73
  });
76
- /** Ресурсы дублируют два самых нужных документа, чтобы клиент мог их закрепить. */
77
74
  server.registerResource('operating-rules', 'oneentry://knowledge/mcp/operating-rules', {
78
75
  title: 'OneEntry CMS operating rules',
79
76
  description: 'The payload and safety rules an agent must follow before writing to this CMS.',
@@ -110,4 +107,3 @@ export const registerDocs = (server, deps) => {
110
107
  ],
111
108
  }));
112
109
  };
113
- //# sourceMappingURL=docs.js.map
@@ -1,11 +1,4 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { SharedDeps } from '../session.js';
3
- /**
4
- * Собирает краткое руководство: режим, политика, карта тегов и порядок работы.
5
- * Генерируется, а не хранится текстом, потому что половина содержимого —
6
- * фактическое состояние сервера, и расхождение здесь дороже дублирования.
7
- */
8
3
  export declare const renderGuide: (deps: SharedDeps) => string;
9
- /** Регистрирует `cms_guide`. */
10
4
  export declare const registerGuide: (server: McpServer, deps: SharedDeps) => void;
11
- //# sourceMappingURL=guide.d.ts.map
@@ -1,11 +1,5 @@
1
1
  import { textResult } from './result.js';
2
- /** Сколько тегов показывать в карте API. */
3
2
  const TAG_LIMIT = 24;
4
- /**
5
- * Собирает краткое руководство: режим, политика, карта тегов и порядок работы.
6
- * Генерируется, а не хранится текстом, потому что половина содержимого —
7
- * фактическое состояние сервера, и расхождение здесь дороже дублирования.
8
- */
9
3
  export const renderGuide = (deps) => {
10
4
  const { config, catalog, knowledge } = deps;
11
5
  const counts = new Map();
@@ -22,12 +16,19 @@ export const renderGuide = (deps) => {
22
16
  : config.allow === 'write'
23
17
  ? 'writes allowed; DELETE and the permanently gated paths still need a confirm token'
24
18
  : 'writes and deletes allowed; deletes and the permanently gated paths still need a confirm token';
19
+ const pack = knowledge.pack;
20
+ const knowledgeOrigin = pack.source === 'seed'
21
+ ? 'bundled operating rules only'
22
+ : pack.source === 'local'
23
+ ? `local clone ${pack.repo ?? ''}`.trim()
24
+ : `${pack.repo ?? ''}@${pack.ref ?? ''} (${pack.sha?.slice(0, 12) ?? '?'}, ${pack.source})`;
25
25
  return [
26
26
  '# OneEntry CMS Admin API — agent guide',
27
27
  '',
28
28
  `Mode: **${config.mode}** · Base URL: \`${config.baseUrl}\` · Policy: ${allowLine}.`,
29
- `Catalog: ${String(catalog.operations().length)} operations (swagger ${catalog.catalog.swaggerHash}).`,
30
- `Knowledge: ${String(knowledge.docs().length)} docs / ${String(knowledge.pack.chunks.length)} sections, source ${knowledge.pack.source}.`,
29
+ `Catalog: ${String(catalog.operations().length)} operations (swagger ${catalog.catalog.swaggerHash || 'unavailable'}).`,
30
+ `Knowledge: ${String(knowledge.docs().length)} docs / ${String(pack.chunks.length)} sections, from ${knowledgeOrigin}.`,
31
+ ...(pack.warning ? ['', `> **Knowledge is degraded.** ${pack.warning}`] : []),
31
32
  '',
32
33
  '## How to work here',
33
34
  '',
@@ -35,7 +36,8 @@ export const renderGuide = (deps) => {
35
36
  ' payload rules (locale-keyed `localizeInfos`, attribute-set-driven `attributesSets`,',
36
37
  ' lexorank vs numeric `position`, eventual consistency) that make the difference between',
37
38
  ' a 400 and a correct call.',
38
- '2. `cms_docs_search` — find the reference doc for the entity you are touching.',
39
+ '2. `cms_docs_search` — find the reference doc for the entity you are touching;',
40
+ ' `mcp/docs/server/doc-map` lists every document with a one-line "read this when".',
39
41
  '3. `cms_api_search` → `cms_api_describe` — get the real operation and its payload shape.',
40
42
  '4. `cms_api_call` — `dryRun: true` first for anything that mutates.',
41
43
  '',
@@ -56,7 +58,6 @@ export const renderGuide = (deps) => {
56
58
  : []),
57
59
  ].join('\n');
58
60
  };
59
- /** Регистрирует `cms_guide`. */
60
61
  export const registerGuide = (server, deps) => {
61
62
  server.registerTool('cms_guide', {
62
63
  title: 'CMS guide',
@@ -64,4 +65,3 @@ export const registerGuide = (server, deps) => {
64
65
  annotations: { readOnlyHint: true, openWorldHint: false },
65
66
  }, () => textResult(renderGuide(deps)));
66
67
  };
67
- //# sourceMappingURL=guide.js.map
@@ -1,4 +1,3 @@
1
- /** Форма результата вызова инструмента MCP. */
2
1
  export interface ToolResult {
3
2
  content: Array<{
4
3
  type: 'text';
@@ -7,13 +6,6 @@ export interface ToolResult {
7
6
  isError?: boolean;
8
7
  [key: string]: unknown;
9
8
  }
10
- /** Успешный результат-JSON. */
11
9
  export declare const jsonResult: (payload: unknown) => ToolResult;
12
- /** Успешный результат-текст (для человекочитаемых руководств). */
13
10
  export declare const textResult: (text: string) => ToolResult;
14
- /**
15
- * Ошибка инструмента. Возвращаем её как isError, а не бросаем исключение:
16
- * модель должна увидеть текст и исправиться, а не получить обрыв сессии.
17
- */
18
11
  export declare const errorResult: (message: string, extra?: Record<string, unknown>) => ToolResult;
19
- //# sourceMappingURL=result.d.ts.map
@@ -1,19 +1,12 @@
1
- /** Успешный результат-JSON. */
2
1
  export const jsonResult = (payload) => ({
3
2
  content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
4
3
  });
5
- /** Успешный результат-текст (для человекочитаемых руководств). */
6
4
  export const textResult = (text) => ({
7
5
  content: [{ type: 'text', text }],
8
6
  });
9
- /**
10
- * Ошибка инструмента. Возвращаем её как isError, а не бросаем исключение:
11
- * модель должна увидеть текст и исправиться, а не получить обрыв сессии.
12
- */
13
7
  export const errorResult = (message, extra) => ({
14
8
  content: [
15
9
  { type: 'text', text: JSON.stringify({ error: message, ...(extra ?? {}) }, null, 2) },
16
10
  ],
17
11
  isError: true,
18
12
  });
19
- //# sourceMappingURL=result.js.map
@@ -1,5 +1,3 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { Session } from '../session.js';
3
- /** Регистрирует `cms_whoami` — состояние сервера и подключённого админа. */
4
3
  export declare const registerWhoami: (server: McpServer, getSession: () => Session) => void;
5
- //# sourceMappingURL=whoami.d.ts.map
@@ -1,5 +1,4 @@
1
1
  import { jsonResult } from './result.js';
2
- /** Регистрирует `cms_whoami` — состояние сервера и подключённого админа. */
3
2
  export const registerWhoami = (server, getSession) => {
4
3
  server.registerTool('cms_whoami', {
5
4
  title: 'Who am I',
@@ -19,7 +18,7 @@ export const registerWhoami = (server, getSession) => {
19
18
  : {
20
19
  admin: null,
21
20
  hint: session.isConfigured
22
- ? 'Credentials are configured but authentication failed — check ONEENTRY_CMS_LOGIN / ONEENTRY_CMS_PASSWORD and that the stand is reachable.'
21
+ ? 'Credentials are configured but authentication failed — check ONEENTRY_CMS_LOGIN / ONEENTRY_CMS_PASSWORD and that the instance is reachable.'
23
22
  : 'No credentials configured. Set ONEENTRY_CMS_LOGIN + ONEENTRY_CMS_PASSWORD (or ONEENTRY_CMS_TOKEN).',
24
23
  }),
25
24
  catalog: {
@@ -31,12 +30,15 @@ export const registerWhoami = (server, getSession) => {
31
30
  },
32
31
  knowledge: {
33
32
  source: knowledge.pack.source,
33
+ ...(knowledge.pack.repo ? { repo: knowledge.pack.repo } : {}),
34
+ ...(knowledge.pack.ref ? { ref: knowledge.pack.ref } : {}),
35
+ ...(knowledge.pack.sha ? { commit: knowledge.pack.sha.slice(0, 12) } : {}),
34
36
  builtAt: knowledge.pack.builtAt,
35
37
  docs: knowledge.docs().length,
36
38
  sections: knowledge.pack.chunks.length,
39
+ ...(knowledge.pack.warning ? { warning: knowledge.pack.warning } : {}),
37
40
  },
38
41
  audit: config.auditPath ?? null,
39
42
  });
40
43
  });
41
44
  };
42
- //# sourceMappingURL=whoami.js.map
@@ -1,10 +1,4 @@
1
1
  import type { Config } from '../config/config.js';
2
- /**
3
- * Запускает сервер поверх Streamable HTTP — remote-режим.
4
- * На каждое MCP-подключение создаётся своя `Session` со своими токенами
5
- * и своими подтверждениями; общий каталог и знания строятся один раз.
6
- */
7
2
  export declare const runHttp: (config: Config) => Promise<{
8
3
  close: () => Promise<void>;
9
4
  }>;
10
- //# sourceMappingURL=http.d.ts.map