@oneentry/mcp-server 1.1.6 → 1.1.7
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/README.md +42 -38
- package/dist/index.js +200 -113
- package/package.json +1 -1
- package/src/index.ts +231 -144
package/README.md
CHANGED
|
@@ -6,13 +6,13 @@ MCP server for OneEntry SDK — gives AI assistants (Claude Code, Cursor, Windsu
|
|
|
6
6
|
|
|
7
7
|
## Two ways to connect
|
|
8
8
|
|
|
9
|
-
| | npm package
|
|
10
|
-
| ------------ |
|
|
11
|
-
| **Install** | `npx -y @oneentry/mcp-server@latest` | No install — just a URL
|
|
12
|
-
| **Works in** | Claude Code only
|
|
13
|
-
| **Requires** | Nothing
|
|
14
|
-
| **Provides** | Rules + Skills (context docs)
|
|
15
|
-
| **Best for** | Quick local setup
|
|
9
|
+
| | npm package | Remote server |
|
|
10
|
+
| ------------ | ------------------------------------ | -------------------------------------------- |
|
|
11
|
+
| **Install** | `npx -y @oneentry/mcp-server@latest` | No install — just a URL |
|
|
12
|
+
| **Works in** | Claude Code only | Claude Code, Cursor, Windsurf |
|
|
13
|
+
| **Requires** | Nothing | OneEntry project token + URL |
|
|
14
|
+
| **Provides** | Rules + Skills (context docs) | Rules + Skills + **SDK documentation tools** |
|
|
15
|
+
| **Best for** | Quick local setup | Full AI-assisted development |
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
@@ -31,16 +31,16 @@ You will need:
|
|
|
31
31
|
|
|
32
32
|
```json
|
|
33
33
|
{
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
34
|
+
"mcpServers": {
|
|
35
|
+
"oneentry": {
|
|
36
|
+
"type": "streamable-http",
|
|
37
|
+
"url": "https://mcp-sdk-js.oneentry.cloud/mcp",
|
|
38
|
+
"headers": {
|
|
39
|
+
"X-OneEntry-Token": "YOUR_TOKEN",
|
|
40
|
+
"X-OneEntry-URL": "https://yourproject.oneentry.cloud"
|
|
41
|
+
}
|
|
43
42
|
}
|
|
43
|
+
}
|
|
44
44
|
}
|
|
45
45
|
```
|
|
46
46
|
|
|
@@ -48,15 +48,15 @@ You will need:
|
|
|
48
48
|
|
|
49
49
|
```json
|
|
50
50
|
{
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
51
|
+
"mcpServers": {
|
|
52
|
+
"oneentry": {
|
|
53
|
+
"url": "https://mcp-sdk-js.oneentry.cloud/mcp",
|
|
54
|
+
"headers": {
|
|
55
|
+
"X-OneEntry-Token": "YOUR_TOKEN",
|
|
56
|
+
"X-OneEntry-URL": "https://yourproject.oneentry.cloud"
|
|
57
|
+
}
|
|
59
58
|
}
|
|
59
|
+
}
|
|
60
60
|
}
|
|
61
61
|
```
|
|
62
62
|
|
|
@@ -64,14 +64,14 @@ You will need:
|
|
|
64
64
|
|
|
65
65
|
Add via **Settings → MCP Servers**:
|
|
66
66
|
|
|
67
|
-
```
|
|
67
|
+
```text
|
|
68
68
|
URL: https://mcp-sdk-js.oneentry.cloud/mcp?token=YOUR_TOKEN&url=https://yourproject.oneentry.cloud
|
|
69
69
|
Authentication: None
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
### Alternative — custom headers (instead of query params)
|
|
73
73
|
|
|
74
|
-
```
|
|
74
|
+
```text
|
|
75
75
|
URL: https://mcp-sdk-js.oneentry.cloud/mcp
|
|
76
76
|
X-OneEntry-Token: YOUR_TOKEN
|
|
77
77
|
X-OneEntry-URL: https://yourproject.oneentry.cloud
|
|
@@ -96,33 +96,37 @@ Serves rules and skills as readable context documents. No token required.
|
|
|
96
96
|
}
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
**
|
|
99
|
+
**Local install (per project):**
|
|
100
100
|
|
|
101
101
|
```bash
|
|
102
|
-
npm install -
|
|
102
|
+
npm install --save-dev @oneentry/mcp-server
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
```json
|
|
106
106
|
{
|
|
107
107
|
"mcpServers": {
|
|
108
108
|
"oneentry": {
|
|
109
|
-
"command": "oneentry-mcp"
|
|
109
|
+
"command": "./node_modules/.bin/oneentry-mcp"
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
## ⚠️ Required for Claude Code: add to your project's CLAUDE.md
|
|
118
|
-
|
|
119
|
-
After connecting the MCP server, create or edit `CLAUDE.md` in the root of your project and add:
|
|
115
|
+
**Global install:**
|
|
120
116
|
|
|
121
|
-
```
|
|
122
|
-
|
|
117
|
+
```bash
|
|
118
|
+
npm install -g @oneentry/mcp-server
|
|
123
119
|
```
|
|
124
120
|
|
|
125
|
-
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"mcpServers": {
|
|
124
|
+
"oneentry": {
|
|
125
|
+
"command": "oneentry-mcp"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
126
130
|
|
|
127
131
|
---
|
|
128
132
|
|
|
@@ -152,7 +156,7 @@ Invoke in Claude Code with `/mcp__oneentry__<name>`:
|
|
|
152
156
|
|
|
153
157
|
| Prompt | Description |
|
|
154
158
|
| ---------------------------- | ----------------------------------------------------- |
|
|
155
|
-
| `oneentry-context` |
|
|
159
|
+
| `oneentry-context` | Reload full SDK context manually |
|
|
156
160
|
| `setup-nextjs` | Initialize Next.js project |
|
|
157
161
|
| `setup-oneentry` | Initialize SDK in a Next.js project |
|
|
158
162
|
| `inspect-api` | Inspect real API markers and structure |
|
package/dist/index.js
CHANGED
|
@@ -5,127 +5,214 @@ import { z } from "zod";
|
|
|
5
5
|
import { fetchContent } from "./content.js";
|
|
6
6
|
import { RULES, SKILLS } from "./registry.js";
|
|
7
7
|
import { CLAUDE_MD_PATH, SERVER_NAME, VERSION } from "./config.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Точка входа MCP-сервера OneEntry SDK.
|
|
10
|
+
*
|
|
11
|
+
* ## Архитектура
|
|
12
|
+
*
|
|
13
|
+
* MCP (Model Context Protocol) — протокол, по которому AI-клиент (Claude Code,
|
|
14
|
+
* Cursor, Windsurf) общается с внешним сервером контекста. Сервер предоставляет
|
|
15
|
+
* три типа объектов:
|
|
16
|
+
*
|
|
17
|
+
* - **Resources** — документы, которые AI может прочитать по URI (`@oneentry://...`).
|
|
18
|
+
* Аналог файловой системы: клиент сам решает, когда читать.
|
|
19
|
+
*
|
|
20
|
+
* - **Prompts** — готовые шаблоны сообщений (скиллы). AI-клиент подставляет их
|
|
21
|
+
* в чат по команде пользователя (`/mcp__oneentry__<name>`).
|
|
22
|
+
*
|
|
23
|
+
* - **Tools** — функции, которые AI вызывает автоматически по ситуации.
|
|
24
|
+
* Возвращают текст прямо в контекст разговора.
|
|
25
|
+
*
|
|
26
|
+
* ## Как загружается CLAUDE.md
|
|
27
|
+
*
|
|
28
|
+
* При подключении MCP-сервер отвечает на `initialize`-запрос клиента и
|
|
29
|
+
* включает в ответ поле `instructions`. Claude Code читает это поле и
|
|
30
|
+
* автоматически добавляет его в системный контекст сессии — без каких-либо
|
|
31
|
+
* дополнительных действий от пользователя.
|
|
32
|
+
*
|
|
33
|
+
* Поэтому CLAUDE.md загружается **первым делом**, до регистрации всего
|
|
34
|
+
* остального, и передаётся в конструктор `McpServer` через `{ instructions }`.
|
|
35
|
+
*/
|
|
36
|
+
async function main() {
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Загрузка CLAUDE.md и создание сервера
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
/**
|
|
41
|
+
* Загружаем CLAUDE.md с GitHub до создания сервера — нам нужно передать
|
|
42
|
+
* содержимое в конструктор как `instructions`.
|
|
43
|
+
*
|
|
44
|
+
* `instructions` — стандартное поле MCP InitializeResult. Клиент получает
|
|
45
|
+
* его в ответ на первый `initialize`-запрос и автоматически включает в
|
|
46
|
+
* системный промпт сессии. Именно так другие MCP-серверы "работают сразу"
|
|
47
|
+
* без дополнительной настройки CLAUDE.md в проекте.
|
|
48
|
+
*/
|
|
49
|
+
const instructions = await fetchContent(CLAUDE_MD_PATH);
|
|
50
|
+
const server = new McpServer({ name: SERVER_NAME, version: VERSION }, { instructions });
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Resources — документы для явного чтения по URI
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
/**
|
|
55
|
+
* Главный ресурс: полный CLAUDE.md как документ.
|
|
56
|
+
* Доступен в Claude Code по `@oneentry://claude-md`.
|
|
57
|
+
* Полезен если нужно явно переподгрузить контекст или вставить его в чат.
|
|
58
|
+
*/
|
|
59
|
+
server.registerResource("OneEntry SDK — Main Instructions (CLAUDE.md)", "oneentry://claude-md", {
|
|
60
|
+
description: "Full system prompt with OneEntry SDK rules, patterns, anti-hallucination guidelines and module reference",
|
|
61
|
+
mimeType: "text/markdown",
|
|
62
|
+
}, async (uri) => {
|
|
63
|
+
const text = await fetchContent(CLAUDE_MD_PATH);
|
|
25
64
|
return { contents: [{ uri: uri.toString(), mimeType: "text/markdown", text }] };
|
|
26
65
|
});
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
messages: [
|
|
39
|
-
{
|
|
40
|
-
role: "user",
|
|
41
|
-
content: {
|
|
42
|
-
type: "text",
|
|
43
|
-
text: `The following are the complete instructions and rules for working with OneEntry SDK. Apply them to all subsequent code generation:\n\n${text}`,
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
};
|
|
48
|
-
});
|
|
49
|
-
// Skills
|
|
50
|
-
for (const skill of SKILLS) {
|
|
51
|
-
if (skill.hasArguments) {
|
|
52
|
-
server.registerPrompt(skill.name, {
|
|
53
|
-
description: skill.description,
|
|
54
|
-
argsSchema: {
|
|
55
|
-
arguments: z
|
|
56
|
-
.string()
|
|
57
|
-
.optional()
|
|
58
|
-
.describe(skill.argumentDescription ?? "Optional arguments"),
|
|
59
|
-
},
|
|
60
|
-
}, async (args) => {
|
|
61
|
-
let text = await fetchContent(skill.path);
|
|
62
|
-
// Replace $ARGUMENTS placeholder (used in inspect-api and create-page skills)
|
|
63
|
-
if (args.arguments) {
|
|
64
|
-
text = text.replace(/\$ARGUMENTS/g, args.arguments);
|
|
65
|
-
}
|
|
66
|
-
return {
|
|
67
|
-
description: skill.description,
|
|
68
|
-
messages: [{ role: "user", content: { type: "text", text } }],
|
|
69
|
-
};
|
|
66
|
+
/**
|
|
67
|
+
* Отдельные правила из registry.ts — каждое доступно по своему URI:
|
|
68
|
+
* `@oneentry://rules/linting`, `@oneentry://rules/tokens`, и т.д.
|
|
69
|
+
*
|
|
70
|
+
* Ресурсы удобны когда нужно прочитать только одно конкретное правило,
|
|
71
|
+
* не загружая весь CLAUDE.md целиком.
|
|
72
|
+
*/
|
|
73
|
+
for (const rule of RULES) {
|
|
74
|
+
server.registerResource(rule.displayName, `oneentry://rules/${rule.name}`, { description: rule.description, mimeType: "text/markdown" }, async (uri) => {
|
|
75
|
+
const text = await fetchContent(rule.path);
|
|
76
|
+
return { contents: [{ uri: uri.toString(), mimeType: "text/markdown", text }] };
|
|
70
77
|
});
|
|
71
78
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Prompts (скиллы) — шаблоны сообщений для вставки в чат
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
/**
|
|
83
|
+
* Специальный промпт для ручной перезагрузки контекста.
|
|
84
|
+
* Вызывается командой `/mcp__oneentry__oneentry-context`.
|
|
85
|
+
*
|
|
86
|
+
* Возвращает CLAUDE.md завёрнутым в сообщение с ролью `user` — это
|
|
87
|
+
* стандартный способ подачи контекста через MCP Prompts API.
|
|
88
|
+
* Клиент вставляет это сообщение в историю чата.
|
|
89
|
+
*/
|
|
90
|
+
server.registerPrompt("oneentry-context", {
|
|
91
|
+
description: "Load full OneEntry SDK context — system instructions, rules and anti-hallucination guidelines. Use this at the start of a session.",
|
|
92
|
+
}, async () => {
|
|
93
|
+
const text = await fetchContent(CLAUDE_MD_PATH);
|
|
94
|
+
return {
|
|
95
|
+
description: "OneEntry SDK — full system context loaded",
|
|
96
|
+
messages: [
|
|
97
|
+
{
|
|
98
|
+
role: "user",
|
|
99
|
+
content: {
|
|
100
|
+
type: "text",
|
|
101
|
+
text: `The following are the complete instructions and rules for working with OneEntry SDK. Apply them to all subsequent code generation:\n\n${text}`,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
/**
|
|
108
|
+
* Регистрируем все скиллы из registry.ts как MCP Prompts.
|
|
109
|
+
* Каждый скилл — это markdown-инструкция для выполнения конкретной задачи
|
|
110
|
+
* (создание страницы, формы, корзины и т.д.).
|
|
111
|
+
*
|
|
112
|
+
* Скиллы делятся на два типа:
|
|
113
|
+
* - `hasArguments: true` — принимают аргумент (например, маркер страницы),
|
|
114
|
+
* который подставляется вместо плейсхолдера `$ARGUMENTS` в тексте скилла.
|
|
115
|
+
* - без аргументов — возвращают текст как есть.
|
|
116
|
+
*/
|
|
117
|
+
for (const skill of SKILLS) {
|
|
118
|
+
if (skill.hasArguments) {
|
|
119
|
+
server.registerPrompt(skill.name, {
|
|
76
120
|
description: skill.description,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}, async (
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
server.registerTool("get-skill", {
|
|
106
|
-
description: "Fetch the content of a specific OneEntry skill/slash-command by name",
|
|
107
|
-
inputSchema: {
|
|
108
|
-
name: z.string().describe(`Skill name. Available: ${SKILLS.map((s) => s.name).join(", ")}`),
|
|
109
|
-
arguments: z.string().optional().describe("Optional arguments passed to the skill (replaces $ARGUMENTS placeholder)"),
|
|
110
|
-
},
|
|
111
|
-
}, async (args) => {
|
|
112
|
-
const skill = SKILLS.find((s) => s.name === args.name);
|
|
113
|
-
if (!skill) {
|
|
114
|
-
return { content: [{ type: "text", text: `Unknown skill: ${args.name}. Available: ${SKILLS.map((s) => s.name).join(", ")}` }], isError: true };
|
|
115
|
-
}
|
|
116
|
-
let text = await fetchContent(skill.path);
|
|
117
|
-
if (args.arguments) {
|
|
118
|
-
text = text.replace(/\$ARGUMENTS/g, args.arguments);
|
|
121
|
+
argsSchema: {
|
|
122
|
+
arguments: z
|
|
123
|
+
.string()
|
|
124
|
+
.optional()
|
|
125
|
+
.describe(skill.argumentDescription ?? "Optional arguments"),
|
|
126
|
+
},
|
|
127
|
+
}, async (args) => {
|
|
128
|
+
let text = await fetchContent(skill.path);
|
|
129
|
+
// Подставляем аргумент вместо плейсхолдера $ARGUMENTS в тексте скилла.
|
|
130
|
+
// Используется, например, в inspect-api (тип данных) и create-page (маркер страницы).
|
|
131
|
+
if (args.arguments) {
|
|
132
|
+
text = text.replace(/\$ARGUMENTS/g, args.arguments);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
description: skill.description,
|
|
136
|
+
messages: [{ role: "user", content: { type: "text", text } }],
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
server.registerPrompt(skill.name, { description: skill.description }, async () => {
|
|
142
|
+
const text = await fetchContent(skill.path);
|
|
143
|
+
return {
|
|
144
|
+
description: skill.description,
|
|
145
|
+
messages: [{ role: "user", content: { type: "text", text } }],
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
}
|
|
119
149
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
// ---------------------------------------------------------------------------
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Tools — функции, вызываемые AI автоматически по ситуации
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
/**
|
|
154
|
+
* Инструмент для ручной перезагрузки CLAUDE.md в контекст разговора.
|
|
155
|
+
* В отличие от `instructions` (которые загружаются один раз при коннекте),
|
|
156
|
+
* этот tool позволяет получить свежую версию документа прямо в чат —
|
|
157
|
+
* например, если кэш устарел или контекст был сброшен.
|
|
158
|
+
*/
|
|
159
|
+
server.registerTool("load-context", {
|
|
160
|
+
description: "Reload the full OneEntry SDK instructions, rules, and anti-hallucination guidelines into the context. " +
|
|
161
|
+
"Call this if you need a fresh copy of the SDK documentation.",
|
|
162
|
+
inputSchema: {},
|
|
163
|
+
}, async () => {
|
|
164
|
+
const text = await fetchContent(CLAUDE_MD_PATH);
|
|
165
|
+
return { content: [{ type: "text", text }] };
|
|
166
|
+
});
|
|
167
|
+
/**
|
|
168
|
+
* Инструмент для загрузки отдельного правила по имени.
|
|
169
|
+
* AI может вызвать его когда нужно уточнить конкретный аспект SDK
|
|
170
|
+
* (например, правила работы с токенами или формами) без загрузки всего CLAUDE.md.
|
|
171
|
+
*/
|
|
172
|
+
server.registerTool("get-rule", {
|
|
173
|
+
description: "Fetch the content of a specific OneEntry SDK rule by name",
|
|
174
|
+
inputSchema: { name: z.string().describe(`Rule name. Available: ${RULES.map((r) => r.name).join(", ")}`) },
|
|
175
|
+
}, async ({ name }) => {
|
|
176
|
+
const rule = RULES.find((r) => r.name === name);
|
|
177
|
+
if (!rule) {
|
|
178
|
+
return { content: [{ type: "text", text: `Unknown rule: ${name}. Available: ${RULES.map((r) => r.name).join(", ")}` }], isError: true };
|
|
179
|
+
}
|
|
180
|
+
const text = await fetchContent(rule.path);
|
|
181
|
+
return { content: [{ type: "text", text }] };
|
|
182
|
+
});
|
|
183
|
+
/**
|
|
184
|
+
* Инструмент для загрузки конкретного скилла по имени.
|
|
185
|
+
* Аналог вызова промпта, но через tool API — удобно когда AI сам
|
|
186
|
+
* определяет что нужен тот или иной скилл в процессе работы.
|
|
187
|
+
* Поддерживает аргументы: подставляет их вместо `$ARGUMENTS` в тексте.
|
|
188
|
+
*/
|
|
189
|
+
server.registerTool("get-skill", {
|
|
190
|
+
description: "Fetch the content of a specific OneEntry skill/slash-command by name",
|
|
191
|
+
inputSchema: {
|
|
192
|
+
name: z.string().describe(`Skill name. Available: ${SKILLS.map((s) => s.name).join(", ")}`),
|
|
193
|
+
arguments: z.string().optional().describe("Optional arguments passed to the skill (replaces $ARGUMENTS placeholder)"),
|
|
194
|
+
},
|
|
195
|
+
}, async (args) => {
|
|
196
|
+
const skill = SKILLS.find((s) => s.name === args.name);
|
|
197
|
+
if (!skill) {
|
|
198
|
+
return { content: [{ type: "text", text: `Unknown skill: ${args.name}. Available: ${SKILLS.map((s) => s.name).join(", ")}` }], isError: true };
|
|
199
|
+
}
|
|
200
|
+
let text = await fetchContent(skill.path);
|
|
201
|
+
if (args.arguments) {
|
|
202
|
+
text = text.replace(/\$ARGUMENTS/g, args.arguments);
|
|
203
|
+
}
|
|
204
|
+
return { content: [{ type: "text", text }] };
|
|
205
|
+
});
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
// Запуск транспорта
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
/**
|
|
210
|
+
* StdioServerTransport — транспорт на основе stdin/stdout.
|
|
211
|
+
* MCP-клиент запускает этот процесс и общается с ним через стандартные потоки.
|
|
212
|
+
* Все логи пишем в stderr чтобы не засорять MCP-протокол в stdout.
|
|
213
|
+
*/
|
|
126
214
|
const transport = new StdioServerTransport();
|
|
127
215
|
await server.connect(transport);
|
|
128
|
-
// Log to stderr so it doesn't interfere with MCP stdio protocol
|
|
129
216
|
process.stderr.write("OneEntry MCP server running\n");
|
|
130
217
|
}
|
|
131
218
|
main().catch((err) => {
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -6,176 +6,263 @@ import { fetchContent } from "./content.js";
|
|
|
6
6
|
import { RULES, SKILLS } from "./registry.js";
|
|
7
7
|
import { CLAUDE_MD_PATH, SERVER_NAME, VERSION } from "./config.js";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Точка входа MCP-сервера OneEntry SDK.
|
|
11
|
+
*
|
|
12
|
+
* ## Архитектура
|
|
13
|
+
*
|
|
14
|
+
* MCP (Model Context Protocol) — протокол, по которому AI-клиент (Claude Code,
|
|
15
|
+
* Cursor, Windsurf) общается с внешним сервером контекста. Сервер предоставляет
|
|
16
|
+
* три типа объектов:
|
|
17
|
+
*
|
|
18
|
+
* - **Resources** — документы, которые AI может прочитать по URI (`@oneentry://...`).
|
|
19
|
+
* Аналог файловой системы: клиент сам решает, когда читать.
|
|
20
|
+
*
|
|
21
|
+
* - **Prompts** — готовые шаблоны сообщений (скиллы). AI-клиент подставляет их
|
|
22
|
+
* в чат по команде пользователя (`/mcp__oneentry__<name>`).
|
|
23
|
+
*
|
|
24
|
+
* - **Tools** — функции, которые AI вызывает автоматически по ситуации.
|
|
25
|
+
* Возвращают текст прямо в контекст разговора.
|
|
26
|
+
*
|
|
27
|
+
* ## Как загружается CLAUDE.md
|
|
28
|
+
*
|
|
29
|
+
* При подключении MCP-сервер отвечает на `initialize`-запрос клиента и
|
|
30
|
+
* включает в ответ поле `instructions`. Claude Code читает это поле и
|
|
31
|
+
* автоматически добавляет его в системный контекст сессии — без каких-либо
|
|
32
|
+
* дополнительных действий от пользователя.
|
|
33
|
+
*
|
|
34
|
+
* Поэтому CLAUDE.md загружается **первым делом**, до регистрации всего
|
|
35
|
+
* остального, и передаётся в конструктор `McpServer` через `{ instructions }`.
|
|
36
|
+
*/
|
|
37
|
+
async function main() {
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Загрузка CLAUDE.md и создание сервера
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
14
41
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Загружаем CLAUDE.md с GitHub до создания сервера — нам нужно передать
|
|
44
|
+
* содержимое в конструктор как `instructions`.
|
|
45
|
+
*
|
|
46
|
+
* `instructions` — стандартное поле MCP InitializeResult. Клиент получает
|
|
47
|
+
* его в ответ на первый `initialize`-запрос и автоматически включает в
|
|
48
|
+
* системный промпт сессии. Именно так другие MCP-серверы "работают сразу"
|
|
49
|
+
* без дополнительной настройки CLAUDE.md в проекте.
|
|
50
|
+
*/
|
|
51
|
+
const instructions = await fetchContent(CLAUDE_MD_PATH);
|
|
52
|
+
const server = new McpServer({ name: SERVER_NAME, version: VERSION }, { instructions });
|
|
18
53
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
{
|
|
23
|
-
description:
|
|
24
|
-
"Full system prompt with OneEntry SDK rules, patterns, anti-hallucination guidelines and module reference",
|
|
25
|
-
mimeType: "text/markdown",
|
|
26
|
-
},
|
|
27
|
-
async (uri) => {
|
|
28
|
-
const text = await fetchContent(CLAUDE_MD_PATH);
|
|
29
|
-
return { contents: [{ uri: uri.toString(), mimeType: "text/markdown", text }] };
|
|
30
|
-
}
|
|
31
|
-
);
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Resources — документы для явного чтения по URI
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
32
57
|
|
|
33
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Главный ресурс: полный CLAUDE.md как документ.
|
|
60
|
+
* Доступен в Claude Code по `@oneentry://claude-md`.
|
|
61
|
+
* Полезен если нужно явно переподгрузить контекст или вставить его в чат.
|
|
62
|
+
*/
|
|
34
63
|
server.registerResource(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
{
|
|
64
|
+
"OneEntry SDK — Main Instructions (CLAUDE.md)",
|
|
65
|
+
"oneentry://claude-md",
|
|
66
|
+
{
|
|
67
|
+
description:
|
|
68
|
+
"Full system prompt with OneEntry SDK rules, patterns, anti-hallucination guidelines and module reference",
|
|
69
|
+
mimeType: "text/markdown",
|
|
70
|
+
},
|
|
38
71
|
async (uri) => {
|
|
39
|
-
const text = await fetchContent(
|
|
72
|
+
const text = await fetchContent(CLAUDE_MD_PATH);
|
|
40
73
|
return { contents: [{ uri: uri.toString(), mimeType: "text/markdown", text }] };
|
|
41
74
|
}
|
|
42
75
|
);
|
|
43
|
-
}
|
|
44
76
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Отдельные правила из registry.ts — каждое доступно по своему URI:
|
|
79
|
+
* `@oneentry://rules/linting`, `@oneentry://rules/tokens`, и т.д.
|
|
80
|
+
*
|
|
81
|
+
* Ресурсы удобны когда нужно прочитать только одно конкретное правило,
|
|
82
|
+
* не загружая весь CLAUDE.md целиком.
|
|
83
|
+
*/
|
|
84
|
+
for (const rule of RULES) {
|
|
85
|
+
server.registerResource(
|
|
86
|
+
rule.displayName,
|
|
87
|
+
`oneentry://rules/${rule.name}`,
|
|
88
|
+
{ description: rule.description, mimeType: "text/markdown" },
|
|
89
|
+
async (uri) => {
|
|
90
|
+
const text = await fetchContent(rule.path);
|
|
91
|
+
return { contents: [{ uri: uri.toString(), mimeType: "text/markdown", text }] };
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
}
|
|
48
95
|
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Prompts (скиллы) — шаблоны сообщений для вставки в чат
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Специальный промпт для ручной перезагрузки контекста.
|
|
102
|
+
* Вызывается командой `/mcp__oneentry__oneentry-context`.
|
|
103
|
+
*
|
|
104
|
+
* Возвращает CLAUDE.md завёрнутым в сообщение с ролью `user` — это
|
|
105
|
+
* стандартный способ подачи контекста через MCP Prompts API.
|
|
106
|
+
* Клиент вставляет это сообщение в историю чата.
|
|
107
|
+
*/
|
|
108
|
+
server.registerPrompt(
|
|
109
|
+
"oneentry-context",
|
|
110
|
+
{
|
|
111
|
+
description:
|
|
112
|
+
"Load full OneEntry SDK context — system instructions, rules and anti-hallucination guidelines. Use this at the start of a session.",
|
|
113
|
+
},
|
|
114
|
+
async () => {
|
|
115
|
+
const text = await fetchContent(CLAUDE_MD_PATH);
|
|
116
|
+
return {
|
|
117
|
+
description: "OneEntry SDK — full system context loaded",
|
|
118
|
+
messages: [
|
|
119
|
+
{
|
|
120
|
+
role: "user" as const,
|
|
121
|
+
content: {
|
|
122
|
+
type: "text" as const,
|
|
123
|
+
text: `The following are the complete instructions and rules for working with OneEntry SDK. Apply them to all subsequent code generation:\n\n${text}`,
|
|
124
|
+
},
|
|
66
125
|
},
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
);
|
|
126
|
+
],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
);
|
|
72
130
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Регистрируем все скиллы из registry.ts как MCP Prompts.
|
|
133
|
+
* Каждый скилл — это markdown-инструкция для выполнения конкретной задачи
|
|
134
|
+
* (создание страницы, формы, корзины и т.д.).
|
|
135
|
+
*
|
|
136
|
+
* Скиллы делятся на два типа:
|
|
137
|
+
* - `hasArguments: true` — принимают аргумент (например, маркер страницы),
|
|
138
|
+
* который подставляется вместо плейсхолдера `$ARGUMENTS` в тексте скилла.
|
|
139
|
+
* - без аргументов — возвращают текст как есть.
|
|
140
|
+
*/
|
|
141
|
+
for (const skill of SKILLS) {
|
|
142
|
+
if (skill.hasArguments) {
|
|
143
|
+
server.registerPrompt(
|
|
144
|
+
skill.name,
|
|
145
|
+
{
|
|
146
|
+
description: skill.description,
|
|
147
|
+
argsSchema: {
|
|
148
|
+
arguments: z
|
|
149
|
+
.string()
|
|
150
|
+
.optional()
|
|
151
|
+
.describe(skill.argumentDescription ?? "Optional arguments"),
|
|
152
|
+
},
|
|
85
153
|
},
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
154
|
+
async (args) => {
|
|
155
|
+
let text = await fetchContent(skill.path);
|
|
156
|
+
// Подставляем аргумент вместо плейсхолдера $ARGUMENTS в тексте скилла.
|
|
157
|
+
// Используется, например, в inspect-api (тип данных) и create-page (маркер страницы).
|
|
158
|
+
if (args.arguments) {
|
|
159
|
+
text = text.replace(/\$ARGUMENTS/g, args.arguments);
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
description: skill.description,
|
|
163
|
+
messages: [{ role: "user" as const, content: { type: "text" as const, text } }],
|
|
164
|
+
};
|
|
92
165
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
messages: [{ role: "user" as const, content: { type: "text" as const, text } }],
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
);
|
|
166
|
+
);
|
|
167
|
+
} else {
|
|
168
|
+
server.registerPrompt(
|
|
169
|
+
skill.name,
|
|
170
|
+
{ description: skill.description },
|
|
171
|
+
async () => {
|
|
172
|
+
const text = await fetchContent(skill.path);
|
|
173
|
+
return {
|
|
174
|
+
description: skill.description,
|
|
175
|
+
messages: [{ role: "user" as const, content: { type: "text" as const, text } }],
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
);
|
|
179
|
+
}
|
|
111
180
|
}
|
|
112
|
-
}
|
|
113
181
|
|
|
114
|
-
// ---------------------------------------------------------------------------
|
|
115
|
-
// Tools
|
|
116
|
-
// ---------------------------------------------------------------------------
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
// Tools — функции, вызываемые AI автоматически по ситуации
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
117
185
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
{
|
|
136
|
-
description: "Fetch the content of a specific OneEntry SDK rule by name",
|
|
137
|
-
inputSchema: { name: z.string().describe(`Rule name. Available: ${RULES.map((r) => r.name).join(", ")}`) },
|
|
138
|
-
},
|
|
139
|
-
async ({ name }) => {
|
|
140
|
-
const rule = RULES.find((r) => r.name === name);
|
|
141
|
-
if (!rule) {
|
|
142
|
-
return { content: [{ type: "text" as const, text: `Unknown rule: ${name}. Available: ${RULES.map((r) => r.name).join(", ")}` }], isError: true };
|
|
186
|
+
/**
|
|
187
|
+
* Инструмент для ручной перезагрузки CLAUDE.md в контекст разговора.
|
|
188
|
+
* В отличие от `instructions` (которые загружаются один раз при коннекте),
|
|
189
|
+
* этот tool позволяет получить свежую версию документа прямо в чат —
|
|
190
|
+
* например, если кэш устарел или контекст был сброшен.
|
|
191
|
+
*/
|
|
192
|
+
server.registerTool(
|
|
193
|
+
"load-context",
|
|
194
|
+
{
|
|
195
|
+
description:
|
|
196
|
+
"Reload the full OneEntry SDK instructions, rules, and anti-hallucination guidelines into the context. " +
|
|
197
|
+
"Call this if you need a fresh copy of the SDK documentation.",
|
|
198
|
+
inputSchema: {},
|
|
199
|
+
},
|
|
200
|
+
async () => {
|
|
201
|
+
const text = await fetchContent(CLAUDE_MD_PATH);
|
|
202
|
+
return { content: [{ type: "text" as const, text }] };
|
|
143
203
|
}
|
|
144
|
-
|
|
145
|
-
return { content: [{ type: "text" as const, text }] };
|
|
146
|
-
}
|
|
147
|
-
);
|
|
204
|
+
);
|
|
148
205
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
206
|
+
/**
|
|
207
|
+
* Инструмент для загрузки отдельного правила по имени.
|
|
208
|
+
* AI может вызвать его когда нужно уточнить конкретный аспект SDK
|
|
209
|
+
* (например, правила работы с токенами или формами) без загрузки всего CLAUDE.md.
|
|
210
|
+
*/
|
|
211
|
+
server.registerTool(
|
|
212
|
+
"get-rule",
|
|
213
|
+
{
|
|
214
|
+
description: "Fetch the content of a specific OneEntry SDK rule by name",
|
|
215
|
+
inputSchema: { name: z.string().describe(`Rule name. Available: ${RULES.map((r) => r.name).join(", ")}`) },
|
|
156
216
|
},
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
217
|
+
async ({ name }) => {
|
|
218
|
+
const rule = RULES.find((r) => r.name === name);
|
|
219
|
+
if (!rule) {
|
|
220
|
+
return { content: [{ type: "text" as const, text: `Unknown rule: ${name}. Available: ${RULES.map((r) => r.name).join(", ")}` }], isError: true };
|
|
221
|
+
}
|
|
222
|
+
const text = await fetchContent(rule.path);
|
|
223
|
+
return { content: [{ type: "text" as const, text }] };
|
|
162
224
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Инструмент для загрузки конкретного скилла по имени.
|
|
229
|
+
* Аналог вызова промпта, но через tool API — удобно когда AI сам
|
|
230
|
+
* определяет что нужен тот или иной скилл в процессе работы.
|
|
231
|
+
* Поддерживает аргументы: подставляет их вместо `$ARGUMENTS` в тексте.
|
|
232
|
+
*/
|
|
233
|
+
server.registerTool(
|
|
234
|
+
"get-skill",
|
|
235
|
+
{
|
|
236
|
+
description: "Fetch the content of a specific OneEntry skill/slash-command by name",
|
|
237
|
+
inputSchema: {
|
|
238
|
+
name: z.string().describe(`Skill name. Available: ${SKILLS.map((s) => s.name).join(", ")}`),
|
|
239
|
+
arguments: z.string().optional().describe("Optional arguments passed to the skill (replaces $ARGUMENTS placeholder)"),
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
async (args) => {
|
|
243
|
+
const skill = SKILLS.find((s) => s.name === args.name);
|
|
244
|
+
if (!skill) {
|
|
245
|
+
return { content: [{ type: "text" as const, text: `Unknown skill: ${args.name}. Available: ${SKILLS.map((s) => s.name).join(", ")}` }], isError: true };
|
|
246
|
+
}
|
|
247
|
+
let text = await fetchContent(skill.path);
|
|
248
|
+
if (args.arguments) {
|
|
249
|
+
text = text.replace(/\$ARGUMENTS/g, args.arguments);
|
|
250
|
+
}
|
|
251
|
+
return { content: [{ type: "text" as const, text }] };
|
|
166
252
|
}
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
);
|
|
253
|
+
);
|
|
170
254
|
|
|
171
|
-
// ---------------------------------------------------------------------------
|
|
172
|
-
//
|
|
173
|
-
// ---------------------------------------------------------------------------
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
// Запуск транспорта
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
174
258
|
|
|
175
|
-
|
|
259
|
+
/**
|
|
260
|
+
* StdioServerTransport — транспорт на основе stdin/stdout.
|
|
261
|
+
* MCP-клиент запускает этот процесс и общается с ним через стандартные потоки.
|
|
262
|
+
* Все логи пишем в stderr чтобы не засорять MCP-протокол в stdout.
|
|
263
|
+
*/
|
|
176
264
|
const transport = new StdioServerTransport();
|
|
177
265
|
await server.connect(transport);
|
|
178
|
-
// Log to stderr so it doesn't interfere with MCP stdio protocol
|
|
179
266
|
process.stderr.write("OneEntry MCP server running\n");
|
|
180
267
|
}
|
|
181
268
|
|