@contentrain/mcp 0.1.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/LICENSE +21 -0
- package/README.md +274 -0
- package/dist/fs-D83-Ii_y.mjs +75 -0
- package/dist/fs-D83-Ii_y.mjs.map +1 -0
- package/dist/index.d.mts +272 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1488 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Contentrain
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# @contentrain/mcp
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for [Contentrain](https://contentrain.io) CMS content management. Enables AI agents (Claude, Codex, Cursor, etc.) and humans to create, read, update, and delete Contentrain models, content, and assets — with automatic git branch synchronization.
|
|
4
|
+
|
|
5
|
+
## Why?
|
|
6
|
+
|
|
7
|
+
Contentrain is a git-based headless CMS. This MCP server lets AI assistants directly manage your Contentrain content through a standardized protocol, so you can say things like:
|
|
8
|
+
|
|
9
|
+
- *"Create a blog post model with title, excerpt, and cover image fields"*
|
|
10
|
+
- *"Add a new blog post in English and Turkish"*
|
|
11
|
+
- *"List all FAQ entries and update the second one"*
|
|
12
|
+
|
|
13
|
+
All changes are committed and pushed to your git repository automatically.
|
|
14
|
+
|
|
15
|
+
## Prerequisites
|
|
16
|
+
|
|
17
|
+
- A Contentrain project already set up via the [Contentrain Web App](https://app.contentrain.io)
|
|
18
|
+
- The project's GitHub repository cloned locally
|
|
19
|
+
- Node.js >= 18
|
|
20
|
+
|
|
21
|
+
> **Important:** Contentrain projects must be initialized through the Web App first. The Web App creates the repository structure, the `contentrain` branch, models, and environment configuration. This MCP server operates on an existing Contentrain project — it does not replace the initial setup.
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### 1. Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# From npm (when published)
|
|
29
|
+
npm install -g @contentrain/mcp
|
|
30
|
+
|
|
31
|
+
# Or from source
|
|
32
|
+
git clone https://github.com/Contentrain/contentrain-mcp.git
|
|
33
|
+
cd contentrain-mcp
|
|
34
|
+
pnpm install && pnpm build
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. Configure your MCP client
|
|
38
|
+
|
|
39
|
+
Add to your MCP client configuration:
|
|
40
|
+
|
|
41
|
+
**Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"contentrain": {
|
|
47
|
+
"command": "contentrain-mcp",
|
|
48
|
+
"env": {
|
|
49
|
+
"CONTENTRAIN_REPO_PATH": "/path/to/your/contentrain-project",
|
|
50
|
+
"CONTENTRAIN_BRANCH": "contentrain"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Cursor** (`.cursor/mcp.json` in your project root):
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"mcpServers": {
|
|
62
|
+
"contentrain": {
|
|
63
|
+
"command": "contentrain-mcp",
|
|
64
|
+
"env": {
|
|
65
|
+
"CONTENTRAIN_REPO_PATH": "/path/to/your/contentrain-project",
|
|
66
|
+
"CONTENTRAIN_BRANCH": "contentrain"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Claude Code** (`.mcp.json` in your project root):
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"mcpServers": {
|
|
78
|
+
"contentrain": {
|
|
79
|
+
"command": "contentrain-mcp",
|
|
80
|
+
"env": {
|
|
81
|
+
"CONTENTRAIN_REPO_PATH": "/path/to/your/contentrain-project",
|
|
82
|
+
"CONTENTRAIN_BRANCH": "contentrain"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
> If installed from source instead of globally, replace `"command": "contentrain-mcp"` with `"command": "node"` and add `"args": ["/absolute/path/to/contentrain-mcp/dist/index.mjs"]`.
|
|
90
|
+
|
|
91
|
+
### 3. Start using
|
|
92
|
+
|
|
93
|
+
Once configured, your AI assistant has access to 14 tools. Here's an example conversation:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
You: List all my models
|
|
97
|
+
Agent: → calls contentrain_list_models
|
|
98
|
+
You have 3 models: blog (MD, localized), faq (JSON), authors (JSON)
|
|
99
|
+
|
|
100
|
+
You: Show me the blog model schema
|
|
101
|
+
Agent: → calls contentrain_describe_model { modelId: "blog" }
|
|
102
|
+
Blog model has fields: title (string, required), description (string),
|
|
103
|
+
category (one-to-one → blogcategories), imagesrc (media), author (one-to-one → authors)
|
|
104
|
+
|
|
105
|
+
You: Create a new blog post titled "Getting Started with Contentrain"
|
|
106
|
+
Agent: → calls contentrain_create_content {
|
|
107
|
+
modelId: "blog",
|
|
108
|
+
data: { title: "Getting Started with Contentrain", slug: "getting-started-with-contentrain", description: "..." },
|
|
109
|
+
locale: "en",
|
|
110
|
+
status: "draft",
|
|
111
|
+
content: "# Getting Started\n\nWelcome to Contentrain..."
|
|
112
|
+
}
|
|
113
|
+
Created entry e7f3a1b9c0d2 in blog model (draft).
|
|
114
|
+
Committed and pushed to contentrain branch.
|
|
115
|
+
|
|
116
|
+
You: Now publish it
|
|
117
|
+
Agent: → calls contentrain_update_content {
|
|
118
|
+
modelId: "blog",
|
|
119
|
+
entryId: "e7f3a1b9c0d2",
|
|
120
|
+
data: { status: "publish" },
|
|
121
|
+
locale: "en"
|
|
122
|
+
}
|
|
123
|
+
Updated and published.
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Environments
|
|
127
|
+
|
|
128
|
+
Contentrain uses **git branches** to manage environments. Each environment maps to a branch:
|
|
129
|
+
|
|
130
|
+
| Environment | Branch | Description |
|
|
131
|
+
|-------------|--------|-------------|
|
|
132
|
+
| Default | `contentrain` | Main content branch (created automatically) |
|
|
133
|
+
| Staging | `contentrain-staging` | Preview/staging environment |
|
|
134
|
+
| Production | `contentrain-production` | Production environment |
|
|
135
|
+
|
|
136
|
+
Environments are created via the Contentrain Web App. The branch naming follows the pattern `contentrain-{environment-name}`.
|
|
137
|
+
|
|
138
|
+
### Targeting a specific environment
|
|
139
|
+
|
|
140
|
+
Set the `CONTENTRAIN_BRANCH` environment variable to the target environment's branch:
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"mcpServers": {
|
|
145
|
+
"contentrain": {
|
|
146
|
+
"command": "contentrain-mcp",
|
|
147
|
+
"env": {
|
|
148
|
+
"CONTENTRAIN_REPO_PATH": "/path/to/project",
|
|
149
|
+
"CONTENTRAIN_BRANCH": "contentrain-staging"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Multiple environments simultaneously
|
|
157
|
+
|
|
158
|
+
You can register multiple MCP server instances — one per environment:
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"mcpServers": {
|
|
163
|
+
"contentrain-default": {
|
|
164
|
+
"command": "contentrain-mcp",
|
|
165
|
+
"env": {
|
|
166
|
+
"CONTENTRAIN_REPO_PATH": "/path/to/project",
|
|
167
|
+
"CONTENTRAIN_BRANCH": "contentrain"
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
"contentrain-staging": {
|
|
171
|
+
"command": "contentrain-mcp",
|
|
172
|
+
"env": {
|
|
173
|
+
"CONTENTRAIN_REPO_PATH": "/path/to/project",
|
|
174
|
+
"CONTENTRAIN_BRANCH": "contentrain-staging"
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Available Tools
|
|
182
|
+
|
|
183
|
+
### Model Management
|
|
184
|
+
|
|
185
|
+
| Tool | Description |
|
|
186
|
+
|------|-------------|
|
|
187
|
+
| `contentrain_list_models` | List all models with metadata |
|
|
188
|
+
| `contentrain_describe_model` | Get full schema with field definitions |
|
|
189
|
+
| `contentrain_create_model` | Create a new model (JSON, MD, or MDX) |
|
|
190
|
+
| `contentrain_add_field` | Add a field to an existing model |
|
|
191
|
+
| `contentrain_delete_model` | Delete a model and all its content |
|
|
192
|
+
|
|
193
|
+
### Content Management
|
|
194
|
+
|
|
195
|
+
| Tool | Description |
|
|
196
|
+
|------|-------------|
|
|
197
|
+
| `contentrain_list_content` | List all entries for a model |
|
|
198
|
+
| `contentrain_get_content` | Get a single entry by ID |
|
|
199
|
+
| `contentrain_create_content` | Create a new entry with validation |
|
|
200
|
+
| `contentrain_update_content` | Update specific fields of an entry |
|
|
201
|
+
| `contentrain_delete_content` | Delete an entry (all locales + markdown) |
|
|
202
|
+
| `contentrain_validate` | Dry-run validation against model schema |
|
|
203
|
+
|
|
204
|
+
### Asset Management
|
|
205
|
+
|
|
206
|
+
| Tool | Description |
|
|
207
|
+
|------|-------------|
|
|
208
|
+
| `contentrain_list_assets` | List all registered assets |
|
|
209
|
+
| `contentrain_register_asset` | Register an existing file as an asset |
|
|
210
|
+
| `contentrain_deregister_asset` | Remove an asset from the registry |
|
|
211
|
+
|
|
212
|
+
## Environment Variables
|
|
213
|
+
|
|
214
|
+
| Variable | Default | Description |
|
|
215
|
+
|----------|---------|-------------|
|
|
216
|
+
| `CONTENTRAIN_REPO_PATH` | `process.cwd()` | Path to the git repository |
|
|
217
|
+
| `CONTENTRAIN_BRANCH` | `contentrain` | Target environment branch |
|
|
218
|
+
| `CONTENTRAIN_REMOTE` | `origin` | Git remote name |
|
|
219
|
+
| `CONTENTRAIN_DIR` | `contentrain` | Contentrain directory name |
|
|
220
|
+
| `CONTENTRAIN_DRY_RUN` | `false` | Skip git operations (local changes only) |
|
|
221
|
+
| `CONTENTRAIN_AUTHOR_NAME` | — | Git commit author name |
|
|
222
|
+
| `CONTENTRAIN_AUTHOR_EMAIL` | — | Git commit author email |
|
|
223
|
+
|
|
224
|
+
## Programmatic Usage
|
|
225
|
+
|
|
226
|
+
You can also use the writer directly in your own code:
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
import { ContentrainWriter } from '@contentrain/mcp'
|
|
230
|
+
|
|
231
|
+
const writer = new ContentrainWriter({
|
|
232
|
+
repoPath: '/path/to/your/project',
|
|
233
|
+
branch: 'contentrain',
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
// Read operations (no git transaction needed)
|
|
237
|
+
const models = await writer.model.list()
|
|
238
|
+
const entries = await writer.content.list('blog-posts')
|
|
239
|
+
|
|
240
|
+
// Write operations (uses git worktree + commit + push)
|
|
241
|
+
await writer.transaction(async (tx) => {
|
|
242
|
+
await tx.content.create('blog-posts', {
|
|
243
|
+
title: 'Hello World',
|
|
244
|
+
excerpt: 'My first post',
|
|
245
|
+
}, { status: 'draft' })
|
|
246
|
+
}, { message: 'add new blog post' })
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## How Git Sync Works
|
|
250
|
+
|
|
251
|
+
Every write operation runs inside a **transaction**:
|
|
252
|
+
|
|
253
|
+
1. Creates an isolated git worktree from the target branch
|
|
254
|
+
2. Applies all changes inside the worktree
|
|
255
|
+
3. Commits and pushes to the target branch
|
|
256
|
+
4. If the push is rejected (remote advanced), performs a **structural 3-way merge** — diffing JSON entries by their `ID` field instead of text lines — then retries (up to 3 attempts)
|
|
257
|
+
5. Cleans up the worktree
|
|
258
|
+
|
|
259
|
+
This means concurrent writes from multiple agents or the Contentrain web app won't conflict.
|
|
260
|
+
|
|
261
|
+
## Development
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
pnpm install # Install dependencies
|
|
265
|
+
pnpm dev # Watch mode build
|
|
266
|
+
pnpm test # Run tests (watch mode)
|
|
267
|
+
pnpm test:run # Run tests once
|
|
268
|
+
pnpm lint # Lint with oxlint
|
|
269
|
+
pnpm build # Production build
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## License
|
|
273
|
+
|
|
274
|
+
MIT
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { access, mkdir, readdir, rm, unlink } from "node:fs/promises";
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __exportAll = (all, no_symbols) => {
|
|
5
|
+
let target = {};
|
|
6
|
+
for (var name in all) __defProp(target, name, {
|
|
7
|
+
get: all[name],
|
|
8
|
+
enumerable: true
|
|
9
|
+
});
|
|
10
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
11
|
+
return target;
|
|
12
|
+
};
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/util/fs.ts
|
|
15
|
+
var fs_exports = /* @__PURE__ */ __exportAll({
|
|
16
|
+
ensureDir: () => ensureDir,
|
|
17
|
+
fileExists: () => fileExists,
|
|
18
|
+
listJsonFiles: () => listJsonFiles,
|
|
19
|
+
removeDir: () => removeDir,
|
|
20
|
+
removeFile: () => removeFile
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* Ensure a directory exists, creating it recursively if needed.
|
|
24
|
+
*/
|
|
25
|
+
async function ensureDir(dirPath) {
|
|
26
|
+
await mkdir(dirPath, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Check if a file exists.
|
|
30
|
+
*/
|
|
31
|
+
async function fileExists(filePath) {
|
|
32
|
+
try {
|
|
33
|
+
await access(filePath);
|
|
34
|
+
return true;
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Remove a file if it exists.
|
|
41
|
+
*/
|
|
42
|
+
async function removeFile(filePath) {
|
|
43
|
+
try {
|
|
44
|
+
await unlink(filePath);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
if (err instanceof Error && "code" in err && err.code !== "ENOENT") throw err;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Remove a directory recursively.
|
|
51
|
+
*/
|
|
52
|
+
async function removeDir(dirPath) {
|
|
53
|
+
try {
|
|
54
|
+
await rm(dirPath, {
|
|
55
|
+
recursive: true,
|
|
56
|
+
force: true
|
|
57
|
+
});
|
|
58
|
+
} catch (err) {
|
|
59
|
+
if (err instanceof Error && "code" in err && err.code !== "ENOENT") throw err;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* List JSON files in a directory (returns filenames without extension).
|
|
64
|
+
*/
|
|
65
|
+
async function listJsonFiles(dirPath) {
|
|
66
|
+
try {
|
|
67
|
+
return (await readdir(dirPath)).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
|
|
68
|
+
} catch {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
export { removeDir as a, listJsonFiles as i, fileExists as n, removeFile as o, fs_exports as r, ensureDir as t };
|
|
74
|
+
|
|
75
|
+
//# sourceMappingURL=fs-D83-Ii_y.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-D83-Ii_y.mjs","names":[],"sources":["../src/util/fs.ts"],"sourcesContent":["import { mkdir, access, unlink, readdir, rm } from 'node:fs/promises'\n\n/**\n * Ensure a directory exists, creating it recursively if needed.\n */\nexport async function ensureDir(dirPath: string): Promise<void> {\n await mkdir(dirPath, { recursive: true })\n}\n\n/**\n * Check if a file exists.\n */\nexport async function fileExists(filePath: string): Promise<boolean> {\n try {\n await access(filePath)\n return true\n }\n catch {\n return false\n }\n}\n\n/**\n * Remove a file if it exists.\n */\nexport async function removeFile(filePath: string): Promise<void> {\n try {\n await unlink(filePath)\n }\n catch (err: unknown) {\n if (err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw err\n }\n }\n}\n\n/**\n * Remove a directory recursively.\n */\nexport async function removeDir(dirPath: string): Promise<void> {\n try {\n await rm(dirPath, { recursive: true, force: true })\n }\n catch (err: unknown) {\n if (err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw err\n }\n }\n}\n\n/**\n * List JSON files in a directory (returns filenames without extension).\n */\nexport async function listJsonFiles(dirPath: string): Promise<string[]> {\n try {\n const files = await readdir(dirPath)\n return files\n .filter(f => f.endsWith('.json'))\n .map(f => f.replace('.json', ''))\n }\n catch {\n return []\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAKA,eAAsB,UAAU,SAAgC;AAC9D,OAAM,MAAM,SAAS,EAAE,WAAW,MAAM,CAAC;;;;;AAM3C,eAAsB,WAAW,UAAoC;AACnE,KAAI;AACF,QAAM,OAAO,SAAS;AACtB,SAAO;SAEH;AACJ,SAAO;;;;;;AAOX,eAAsB,WAAW,UAAiC;AAChE,KAAI;AACF,QAAM,OAAO,SAAS;UAEjB,KAAc;AACnB,MAAI,eAAe,SAAS,UAAU,OAAQ,IAA8B,SAAS,SACnF,OAAM;;;;;;AAQZ,eAAsB,UAAU,SAAgC;AAC9D,KAAI;AACF,QAAM,GAAG,SAAS;GAAE,WAAW;GAAM,OAAO;GAAM,CAAC;UAE9C,KAAc;AACnB,MAAI,eAAe,SAAS,UAAU,OAAQ,IAA8B,SAAS,SACnF,OAAM;;;;;;AAQZ,eAAsB,cAAc,SAAoC;AACtE,KAAI;AAEF,UADc,MAAM,QAAQ,QAAQ,EAEjC,QAAO,MAAK,EAAE,SAAS,QAAQ,CAAC,CAChC,KAAI,MAAK,EAAE,QAAQ,SAAS,GAAG,CAAC;SAE/B;AACJ,SAAO,EAAE"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
|
|
3
|
+
//#region src/types/index.d.ts
|
|
4
|
+
interface ModelMetadata {
|
|
5
|
+
name: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
modelId: string;
|
|
8
|
+
path?: string;
|
|
9
|
+
localization: boolean;
|
|
10
|
+
type: 'JSON' | 'MD' | 'MDX';
|
|
11
|
+
createdBy?: string;
|
|
12
|
+
isServerless: boolean;
|
|
13
|
+
}
|
|
14
|
+
interface FieldDefinition {
|
|
15
|
+
name: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
fieldId: string;
|
|
18
|
+
modelId: string;
|
|
19
|
+
componentId: ComponentId;
|
|
20
|
+
fieldType: FieldType;
|
|
21
|
+
options: FieldOptions;
|
|
22
|
+
validations: FieldValidations;
|
|
23
|
+
system: boolean;
|
|
24
|
+
defaultField: boolean;
|
|
25
|
+
}
|
|
26
|
+
type FieldType = 'string' | 'number' | 'boolean' | 'array' | 'date' | 'media' | 'relation';
|
|
27
|
+
type ComponentId = 'single-line-text' | 'multi-line-text' | 'email' | 'url' | 'slug' | 'color' | 'json' | 'md-editor' | 'rich-text-editor' | 'integer' | 'decimal' | 'rating' | 'percent' | 'phone-number' | 'checkbox' | 'switch' | 'date' | 'date-time' | 'media' | 'one-to-one' | 'one-to-many';
|
|
28
|
+
interface FieldOptions {
|
|
29
|
+
'title-field'?: {
|
|
30
|
+
value: boolean;
|
|
31
|
+
};
|
|
32
|
+
'default-value'?: {
|
|
33
|
+
value: boolean;
|
|
34
|
+
form?: Record<string, {
|
|
35
|
+
value: unknown;
|
|
36
|
+
}>;
|
|
37
|
+
};
|
|
38
|
+
reference?: {
|
|
39
|
+
value: boolean;
|
|
40
|
+
form?: {
|
|
41
|
+
reference: {
|
|
42
|
+
value: string;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
[key: string]: unknown;
|
|
47
|
+
}
|
|
48
|
+
interface FieldValidations {
|
|
49
|
+
'required-field'?: {
|
|
50
|
+
value: boolean;
|
|
51
|
+
};
|
|
52
|
+
'unique-field'?: {
|
|
53
|
+
value: boolean;
|
|
54
|
+
};
|
|
55
|
+
[key: string]: unknown;
|
|
56
|
+
}
|
|
57
|
+
interface ContentEntry {
|
|
58
|
+
ID: string;
|
|
59
|
+
createdAt: string;
|
|
60
|
+
updatedAt: string;
|
|
61
|
+
status: 'draft' | 'publish' | 'changed';
|
|
62
|
+
scheduled?: boolean;
|
|
63
|
+
[key: string]: unknown;
|
|
64
|
+
}
|
|
65
|
+
interface MDContentEntry extends ContentEntry {
|
|
66
|
+
slug: string;
|
|
67
|
+
path?: string;
|
|
68
|
+
}
|
|
69
|
+
interface AssetEntry {
|
|
70
|
+
path: string;
|
|
71
|
+
mimetype: string;
|
|
72
|
+
size: number;
|
|
73
|
+
alt?: string;
|
|
74
|
+
meta?: {
|
|
75
|
+
user?: {
|
|
76
|
+
name: string;
|
|
77
|
+
email: string;
|
|
78
|
+
avatar?: string;
|
|
79
|
+
};
|
|
80
|
+
createdAt?: string;
|
|
81
|
+
updatedAt?: string;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
interface ContentrainConfig {
|
|
85
|
+
assetsPath: string;
|
|
86
|
+
}
|
|
87
|
+
interface WriterConfig {
|
|
88
|
+
repoPath: string;
|
|
89
|
+
branch: string;
|
|
90
|
+
contentrainDir?: string;
|
|
91
|
+
remote?: string;
|
|
92
|
+
author?: {
|
|
93
|
+
name: string;
|
|
94
|
+
email: string;
|
|
95
|
+
};
|
|
96
|
+
commitPrefix?: string;
|
|
97
|
+
retryAttempts?: number;
|
|
98
|
+
dryRun?: boolean;
|
|
99
|
+
}
|
|
100
|
+
interface WorktreeContext {
|
|
101
|
+
path: string;
|
|
102
|
+
branch: string;
|
|
103
|
+
id: string;
|
|
104
|
+
}
|
|
105
|
+
interface TransactionOptions {
|
|
106
|
+
message?: string;
|
|
107
|
+
}
|
|
108
|
+
interface ValidationResult {
|
|
109
|
+
valid: boolean;
|
|
110
|
+
errors: ValidationFieldError[];
|
|
111
|
+
}
|
|
112
|
+
interface ValidationFieldError {
|
|
113
|
+
fieldId: string;
|
|
114
|
+
message: string;
|
|
115
|
+
rule: string;
|
|
116
|
+
}
|
|
117
|
+
interface ToolResult {
|
|
118
|
+
success: boolean;
|
|
119
|
+
data?: unknown;
|
|
120
|
+
error?: string;
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/server.d.ts
|
|
124
|
+
declare function createServer(config: WriterConfig): McpServer;
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/core/model/manager.d.ts
|
|
127
|
+
declare class ModelManager {
|
|
128
|
+
private basePath;
|
|
129
|
+
constructor(basePath: string);
|
|
130
|
+
private get modelsDir();
|
|
131
|
+
private get metadataPath();
|
|
132
|
+
list(): Promise<ModelMetadata[]>;
|
|
133
|
+
get(modelId: string): Promise<ModelMetadata>;
|
|
134
|
+
getFields(modelId: string): Promise<FieldDefinition[]>;
|
|
135
|
+
describe(modelId: string): Promise<{
|
|
136
|
+
model: ModelMetadata;
|
|
137
|
+
fields: FieldDefinition[];
|
|
138
|
+
customFields: FieldDefinition[];
|
|
139
|
+
}>;
|
|
140
|
+
create(input: {
|
|
141
|
+
name: string;
|
|
142
|
+
description?: string;
|
|
143
|
+
modelId: string;
|
|
144
|
+
localization: boolean;
|
|
145
|
+
type: 'JSON' | 'MD' | 'MDX';
|
|
146
|
+
path?: string;
|
|
147
|
+
fields?: Omit<FieldDefinition, 'modelId' | 'system' | 'defaultField'>[];
|
|
148
|
+
}): Promise<ModelMetadata>;
|
|
149
|
+
addField(modelId: string, field: Omit<FieldDefinition, 'modelId' | 'system' | 'defaultField'>): Promise<FieldDefinition>;
|
|
150
|
+
removeField(modelId: string, fieldId: string): Promise<void>;
|
|
151
|
+
delete(modelId: string): Promise<void>;
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/core/content/manager.d.ts
|
|
155
|
+
declare class ContentManager {
|
|
156
|
+
private basePath;
|
|
157
|
+
private repoPath;
|
|
158
|
+
private modelManager;
|
|
159
|
+
private markdownSync;
|
|
160
|
+
constructor(basePath: string, repoPath: string);
|
|
161
|
+
private contentDir;
|
|
162
|
+
private contentFilePath;
|
|
163
|
+
list(modelId: string, locale?: string): Promise<ContentEntry[]>;
|
|
164
|
+
get(modelId: string, entryId: string, locale?: string): Promise<ContentEntry>;
|
|
165
|
+
getLocales(modelId: string): Promise<string[]>;
|
|
166
|
+
create(modelId: string, data: Record<string, unknown>, options?: {
|
|
167
|
+
locale?: string;
|
|
168
|
+
id?: string;
|
|
169
|
+
status?: 'draft' | 'publish';
|
|
170
|
+
content?: string;
|
|
171
|
+
}): Promise<string>;
|
|
172
|
+
update(modelId: string, entryId: string, data: Record<string, unknown>, options?: {
|
|
173
|
+
locale?: string;
|
|
174
|
+
content?: string;
|
|
175
|
+
}): Promise<ContentEntry>;
|
|
176
|
+
delete(modelId: string, entryId: string): Promise<void>;
|
|
177
|
+
private resolveContentPath;
|
|
178
|
+
private createLocalized;
|
|
179
|
+
private appendEntry;
|
|
180
|
+
private deleteFromFile;
|
|
181
|
+
}
|
|
182
|
+
//#endregion
|
|
183
|
+
//#region src/core/asset/manager.d.ts
|
|
184
|
+
declare class AssetManager {
|
|
185
|
+
private basePath;
|
|
186
|
+
private repoPath;
|
|
187
|
+
private config;
|
|
188
|
+
private assetsPath;
|
|
189
|
+
constructor(basePath: string, repoPath: string, config: WriterConfig);
|
|
190
|
+
list(): Promise<AssetEntry[]>;
|
|
191
|
+
get(assetPath: string): Promise<AssetEntry | undefined>;
|
|
192
|
+
register(input: {
|
|
193
|
+
path: string;
|
|
194
|
+
mimetype: string;
|
|
195
|
+
alt?: string;
|
|
196
|
+
}): Promise<AssetEntry>;
|
|
197
|
+
deregister(assetPath: string): Promise<void>;
|
|
198
|
+
update(assetPath: string, updates: {
|
|
199
|
+
alt?: string;
|
|
200
|
+
mimetype?: string;
|
|
201
|
+
}): Promise<AssetEntry>;
|
|
202
|
+
}
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region src/core/validation/validator.d.ts
|
|
205
|
+
declare class Validator {
|
|
206
|
+
private basePath;
|
|
207
|
+
private modelManager;
|
|
208
|
+
constructor(basePath: string);
|
|
209
|
+
/**
|
|
210
|
+
* Validate content data against model field definitions.
|
|
211
|
+
*/
|
|
212
|
+
validate(modelId: string, data: Record<string, unknown>, options?: {
|
|
213
|
+
isUpdate?: boolean;
|
|
214
|
+
}): Promise<ValidationResult>;
|
|
215
|
+
/**
|
|
216
|
+
* Validate ID format.
|
|
217
|
+
*/
|
|
218
|
+
validateId(id: string): ValidationResult;
|
|
219
|
+
/**
|
|
220
|
+
* Validate status value.
|
|
221
|
+
*/
|
|
222
|
+
validateStatus(status: string): ValidationResult;
|
|
223
|
+
private checkRelationIntegrity;
|
|
224
|
+
private checkAssetExists;
|
|
225
|
+
private checkUniqueness;
|
|
226
|
+
}
|
|
227
|
+
//#endregion
|
|
228
|
+
//#region src/core/writer.d.ts
|
|
229
|
+
/**
|
|
230
|
+
* Main orchestrator — coordinates all managers and git sync.
|
|
231
|
+
*
|
|
232
|
+
* Three modes:
|
|
233
|
+
* 1. Read mode: reads from repoPath directly (no git sync)
|
|
234
|
+
* 2. Transaction mode: creates a worktree, applies changes, commits + pushes
|
|
235
|
+
* 3. Dry-run mode: copies contentrain dir to temp, applies changes there, then discards
|
|
236
|
+
*/
|
|
237
|
+
declare class ContentrainWriter {
|
|
238
|
+
private config;
|
|
239
|
+
private contentrainDir;
|
|
240
|
+
readonly model: ModelManager;
|
|
241
|
+
readonly content: ContentManager;
|
|
242
|
+
readonly asset: AssetManager;
|
|
243
|
+
readonly validator: Validator;
|
|
244
|
+
private gitSync;
|
|
245
|
+
constructor(config: WriterConfig);
|
|
246
|
+
/**
|
|
247
|
+
* Execute operations in a git-synced transaction.
|
|
248
|
+
* Creates worktree → runs operations → validates → commits → pushes.
|
|
249
|
+
* In dry-run mode, copies to a temp directory and discards after execution.
|
|
250
|
+
*/
|
|
251
|
+
transaction<T>(fn: (managers: TransactionManagers) => Promise<T>, options?: {
|
|
252
|
+
message?: string;
|
|
253
|
+
}): Promise<T>;
|
|
254
|
+
/**
|
|
255
|
+
* Dry-run: copy contentrain dir to temp, run operations there, then discard.
|
|
256
|
+
* The original repository is never modified.
|
|
257
|
+
*/
|
|
258
|
+
private dryRunTransaction;
|
|
259
|
+
/**
|
|
260
|
+
* Create managers for a specific repo path (used in transactions).
|
|
261
|
+
*/
|
|
262
|
+
private createManagers;
|
|
263
|
+
}
|
|
264
|
+
interface TransactionManagers {
|
|
265
|
+
model: ModelManager;
|
|
266
|
+
content: ContentManager;
|
|
267
|
+
asset: AssetManager;
|
|
268
|
+
validator: Validator;
|
|
269
|
+
}
|
|
270
|
+
//#endregion
|
|
271
|
+
export { AssetEntry, ComponentId, ContentEntry, ContentrainConfig, ContentrainWriter, FieldDefinition, FieldOptions, FieldType, FieldValidations, MDContentEntry, ModelMetadata, ToolResult, TransactionOptions, ValidationFieldError, ValidationResult, WorktreeContext, WriterConfig, createServer };
|
|
272
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/index.ts","../src/server.ts","../src/core/model/manager.ts","../src/core/content/manager.ts","../src/core/asset/manager.ts","../src/core/validation/validator.ts","../src/core/writer.ts"],"mappings":"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"}
|