@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.
- package/LICENSE +21 -0
- package/README.md +135 -92
- package/data/permissions.json +411 -0
- package/dist/api/audit.d.ts +0 -11
- package/dist/api/audit.js +0 -10
- package/dist/api/auth.d.ts +0 -18
- package/dist/api/auth.js +0 -20
- package/dist/api/build-catalog.d.ts +10 -0
- package/dist/api/build-catalog.js +187 -0
- package/dist/api/catalog.d.ts +8 -15
- package/dist/api/catalog.js +54 -16
- package/dist/api/client.d.ts +0 -24
- package/dist/api/client.js +6 -28
- package/dist/api/normalize-schema.d.ts +3 -0
- package/dist/api/normalize-schema.js +133 -0
- package/dist/api/policy.d.ts +0 -23
- package/dist/api/policy.js +0 -0
- package/dist/api/shape.d.ts +0 -13
- package/dist/api/shape.js +0 -21
- package/dist/api/swagger-source.d.ts +11 -0
- package/dist/api/swagger-source.js +58 -0
- package/dist/api/types.d.ts +0 -34
- package/dist/api/types.js +0 -1
- package/dist/bin/cli.d.ts +0 -1
- package/dist/bin/cli.js +11 -7
- package/dist/config/config.d.ts +66 -46
- package/dist/config/config.js +47 -44
- package/dist/index.d.ts +9 -7
- package/dist/index.js +5 -6
- package/dist/knowledge/chunk.d.ts +0 -15
- package/dist/knowledge/chunk.js +0 -16
- package/dist/knowledge/github.d.ts +21 -0
- package/dist/knowledge/github.js +174 -0
- package/dist/knowledge/loader.d.ts +23 -24
- package/dist/knowledge/loader.js +69 -108
- package/dist/knowledge/search.d.ts +1 -12
- package/dist/knowledge/search.js +19 -20
- package/dist/knowledge/tar.d.ts +7 -0
- package/dist/knowledge/tar.js +87 -0
- package/dist/knowledge/types.d.ts +8 -22
- package/dist/knowledge/types.js +0 -1
- package/dist/server.d.ts +1 -12
- package/dist/server.js +21 -18
- package/dist/session.d.ts +1 -18
- package/dist/session.js +3 -13
- package/dist/tools/api-call.d.ts +0 -2
- package/dist/tools/api-call.js +0 -23
- package/dist/tools/api-discovery.d.ts +0 -2
- package/dist/tools/api-discovery.js +2 -5
- package/dist/tools/docs.d.ts +0 -2
- package/dist/tools/docs.js +1 -5
- package/dist/tools/guide.d.ts +0 -7
- package/dist/tools/guide.js +11 -11
- package/dist/tools/result.d.ts +0 -8
- package/dist/tools/result.js +0 -7
- package/dist/tools/whoami.d.ts +0 -2
- package/dist/tools/whoami.js +5 -3
- package/dist/transports/http.d.ts +0 -6
- package/dist/transports/http.js +17 -18
- package/dist/transports/stdio.d.ts +0 -6
- package/dist/transports/stdio.js +1 -7
- package/knowledge/operating-rules.md +121 -0
- package/package.json +20 -7
- package/data/catalog.json +0 -1
- package/data/knowledge.json +0 -1
- package/dist/api/audit.d.ts.map +0 -1
- package/dist/api/audit.js.map +0 -1
- package/dist/api/auth.d.ts.map +0 -1
- package/dist/api/auth.js.map +0 -1
- package/dist/api/catalog.d.ts.map +0 -1
- package/dist/api/catalog.js.map +0 -1
- package/dist/api/client.d.ts.map +0 -1
- package/dist/api/client.js.map +0 -1
- package/dist/api/policy.d.ts.map +0 -1
- package/dist/api/policy.js.map +0 -1
- package/dist/api/shape.d.ts.map +0 -1
- package/dist/api/shape.js.map +0 -1
- package/dist/api/types.d.ts.map +0 -1
- package/dist/api/types.js.map +0 -1
- package/dist/bin/cli.d.ts.map +0 -1
- package/dist/bin/cli.js.map +0 -1
- package/dist/config/config.d.ts.map +0 -1
- package/dist/config/config.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/knowledge/chunk.d.ts.map +0 -1
- package/dist/knowledge/chunk.js.map +0 -1
- package/dist/knowledge/loader.d.ts.map +0 -1
- package/dist/knowledge/loader.js.map +0 -1
- package/dist/knowledge/search.d.ts.map +0 -1
- package/dist/knowledge/search.js.map +0 -1
- package/dist/knowledge/types.d.ts.map +0 -1
- package/dist/knowledge/types.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/session.d.ts.map +0 -1
- package/dist/session.js.map +0 -1
- package/dist/tools/api-call.d.ts.map +0 -1
- package/dist/tools/api-call.js.map +0 -1
- package/dist/tools/api-discovery.d.ts.map +0 -1
- package/dist/tools/api-discovery.js.map +0 -1
- package/dist/tools/docs.d.ts.map +0 -1
- package/dist/tools/docs.js.map +0 -1
- package/dist/tools/guide.d.ts.map +0 -1
- package/dist/tools/guide.js.map +0 -1
- package/dist/tools/result.d.ts.map +0 -1
- package/dist/tools/result.js.map +0 -1
- package/dist/tools/whoami.d.ts.map +0 -1
- package/dist/tools/whoami.js.map +0 -1
- package/dist/transports/http.d.ts.map +0 -1
- package/dist/transports/http.js.map +0 -1
- package/dist/transports/stdio.d.ts.map +0 -1
- package/dist/transports/stdio.js.map +0 -1
package/dist/transports/http.js
CHANGED
|
@@ -2,9 +2,9 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
3
3
|
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
4
4
|
import express from 'express';
|
|
5
|
+
import { OperationCatalog } from '../api/catalog.js';
|
|
5
6
|
import { createServer, createSharedDeps } from '../server.js';
|
|
6
7
|
import { Session } from '../session.js';
|
|
7
|
-
/** Заголовки, которыми клиент передаёт свои учётные данные CMS. */
|
|
8
8
|
const HEADER_TOKEN = 'x-cms-token';
|
|
9
9
|
const HEADER_LOGIN = 'x-cms-login';
|
|
10
10
|
const HEADER_PASSWORD = 'x-cms-password';
|
|
@@ -13,11 +13,6 @@ const headerValue = (request, name) => {
|
|
|
13
13
|
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
14
14
|
return typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined;
|
|
15
15
|
};
|
|
16
|
-
/**
|
|
17
|
-
* Читает учётные данные сессии из заголовков подключения.
|
|
18
|
-
* Только отсюда и из конфигурации сервера — никогда из аргументов инструмента:
|
|
19
|
-
* иначе внедрённая в текст инструкция могла бы подменить личность.
|
|
20
|
-
*/
|
|
21
16
|
const credentialsFromRequest = (request) => {
|
|
22
17
|
const token = headerValue(request, HEADER_TOKEN);
|
|
23
18
|
if (token) {
|
|
@@ -30,10 +25,6 @@ const credentialsFromRequest = (request) => {
|
|
|
30
25
|
}
|
|
31
26
|
return undefined;
|
|
32
27
|
};
|
|
33
|
-
/**
|
|
34
|
-
* Проверка Origin. Транспорт SDK умеет это сам, но помечает как deprecated
|
|
35
|
-
* в пользу внешней middleware — делаем внешнюю.
|
|
36
|
-
*/
|
|
37
28
|
const originGuard = (allowedOrigins) => (request, response, next) => {
|
|
38
29
|
const origin = headerValue(request, 'origin');
|
|
39
30
|
if (!origin) {
|
|
@@ -48,13 +39,22 @@ const originGuard = (allowedOrigins) => (request, response, next) => {
|
|
|
48
39
|
error: `Origin "${origin}" is not allowed. Start the server with --allowed-origins to permit it.`,
|
|
49
40
|
});
|
|
50
41
|
};
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
42
|
+
const fillCatalog = async (deps, session) => {
|
|
43
|
+
if (!deps.catalog.isEmpty || !session.isConfigured) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const bearer = await session.accessToken();
|
|
48
|
+
const rebuilt = await OperationCatalog.resolve(deps.config, bearer);
|
|
49
|
+
if (!rebuilt.isEmpty) {
|
|
50
|
+
deps.catalog.adopt(rebuilt);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
56
|
export const runHttp = async (config) => {
|
|
57
|
-
const deps = createSharedDeps(config);
|
|
57
|
+
const deps = await createSharedDeps(config);
|
|
58
58
|
const sessions = new Map();
|
|
59
59
|
const app = express();
|
|
60
60
|
app.use(express.json({ limit: '4mb' }));
|
|
@@ -85,6 +85,7 @@ export const runHttp = async (config) => {
|
|
|
85
85
|
},
|
|
86
86
|
});
|
|
87
87
|
const session = new Session(deps, credentialsFromRequest(request));
|
|
88
|
+
void fillCatalog(deps, session);
|
|
88
89
|
const server = createServer(deps, () => session);
|
|
89
90
|
transport.onclose = () => {
|
|
90
91
|
if (transport.sessionId) {
|
|
@@ -94,7 +95,6 @@ export const runHttp = async (config) => {
|
|
|
94
95
|
await server.connect(transport);
|
|
95
96
|
await transport.handleRequest(request, response, request.body);
|
|
96
97
|
});
|
|
97
|
-
/** GET открывает SSE-поток, DELETE закрывает сессию — обе требуют существующей сессии. */
|
|
98
98
|
const requireSession = async (request, response) => {
|
|
99
99
|
const sessionId = headerValue(request, 'mcp-session-id');
|
|
100
100
|
const managed = sessionId ? sessions.get(sessionId) : undefined;
|
|
@@ -123,4 +123,3 @@ export const runHttp = async (config) => {
|
|
|
123
123
|
},
|
|
124
124
|
};
|
|
125
125
|
};
|
|
126
|
-
//# sourceMappingURL=http.js.map
|
|
@@ -1,8 +1,2 @@
|
|
|
1
1
|
import type { Config } from '../config/config.js';
|
|
2
|
-
/**
|
|
3
|
-
* Запускает сервер поверх stdio — local-режим.
|
|
4
|
-
* Сессия одна на процесс: клиент сам порождает процесс на подключение,
|
|
5
|
-
* поэтому изоляция обеспечивается операционной системой, а не нашим кодом.
|
|
6
|
-
*/
|
|
7
2
|
export declare const runStdio: (config: Config) => Promise<void>;
|
|
8
|
-
//# sourceMappingURL=stdio.d.ts.map
|
package/dist/transports/stdio.js
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
2
2
|
import { createServer, createSharedDeps } from '../server.js';
|
|
3
3
|
import { Session } from '../session.js';
|
|
4
|
-
/**
|
|
5
|
-
* Запускает сервер поверх stdio — local-режим.
|
|
6
|
-
* Сессия одна на процесс: клиент сам порождает процесс на подключение,
|
|
7
|
-
* поэтому изоляция обеспечивается операционной системой, а не нашим кодом.
|
|
8
|
-
*/
|
|
9
4
|
export const runStdio = async (config) => {
|
|
10
|
-
const deps = createSharedDeps(config);
|
|
5
|
+
const deps = await createSharedDeps(config);
|
|
11
6
|
const session = new Session(deps);
|
|
12
7
|
const server = createServer(deps, () => session);
|
|
13
8
|
await server.connect(new StdioServerTransport());
|
|
14
9
|
};
|
|
15
|
-
//# sourceMappingURL=stdio.js.map
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Operating rules for the OneEntry Admin API
|
|
2
|
+
|
|
3
|
+
Read this before your first write. Every rule here has broken a real payload, and each one links to the document that explains it in full.
|
|
4
|
+
|
|
5
|
+
These rules are short on purpose. When one of them applies to what you are about to do, follow the pointer before you build the body.
|
|
6
|
+
|
|
7
|
+
→ `mcp/docs/server/doc-map` · `mcp/docs/server/payload-conventions`
|
|
8
|
+
|
|
9
|
+
## The loop you must follow
|
|
10
|
+
|
|
11
|
+
1. `cms_guide` once, at the start.
|
|
12
|
+
2. `cms_docs_search` for the entity you are about to touch — **before** you write a payload, not after a 400.
|
|
13
|
+
3. `cms_api_search` to find the operation, then `cms_api_describe` for its exact shape.
|
|
14
|
+
4. `cms_api_call` with `dryRun: true` for anything that mutates, then again with the confirm token if one was issued.
|
|
15
|
+
|
|
16
|
+
Never invent a path, an operation id or a body key. `cms_api_search` is the only authority on what exists; the documents are the authority on what the values mean.
|
|
17
|
+
|
|
18
|
+
## Trust the example not the type
|
|
19
|
+
|
|
20
|
+
The OpenAPI document this catalog is built from contains field types that are not JSON Schema types. `cms_api_describe` normalises what it can and marks the rest `"x-loose": true`, with the original under `x-source-type`.
|
|
21
|
+
|
|
22
|
+
For a loose field, **the `example` is the contract**. Copy its shape. Client-side validation of your body is advisory only — the instance is the real validator, so a call is never blocked because a loose field could not be checked.
|
|
23
|
+
|
|
24
|
+
→ `mcp/docs/server/cms-api-describe#loose-fields`
|
|
25
|
+
|
|
26
|
+
## Content is locale keyed
|
|
27
|
+
|
|
28
|
+
Titles and descriptive content live under `localizeInfos`, keyed by locale code:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{ "localizeInfos": { "en_US": { "title": "Summer sale" } } }
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Required when creating a product, and effectively required on pages. Do not hardcode `en_US`: read the active locale codes from the instance first, and write every locale the instance has active if the content is meant to be visible in all of them.
|
|
35
|
+
|
|
36
|
+
→ `mcp/docs/api/locales`
|
|
37
|
+
|
|
38
|
+
## Attribute values are two level and locale keyed
|
|
39
|
+
|
|
40
|
+
`attributesSets` is **two levels deep** — locale, then attribute key:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{ "attributesSets": { "en_US": { "string_id42": "SKU-1" } } }
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The inner key is `<attribute type>_id<attribute id>`, taken from the attribute set the entity belongs to. Read the set before you build the body.
|
|
47
|
+
|
|
48
|
+
A flat single-level map is accepted and stored empty. The call answers 201 and the attributes are silently missing, so always read the entity back by id after creating it.
|
|
49
|
+
|
|
50
|
+
→ `mcp/docs/api/attribute-sets`
|
|
51
|
+
|
|
52
|
+
## Positions are lexorank or numeric depending on the endpoint
|
|
53
|
+
|
|
54
|
+
Ordering is a lexorank **string** on parent-scoped Admin operations, and a **number** on flat lists and the Content API. Never sort a lexorank value numerically, and never reorder by patching the field directly — use the dedicated position operations of that entity.
|
|
55
|
+
|
|
56
|
+
→ `mcp/docs/server/payload-conventions#position-is-a-lexorank-string-or-a-number`
|
|
57
|
+
|
|
58
|
+
## A read straight after a write can lag
|
|
59
|
+
|
|
60
|
+
Reading an entity **by id** shows your write immediately. List and search responses may not, for a few seconds.
|
|
61
|
+
|
|
62
|
+
If a list does not yet show what you just created, re-read by id to confirm it exists. **Never repeat the write** — you will create a duplicate that consumes instance quota and has to be cleaned up by hand.
|
|
63
|
+
|
|
64
|
+
## Prefer marker over id
|
|
65
|
+
|
|
66
|
+
Blocks, forms, menus, templates, general types and modules are addressed by a `marker` or `identifier` that is stable across instances. A numeric `id` is not: an id taken from one instance is meaningless on another, and `404 Not found` on an id you were given is usually that mistake.
|
|
67
|
+
|
|
68
|
+
Where an operation accepts either, use the marker.
|
|
69
|
+
|
|
70
|
+
## Baseline data already exists do not recreate it
|
|
71
|
+
|
|
72
|
+
Every instance arrives populated: a guest user group with its content-API permissions, the admin modules, the general types, the attribute set types and field types, the locales, the dynamic block types and their default templates, and the singleton settings records.
|
|
73
|
+
|
|
74
|
+
**List first, create second.** Some duplicates fail loudly, which is harmless. The dangerous ones — user groups, modules, attribute set types, settings records — **succeed silently** and leave a shadow record that nothing references.
|
|
75
|
+
|
|
76
|
+
→ `mcp/docs/api/baseline-data`
|
|
77
|
+
|
|
78
|
+
## Never touch these without a human saying so
|
|
79
|
+
|
|
80
|
+
Mutations under `immutable-settings`, `admins`, `backups`, `modules`, `payments/webhook`, `settings-general`, `system/captcha-keys` and `auth/logout/all-users` are permanently confirm-gated by this server, at every allow level.
|
|
81
|
+
|
|
82
|
+
The gate is not a suggestion. State what you intend to change, show the human the `target` the dry run returned, and wait for them to say yes in this conversation.
|
|
83
|
+
|
|
84
|
+
→ `mcp/docs/server/allow-levels#paths-that-are-always-confirm-gated`
|
|
85
|
+
|
|
86
|
+
## Permissions are checked before the request is sent
|
|
87
|
+
|
|
88
|
+
Each operation declares the permission it requires, and this server refuses locally when the authenticated admin does not hold it. Nothing is sent, so nothing changed.
|
|
89
|
+
|
|
90
|
+
A permission refusal means **ask for the grant** and stop. Retrying cannot succeed, and neither can a different operation that needs the same permission.
|
|
91
|
+
|
|
92
|
+
→ `mcp/docs/api/admins-and-permissions`
|
|
93
|
+
|
|
94
|
+
## Truncated responses are deliberate
|
|
95
|
+
|
|
96
|
+
Large responses come back with a `_truncated` envelope reporting what was shown and what the total was. That is this server capping what it hands you, not the API limiting itself.
|
|
97
|
+
|
|
98
|
+
Do not retry hoping for more. Narrow the request with the operation's own `limit`, `offset` and filter parameters.
|
|
99
|
+
|
|
100
|
+
→ `mcp/docs/server/response-shaping`
|
|
101
|
+
|
|
102
|
+
## Operations with a single supported path
|
|
103
|
+
|
|
104
|
+
For the calls below, one route works and the obvious alternative does not. Use the supported one directly — a different body or a retry on the other route will not help.
|
|
105
|
+
|
|
106
|
+
- **Create a form** — send the payload wrapped in `newForm`. The OpenAPI document shows the fields unwrapped; the wrapped form is the one the endpoint accepts.
|
|
107
|
+
- **Update a product** — always include `blocks` (send `blocks: []` when you have nothing to set), and never include `forms`.
|
|
108
|
+
- **Read locale codes** — use `AdminLocalesController_findAllActive`.
|
|
109
|
+
- **List products** — the list operation is `POST /products/all`; there is no `GET /products`.
|
|
110
|
+
- **Export operations** — `orders.export`, `payments.export` and `users.export` are not grantable on current instances. Treat them as unavailable and tell the human rather than retrying.
|
|
111
|
+
|
|
112
|
+
If a call outside this list answers 5xx, stop and report it with the operation id and the request you sent. Do not retry it with a modified body.
|
|
113
|
+
|
|
114
|
+
## Where to look next
|
|
115
|
+
|
|
116
|
+
With the operation hints removed from `cms_api_describe`, `cms_docs_search` and the map below are how you find anything.
|
|
117
|
+
|
|
118
|
+
- `mcp/docs/server/doc-map` — every document, with when to read it
|
|
119
|
+
- `mcp/docs/server/payload-conventions` — the rules above, in full
|
|
120
|
+
- `mcp/docs/api/baseline-data` — what already exists on your instance
|
|
121
|
+
- `mcp/docs/server/errors-and-refusals` — what a specific error means and what to do next
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oneentry/mcp-platform-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "MCP server that lets an AI agent operate the OneEntry Admin API, grounded in the project's own rules",
|
|
5
|
-
"license": "
|
|
5
|
+
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=20"
|
|
@@ -12,8 +12,20 @@
|
|
|
12
12
|
},
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
15
|
-
"url": "git+https://
|
|
15
|
+
"url": "git+https://github.com/ONEENTRY-PLATFORM/oneentry-platform-mcp.git"
|
|
16
16
|
},
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/ONEENTRY-PLATFORM/oneentry-platform-mcp/issues"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/ONEENTRY-PLATFORM/oneentry-platform-mcp#readme",
|
|
21
|
+
"keywords": [
|
|
22
|
+
"mcp",
|
|
23
|
+
"model-context-protocol",
|
|
24
|
+
"oneentry",
|
|
25
|
+
"cms",
|
|
26
|
+
"admin-api",
|
|
27
|
+
"ai-agent"
|
|
28
|
+
],
|
|
17
29
|
"publishConfig": {
|
|
18
30
|
"access": "public"
|
|
19
31
|
},
|
|
@@ -28,16 +40,17 @@
|
|
|
28
40
|
"files": [
|
|
29
41
|
"dist",
|
|
30
42
|
"data",
|
|
31
|
-
"
|
|
43
|
+
"knowledge",
|
|
44
|
+
"README.md",
|
|
45
|
+
"LICENSE"
|
|
32
46
|
],
|
|
33
47
|
"scripts": {
|
|
34
48
|
"build": "tsc -p tsconfig.build.json",
|
|
35
49
|
"prepublishOnly": "npm run build",
|
|
36
|
-
"prestart": "npm run check:drift",
|
|
37
50
|
"start": "node dist/bin/cli.js",
|
|
38
51
|
"dev": "tsx src/bin/cli.ts",
|
|
39
|
-
"sync:
|
|
40
|
-
"
|
|
52
|
+
"sync:permissions": "tsx build/sync-permissions.ts",
|
|
53
|
+
"publish:knowledge": "node -e \"console.error('REFUSED: the knowledge repository is hand-authored. Bulk-copying internal docs into it is prohibited.'); process.exit(1)\"",
|
|
41
54
|
"lint": "eslint \"{src,build,__tests__}/**/*.ts\"",
|
|
42
55
|
"test": "vitest run",
|
|
43
56
|
"test:watch": "vitest",
|