@content-island/mcp 0.1.13 → 0.2.1
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 +25 -6
- package/dist/index.js +470 -62
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -12,6 +12,25 @@ A Model Context Protocol (MCP) server that enables seamless integration between
|
|
|
12
12
|
- **Type-safe integration**. Generate TypeScript types from your Content Island content models.
|
|
13
13
|
- **Zero-config setup**. Streamlined project initialization with pre-configured build tools and settings.
|
|
14
14
|
- **LLM-optimized workflow**. Purpose-built for AI-assisted development with clear, structured commands.
|
|
15
|
+
- **Content authoring**. Create content entries and upload media files directly from your LLM.
|
|
16
|
+
|
|
17
|
+
### Available Tools
|
|
18
|
+
|
|
19
|
+
| Tool | Description | Token required |
|
|
20
|
+
| ----------------------------------- | ------------------------------------------------------------------------ | ----------------- |
|
|
21
|
+
| `get-content-island-project` | Get project details (languages, content types, fields) | Read **or** Write |
|
|
22
|
+
| `list-content-island-contents` | List content entries with filters, sort and pagination (returns hasMore) | Read **or** Write |
|
|
23
|
+
| `create-content-island-content` | Create a new content entry with fields per language | **Write** |
|
|
24
|
+
| `update-content-island-field-value` | Update or insert a field value identified by `(fieldName, language)` | **Write** |
|
|
25
|
+
| `publish-content-island-content` | Promote a content's current draft to the live state (preflight-gated) | **Write** |
|
|
26
|
+
| `upload-content-island-media` | Upload a media file from a local path or URL | **Write** |
|
|
27
|
+
|
|
28
|
+
> **Note**: A **read token** is sufficient for read-only tools. Tools that create or upload data require a **write token**. A write token also works for read-only tools.
|
|
29
|
+
|
|
30
|
+
### Behavior notes
|
|
31
|
+
|
|
32
|
+
- The MCP server always reads in **preview mode**, so drafts and unpublished field values are visible. The configured access token is automatically prefixed with `PREVIEW_` before it reaches the API. This keeps reads and writes consistent: the tools `create-content-island-content` and `update-content-island-field-value` write to the working state, and `list-content-island-contents` / `get-content-island-project` read from that same working state.
|
|
33
|
+
- `publish-content-island-content` runs a **server-side preflight** before publishing and refuses the call when the content is not valid: every `isRequired` field must have a non-empty value in every project language, and populated values must satisfy their `min-length`, `max-length` and `media-type` validations. `unique` is enforced by the backend. If the preflight fails, the response lists every problem in one pass so the caller can fix them via `update-content-island-field-value` and retry.
|
|
15
34
|
|
|
16
35
|
### Requirements
|
|
17
36
|
|
|
@@ -136,9 +155,9 @@ Follow the MCP install [guide](https://modelcontextprotocol.io/quickstart/user),
|
|
|
136
155
|
|
|
137
156
|
You can configure the MCP server using environment variables. The following variables are available:
|
|
138
157
|
|
|
139
|
-
| name | required | description
|
|
140
|
-
| -------------------------------- | -------- |
|
|
141
|
-
| `CONTENT_ISLAND_ACCESS_TOKEN` | `true` | Your project’s
|
|
142
|
-
| `CONTENT_ISLAND_DOMAIN` | `false` | Domain used for requests. Useful if you're using Content Island `On Premise`.<br/>Default: `api.contentisland.net`
|
|
143
|
-
| `CONTENT_ISLAND_SECURE_PROTOCOL` | `false` | Whether to use HTTPS for requests.<br/>Default: `true` (use HTTPS)
|
|
144
|
-
| `CONTENT_ISLAND_API_VERSION` | `false` | Version of the [REST API](https://docs.contentisland.net/rest-api/v1/overview/).<br/>Default: `1.0`
|
|
158
|
+
| name | required | description |
|
|
159
|
+
| -------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
160
|
+
| `CONTENT_ISLAND_ACCESS_TOKEN` | `true` | Your project’s access token. A **read token** is sufficient for `get-content-island-project`. A **write token** is required for `create-content-island-content` and `upload-content-island-media`.<br/>You can find this token in the [General tab](https://docs.contentisland.net/ui/project/general/). |
|
|
161
|
+
| `CONTENT_ISLAND_DOMAIN` | `false` | Domain used for requests. Useful if you're using Content Island `On Premise`.<br/>Default: `api.contentisland.net` |
|
|
162
|
+
| `CONTENT_ISLAND_SECURE_PROTOCOL` | `false` | Whether to use HTTPS for requests.<br/>Default: `true` (use HTTPS) |
|
|
163
|
+
| `CONTENT_ISLAND_API_VERSION` | `false` | Version of the [REST API](https://docs.contentisland.net/rest-api/v1/overview/).<br/>Default: `1.0` |
|
package/dist/index.js
CHANGED
|
@@ -1,31 +1,38 @@
|
|
|
1
|
-
import { z as
|
|
2
|
-
import { McpServer as
|
|
3
|
-
import { createClient as
|
|
4
|
-
import {
|
|
5
|
-
|
|
1
|
+
import { z as i } from "zod";
|
|
2
|
+
import { McpServer as w } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { createClient as v } from "@content-island/api-client";
|
|
4
|
+
import { readFile as S } from "node:fs/promises";
|
|
5
|
+
import { basename as A, extname as R } from "node:path";
|
|
6
|
+
import { StdioServerTransport as O } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
+
const f = {
|
|
6
8
|
CONTENT_ISLAND_ACCESS_TOKEN: process.env.CONTENT_ISLAND_ACCESS_TOKEN,
|
|
7
9
|
CONTENT_ISLAND_DOMAIN: process.env.CONTENT_ISLAND_DOMAIN,
|
|
8
10
|
CONTENT_ISLAND_SECURE_PROTOCOL: process.env.CONTENT_ISLAND_SECURE_PROTOCOL !== "false",
|
|
9
11
|
CONTENT_ISLAND_API_VERSION: process.env.CONTENT_ISLAND_API_VERSION
|
|
10
|
-
},
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
}, I = "PREVIEW_", x = (e) => e.startsWith(I) ? e : `${I}${e}`, g = () => v({
|
|
13
|
+
accessToken: x(f.CONTENT_ISLAND_ACCESS_TOKEN),
|
|
14
|
+
domain: f.CONTENT_ISLAND_DOMAIN,
|
|
15
|
+
secureProtocol: f.CONTENT_ISLAND_SECURE_PROTOCOL,
|
|
16
|
+
apiVersion: f.CONTENT_ISLAND_API_VERSION
|
|
17
|
+
}), L = "0.2.1", k = {
|
|
18
|
+
version: L
|
|
19
|
+
}, u = new w({
|
|
13
20
|
name: "Content Island",
|
|
14
|
-
version:
|
|
15
|
-
}),
|
|
16
|
-
|
|
21
|
+
version: k.version
|
|
22
|
+
}), j = () => {
|
|
23
|
+
u.prompt(
|
|
17
24
|
"create-content-island-project",
|
|
18
25
|
"Professional MCP Server prompt for creating modern frontend projects integrated with Content Island CMS",
|
|
19
26
|
{
|
|
20
|
-
framework:
|
|
21
|
-
pages:
|
|
22
|
-
location:
|
|
23
|
-
styling:
|
|
24
|
-
design:
|
|
27
|
+
framework: i.string().describe("Framework choice (Next.js, Astro, Nuxt, etc.)"),
|
|
28
|
+
pages: i.string().describe("Pages needed (Homepage, Blog, Contact, etc.)"),
|
|
29
|
+
location: i.string().describe("Project location (root directory or subfolder name)"),
|
|
30
|
+
styling: i.string().describe("Styling preference (Tailwind CSS or custom)"),
|
|
31
|
+
design: i.string().describe("Design assets (mockups, wireframes, or none)")
|
|
25
32
|
},
|
|
26
|
-
async (
|
|
27
|
-
const { framework:
|
|
28
|
-
return
|
|
33
|
+
async (e) => {
|
|
34
|
+
const { framework: t, pages: a, location: n, styling: o, design: s } = e;
|
|
35
|
+
return t && a && n && o && s ? {
|
|
29
36
|
description: "Content Island project generator",
|
|
30
37
|
messages: [
|
|
31
38
|
{
|
|
@@ -34,24 +41,24 @@ const r = {
|
|
|
34
41
|
type: "text",
|
|
35
42
|
text: `# Content Island Website Generator
|
|
36
43
|
|
|
37
|
-
I'll create a professional ${
|
|
44
|
+
I'll create a professional ${t} application integrated with your Content Island project.
|
|
38
45
|
|
|
39
46
|
**Configuration:**
|
|
40
|
-
- Framework: **${
|
|
41
|
-
- Pages: ${
|
|
42
|
-
- Location: ${
|
|
43
|
-
- Styling: ${
|
|
44
|
-
- Design: ${
|
|
47
|
+
- Framework: **${t}**
|
|
48
|
+
- Pages: ${a}
|
|
49
|
+
- Location: ${n}
|
|
50
|
+
- Styling: ${o}
|
|
51
|
+
- Design: ${s}
|
|
45
52
|
|
|
46
53
|
# CRITICAL IMPLEMENTATION INSTRUCTIONS - FOLLOW EXACTLY
|
|
47
54
|
|
|
48
|
-
You are a specialized web development assistant creating a ${
|
|
55
|
+
You are a specialized web development assistant creating a ${t} application integrated with Content Island CMS. You MUST follow these instructions PRECISELY without any deviation or interpretation.
|
|
49
56
|
|
|
50
57
|
## MANDATORY EXECUTION SEQUENCE - NO EXCEPTIONS
|
|
51
58
|
|
|
52
59
|
### STEP 1: FRAMEWORK PROJECT INITIALIZATION (REQUIRED FIRST ACTION)
|
|
53
60
|
- FIRST execute the appropriate framework CLI command to create the base project:
|
|
54
|
-
${
|
|
61
|
+
${t === "Next.js" ? ` * Run: npx create-next-app@latest ${n !== "root directory" ? n : "."} --typescript --tailwind --eslint --app --src-dir --import-alias "@/*"` : t === "Nuxt" ? ` * Run: npx nuxi@latest init ${n !== "root directory" ? n : "."}` : t === "Astro" ? ` * Run: npm create astro@latest ${n !== "root directory" ? n : "."}` : t === "SvelteKit" ? ` * Run: npm create svelte@latest ${n !== "root directory" ? n : "."}` : t === "Vite" ? ` * Run: npm create vite@latest ${n !== "root directory" ? n : "."} -- --template react-ts` : ` * Run the appropriate CLI command for ${t} to create a new project in ${n !== "root directory" ? n : "current directory"}`}
|
|
55
62
|
- Navigate to the project directory if created in a subfolder
|
|
56
63
|
- Wait for the framework setup to complete before proceeding
|
|
57
64
|
- DO NOT skip this step - the framework base is required for all subsequent steps
|
|
@@ -63,7 +70,7 @@ ${e === "Next.js" ? ` * Run: npx create-next-app@latest ${t !== "root directory
|
|
|
63
70
|
- If the tool fails, STOP and ask user to check their Content Island configuration
|
|
64
71
|
|
|
65
72
|
### STEP 3: PACKAGE INSTALLATION (MANDATORY BEFORE CODE GENERATION)
|
|
66
|
-
${
|
|
73
|
+
${t === "Nuxt" ? `
|
|
67
74
|
- FIRST install the official Content Island packages:
|
|
68
75
|
* npm install @content-island/nuxt@latest
|
|
69
76
|
* npm install @content-island/api-client@latest
|
|
@@ -97,7 +104,7 @@ ${e === "Nuxt" ? `
|
|
|
97
104
|
Result: title: string;
|
|
98
105
|
|
|
99
106
|
### STEP 5: API CLIENT IMPLEMENTATION (DOCUMENTED METHODS ONLY)
|
|
100
|
-
${
|
|
107
|
+
${t === "Nuxt" ? `
|
|
101
108
|
- Use Nuxt Content Island composables from '@content-island/nuxt'
|
|
102
109
|
- Configure the module in nuxt.config.ts with your project settings
|
|
103
110
|
- Check Nuxt/Content Island documentation for setup: https://github.com/content-island/nuxt-content-island#readme
|
|
@@ -132,28 +139,28 @@ ${e === "Nuxt" ? `
|
|
|
132
139
|
|
|
133
140
|
|
|
134
141
|
### STEP 6: PROJECT STRUCTURE GENERATION (FRAMEWORK-SPECIFIC)
|
|
135
|
-
- Framework: ${
|
|
136
|
-
- Location: ${
|
|
137
|
-
- Pages requested: ${
|
|
138
|
-
- Styling: ${
|
|
139
|
-
- Design approach: ${
|
|
142
|
+
- Framework: ${t}
|
|
143
|
+
- Location: ${n}
|
|
144
|
+
- Pages requested: ${a}
|
|
145
|
+
- Styling: ${o}
|
|
146
|
+
- Design approach: ${s}
|
|
140
147
|
|
|
141
148
|
#### FRAMEWORK-SPECIFIC REQUIREMENTS:
|
|
142
|
-
${
|
|
149
|
+
${t === "Next.js" ? `
|
|
143
150
|
- Use App Router (app/ directory)
|
|
144
151
|
- Implement getStaticProps/getServerSideProps for data fetching
|
|
145
152
|
- Create proper page.tsx files for each route
|
|
146
153
|
- Use Next.js Image component for optimization
|
|
147
154
|
- Implement proper metadata for SEO
|
|
148
155
|
` : ""}
|
|
149
|
-
${
|
|
156
|
+
${t === "Astro" ? `
|
|
150
157
|
- Use .astro components with proper frontmatter
|
|
151
158
|
- Implement getStaticPaths for dynamic routes
|
|
152
159
|
- Use partial hydration with client:load directives
|
|
153
160
|
- Create Layout components for consistent structure
|
|
154
161
|
- Use Astro.glob() for content collection if needed
|
|
155
162
|
` : ""}
|
|
156
|
-
${
|
|
163
|
+
${t === "Nuxt" ? `
|
|
157
164
|
- Use Nuxt 3 composition API
|
|
158
165
|
- Implement useFetch for data fetching
|
|
159
166
|
- Create proper pages/ directory structure
|
|
@@ -162,7 +169,7 @@ ${e === "Nuxt" ? `
|
|
|
162
169
|
` : ""}
|
|
163
170
|
|
|
164
171
|
### STEP 7: ENVIRONMENT CONFIGURATION (MANDATORY FILES)
|
|
165
|
-
${
|
|
172
|
+
${t === "Nuxt" ? `
|
|
166
173
|
- Configure Content Island in nuxt.config.ts:
|
|
167
174
|
runtimeConfig: {
|
|
168
175
|
contentIsland: {
|
|
@@ -177,14 +184,14 @@ ${e === "Nuxt" ? `
|
|
|
177
184
|
- Document environment setup in README.md
|
|
178
185
|
|
|
179
186
|
### STEP 8: STYLING IMPLEMENTATION
|
|
180
|
-
${
|
|
181
|
-
- Install and configure Tailwind CSS according to ${
|
|
187
|
+
${o.toLowerCase().includes("tailwind") ? `
|
|
188
|
+
- Install and configure Tailwind CSS according to ${t} official documentation
|
|
182
189
|
- Create tailwind.config.js with proper content paths
|
|
183
190
|
- Add Tailwind directives to main CSS file
|
|
184
191
|
- Implement responsive design classes (sm:, md:, lg:, xl:)
|
|
185
192
|
- Use Tailwind accessibility classes for WCAG compliance
|
|
186
193
|
` : `
|
|
187
|
-
- Implement ${
|
|
194
|
+
- Implement ${o} according to ${t} best practices
|
|
188
195
|
- Create modular CSS structure
|
|
189
196
|
- Implement responsive design with media queries
|
|
190
197
|
- Ensure accessibility with proper contrast and focus states
|
|
@@ -242,7 +249,7 @@ ${n.toLowerCase().includes("tailwind") ? `
|
|
|
242
249
|
1. NEVER invent Content Island API methods not documented
|
|
243
250
|
2. NEVER create example/dummy interfaces - use ONLY project structure data
|
|
244
251
|
3. NEVER proceed without executing framework CLI first and calling get-content-island-project tool second
|
|
245
|
-
4. NEVER skip installing the correct Content Island packages (${
|
|
252
|
+
4. NEVER skip installing the correct Content Island packages (${t === "Nuxt" ? "@content-island/nuxt@latest and @content-island/api-client@latest" : "@content-island/api-client@latest"})
|
|
246
253
|
5. NEVER skip error handling implementation
|
|
247
254
|
6. NEVER ignore accessibility requirements
|
|
248
255
|
7. NEVER use deprecated framework patterns
|
|
@@ -255,7 +262,7 @@ ${n.toLowerCase().includes("tailwind") ? `
|
|
|
255
262
|
## VALIDATION CHECKLIST - VERIFY BEFORE COMPLETION:
|
|
256
263
|
□ Framework CLI executed to create base project structure
|
|
257
264
|
□ get-content-island-project tool called and data parsed
|
|
258
|
-
${
|
|
265
|
+
${t === "Nuxt" ? `
|
|
259
266
|
□ @content-island/nuxt@latest and @content-island/api-client@latest installed
|
|
260
267
|
□ @content-island/nuxt configured in nuxt.config.ts modules
|
|
261
268
|
□ Media type imported from '@content-island/api-client' when needed
|
|
@@ -266,7 +273,7 @@ ${e === "Nuxt" ? `
|
|
|
266
273
|
□ All content types have corresponding API functions using official client
|
|
267
274
|
`}
|
|
268
275
|
□ TypeScript interfaces exactly match project structure
|
|
269
|
-
□ All requested pages (${
|
|
276
|
+
□ All requested pages (${a}) are implemented
|
|
270
277
|
□ Error handling implemented for all API calls
|
|
271
278
|
□ Loading states implemented for all data fetching
|
|
272
279
|
□ Responsive design implemented
|
|
@@ -312,24 +319,425 @@ Please provide all this information so I can create your project.`
|
|
|
312
319
|
};
|
|
313
320
|
}
|
|
314
321
|
);
|
|
315
|
-
},
|
|
316
|
-
|
|
317
|
-
},
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
322
|
+
}, P = () => {
|
|
323
|
+
j();
|
|
324
|
+
}, U = {
|
|
325
|
+
401: "Invalid or expired token. Check your CONTENT_ISLAND_ACCESS_TOKEN configuration.",
|
|
326
|
+
403: "This token does not have write permissions. Configure a write token in CONTENT_ISLAND_ACCESS_TOKEN.",
|
|
327
|
+
404: "Resource not found. Verify that the content type, content ID, or project exists."
|
|
328
|
+
}, M = (e) => {
|
|
329
|
+
const t = U[e.status] ?? `API error: ${e.status} ${e.statusText}`;
|
|
330
|
+
if (!e.body)
|
|
331
|
+
return t;
|
|
332
|
+
const a = [];
|
|
333
|
+
if (e.body.error && a.push(e.body.error), e.body.fieldErrors)
|
|
334
|
+
for (const [n, o] of Object.entries(e.body.fieldErrors)) {
|
|
335
|
+
const s = typeof o == "string" ? o : JSON.stringify(o);
|
|
336
|
+
a.push(` - ${n}: ${s}`);
|
|
337
|
+
}
|
|
338
|
+
return a.length > 0 ? `${t}
|
|
339
|
+
${a.join(`
|
|
340
|
+
`)}` : t;
|
|
341
|
+
}, h = (e) => {
|
|
342
|
+
let t = e instanceof Error ? e.message : String(e);
|
|
343
|
+
try {
|
|
344
|
+
const a = JSON.parse(t);
|
|
345
|
+
t = M(a);
|
|
346
|
+
} catch {
|
|
347
|
+
}
|
|
348
|
+
return { content: [{ type: "text", text: t }], isError: !0 };
|
|
349
|
+
}, F = () => {
|
|
350
|
+
u.tool(
|
|
351
|
+
"create-content-island-content",
|
|
352
|
+
`Create a new content entry in the Content Island project. Requires a write token.
|
|
353
|
+
|
|
354
|
+
WORKFLOW (perform BEFORE calling this tool):
|
|
355
|
+
1. Call get-content-island-project to fetch the project schema. Identify the contentType and read every field's "type", "isArray", "validations" array ({ name, customArgs? }), and the project's "languages" list.
|
|
356
|
+
2. For every field you plan to set, validate the value against the field's "validations":
|
|
357
|
+
- "required": every required field must be present and non-empty.
|
|
358
|
+
- "min-length" / "max-length": value.length (or array length when isArray) must satisfy customArgs.
|
|
359
|
+
- "unique": warn the user that the API will reject duplicates against existing contents of this type.
|
|
360
|
+
- "media-type": only file extensions listed in customArgs are allowed.
|
|
361
|
+
If any constraint would fail, do NOT call this tool — ask the user for a valid value first.
|
|
362
|
+
3. Language coverage:
|
|
363
|
+
- If project.languages has more than one language and the user provided field values for only one language (or omitted "content"), STOP and ASK the user:
|
|
364
|
+
"This project supports [list languages]. Should I create the content only in [user's language], or in every language?"
|
|
365
|
+
- If the user picks "every language", ASK whether to:
|
|
366
|
+
(a) reuse the same values across all languages, or
|
|
367
|
+
(b) translate the values into each language.
|
|
368
|
+
Build one entry per language in the "content" array accordingly.
|
|
369
|
+
4. Only then call this tool.
|
|
370
|
+
|
|
371
|
+
Use the returned contentTypes to build a valid payload. Follow these rules strictly:
|
|
372
|
+
|
|
373
|
+
Field type rules (based on the "type" property of each field):
|
|
374
|
+
- short-text: string value. If isArray is true, an array of strings.
|
|
375
|
+
- long-text: markdown string. CRITICAL: If the markdown contains image references with local file paths (e.g. ) or external URLs, you MUST first upload EACH image by calling upload-content-island-media for every image, then replace the original path/URL with the "url" returned by the upload. Only then call this tool with the updated markdown. Never leave local file paths in the markdown — they will not work. Code blocks must specify the language (e.g. \`\`\`typescript, \`\`\`bash, \`\`\`python, etc.). Cannot be an array.
|
|
376
|
+
- number: numeric value. If isArray is true, an array of numbers.
|
|
377
|
+
- boolean: true or false. Cannot be an array.
|
|
378
|
+
- date: string in "yyyy-mm-dd" format. Cannot be an array.
|
|
379
|
+
- date-time: ISO 8601 string (e.g. "2026-04-14T10:30:00Z"). Cannot be an array.
|
|
380
|
+
- color: hexadecimal color string (e.g. "#FF5733"). Cannot be an array.
|
|
381
|
+
- media: object with { name: string, url: string }. Upload first via upload-content-island-media. If isArray is true, an array of media objects.
|
|
382
|
+
|
|
383
|
+
Fields whose "type" contains "|" (e.g. "abc123|Foo") are either ENUMS or RELATIONS — both look identical in "type", so you MUST disambiguate using "tsType":
|
|
384
|
+
- ENUM (type contains "|" AND tsType starts with a single quote, e.g. "'M' | 'L' | 'XL'"): send a plain string equal to one of the values inside the union (e.g. "M"). If isArray is true, an array of those strings. NEVER send a content id or an object — only the plain string.
|
|
385
|
+
- RELATION (type contains "|" AND tsType is exactly "string"): send the 24-char hex content id of an existing content of the related content type. If isArray is true, an array of ids. NEVER send a name or the value of a field — only the id.
|
|
386
|
+
|
|
387
|
+
General rules:
|
|
388
|
+
- Fields with isRequired: true MUST be included.
|
|
389
|
+
- Provide one entry per language in the project's languages array.
|
|
390
|
+
- contentType must match an existing content type name exactly.
|
|
391
|
+
- Field names must match exactly as defined in the schema.
|
|
392
|
+
|
|
393
|
+
Importing content from a web URL (blog posts, news articles, etc.):
|
|
394
|
+
If the user asks you to create content from a web URL, follow this workflow:
|
|
395
|
+
1. Fetch the URL and extract the relevant fields (title, body, author, etc.).
|
|
396
|
+
2. Identify every image referenced in the extracted content (both in long-text markdown and in media fields).
|
|
397
|
+
3. For EACH image, call upload-content-island-media with the image's absolute URL (e.g. "https://original-site.com/image.png"). This uploads the image to Content Island storage and returns a { name, url } object.
|
|
398
|
+
4. Replace every original image URL in the extracted content with the "url" returned by upload-content-island-media. Never leave the original external URLs — they may become broken links if the source site removes them.
|
|
399
|
+
5. Only then call this tool with the fully processed payload.`,
|
|
400
|
+
{
|
|
401
|
+
contentType: i.string().describe(
|
|
402
|
+
'The content type name (e.g. "post", "page"). Must match an existing content type name exactly. Use get-content-island-project to discover available content types.'
|
|
403
|
+
),
|
|
404
|
+
name: i.string().describe("Display name for the new content entry."),
|
|
405
|
+
content: i.array(
|
|
406
|
+
i.object({
|
|
407
|
+
language: i.string().optional().describe(
|
|
408
|
+
`Language code (e.g. "en", "es"). Must be one of the languages from the project. Defaults to the project's first language if omitted.`
|
|
409
|
+
),
|
|
410
|
+
fields: i.array(
|
|
411
|
+
i.object({
|
|
412
|
+
name: i.string().describe(
|
|
413
|
+
"Field name exactly as defined in the content type schema from get-content-island-project."
|
|
414
|
+
),
|
|
415
|
+
value: i.any().describe(
|
|
416
|
+
"Field value. Must match the type and constraints of the field as described in the tool description above."
|
|
417
|
+
)
|
|
418
|
+
})
|
|
419
|
+
).describe("Array of field name/value pairs. All fields with isRequired: true must be included.")
|
|
420
|
+
})
|
|
421
|
+
).optional().describe(
|
|
422
|
+
"Content entries per language. Provide one entry per language in the project. If omitted, creates an empty content with no field values."
|
|
423
|
+
)
|
|
424
|
+
},
|
|
425
|
+
async ({ contentType: e, name: t, content: a }) => {
|
|
426
|
+
try {
|
|
427
|
+
const n = g(), o = { contentType: e, name: t, content: a }, s = await n.createContent(o);
|
|
428
|
+
return {
|
|
429
|
+
content: [{ type: "text", text: JSON.stringify(s, null, 2) }]
|
|
430
|
+
};
|
|
431
|
+
} catch (n) {
|
|
432
|
+
return h(n);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
);
|
|
436
|
+
}, D = () => {
|
|
437
|
+
u.tool(
|
|
438
|
+
"get-content-island-project",
|
|
439
|
+
`Get the user's Content Island project schema (languages and contentTypes).
|
|
440
|
+
|
|
441
|
+
The contentTypes array mixes BOTH entities (item type: "entity", has a "fields" array) and enums (item type: "enum", has a "values" array).
|
|
442
|
+
|
|
443
|
+
CRITICAL — Disambiguating fields whose "type" contains "|" (e.g. "abc123|Foo"):
|
|
444
|
+
A field type that contains "|" can be either an ENUM or a RELATION to another entity. They look identical in "type". Use the field's "tsType" to tell them apart:
|
|
445
|
+
- tsType starts with a single quote (e.g. "'M' | 'L' | 'XL'") → the field is an ENUM. Valid values are the literal strings inside the union.
|
|
446
|
+
- tsType is exactly "string" → the field is a RELATION to a content of another type. Valid values are 24-char hex content ids.
|
|
447
|
+
|
|
448
|
+
Always read tsType first when the type contains "|". Do NOT guess from the name.
|
|
449
|
+
|
|
450
|
+
Each entity field also carries a "validations" array (optional). Each entry is { name, customArgs? } and may be one of: "required", "unique", "min-length", "max-length", "media-type". When you intend to write a value (via create-content-island-content or update-content-island-field-value), always check these validations and refuse to call the write tool if the user's value would fail any of them.
|
|
451
|
+
|
|
452
|
+
The "languages" array at the project root lists every language the project supports. Field values are stored per language, so a single content entry may have one fieldValue per language. When writing, consider language coverage explicitly (see the write tools' descriptions).`,
|
|
453
|
+
async () => {
|
|
454
|
+
const t = await g().getProject();
|
|
455
|
+
return {
|
|
456
|
+
content: [{ type: "text", text: JSON.stringify(t, null, 2) }]
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
);
|
|
460
|
+
}, m = 25, y = 100, _ = () => {
|
|
461
|
+
u.tool(
|
|
462
|
+
"list-content-island-contents",
|
|
463
|
+
`List content entries of the Content Island project. Returns the raw content structure with all fieldValues (each one carries its fieldName, language and value), so you can inspect every translation in one call. Drafts and unpublished values are included.
|
|
464
|
+
|
|
465
|
+
IMPORTANT: Before calling this tool, you SHOULD call get-content-island-project to discover the available contentTypes and languages so the filters you pass are valid.
|
|
466
|
+
|
|
467
|
+
Pagination:
|
|
468
|
+
- The response always includes { items, skip, take, hasMore }.
|
|
469
|
+
- Default take is ${m}, maximum allowed is ${y}.
|
|
470
|
+
- If hasMore is true, call this tool again with skip = previous skip + previous take to fetch the next page. Repeat until hasMore is false (or items is empty).
|
|
471
|
+
- hasMore is computed as items.length === take, so it can occasionally be a false positive when the total happens to be an exact multiple of take; in that case the next call returns an empty items array, which confirms the end of the list.
|
|
472
|
+
|
|
473
|
+
Filters (all optional):
|
|
474
|
+
- contentType: filter by content type name (e.g. "post"). Must match an existing content type from get-content-island-project.
|
|
475
|
+
- id: a single content id, or an array of ids to fetch a specific subset.
|
|
476
|
+
- language: restricts which fieldValues are included inside each content. NOTE: this does NOT remove contents from the result; a content without a translation in the given language will still appear, but with an empty fields array. If you need to discard those contents, filter them on your side.
|
|
477
|
+
- status: filter by publication state. One of "draft" (never published), "changed" (published but has unpublished edits), "published" (in sync with the live state), or an array combining several. To find contents with unpublished data, pass status: ["draft", "changed"].
|
|
478
|
+
- includeRelatedContent: when true, related content references are expanded inline in fieldValues.
|
|
479
|
+
|
|
480
|
+
Each returned item includes a "status" field with the same values described above. Use it to tell apart fully-published contents from contents with pending changes.
|
|
481
|
+
|
|
482
|
+
Sort (optional): object with any of:
|
|
483
|
+
- contentType: "asc" | "desc"
|
|
484
|
+
- lastUpdate: "asc" | "desc"`,
|
|
485
|
+
{
|
|
486
|
+
contentType: i.string().optional().describe("Filter by content type name. Must match an existing content type from get-content-island-project."),
|
|
487
|
+
id: i.union([i.string(), i.array(i.string())]).optional().describe("A single content id, or an array of ids to fetch a specific subset of contents."),
|
|
488
|
+
language: i.string().optional().describe(
|
|
489
|
+
'Language code (e.g. "en", "es"). Restricts which fieldValues are included inside each content. Does not remove contents that lack a translation in the given language.'
|
|
490
|
+
),
|
|
491
|
+
status: i.union([i.enum(["draft", "changed", "published"]), i.array(i.enum(["draft", "changed", "published"]))]).optional().describe(
|
|
492
|
+
'Filter by publication state. Pass a single value (e.g. "draft") or an array (e.g. ["draft", "changed"]) to find contents with unpublished data.'
|
|
493
|
+
),
|
|
494
|
+
includeRelatedContent: i.boolean().optional().describe("When true, related content references are expanded inline in fieldValues."),
|
|
495
|
+
sort: i.object({
|
|
496
|
+
contentType: i.enum(["asc", "desc"]).optional(),
|
|
497
|
+
lastUpdate: i.enum(["asc", "desc"]).optional()
|
|
498
|
+
}).optional().describe('Sort order. Each field is independently set to "asc" or "desc".'),
|
|
499
|
+
take: i.number().int().min(1).max(y).optional().describe(`Maximum number of items to return. Default ${m}, maximum ${y}.`),
|
|
500
|
+
skip: i.number().int().min(0).optional().describe("Number of items to skip from the start of the result set. Use for pagination.")
|
|
501
|
+
},
|
|
502
|
+
async ({ contentType: e, id: t, language: a, status: n, includeRelatedContent: o, sort: s, take: r, skip: d }) => {
|
|
503
|
+
try {
|
|
504
|
+
const l = g(), c = r ?? m, E = d ?? 0, p = {
|
|
505
|
+
pagination: { take: c, skip: E }
|
|
506
|
+
};
|
|
507
|
+
e !== void 0 && (p.contentType = e), t !== void 0 && (p.id = Array.isArray(t) ? { in: t } : t), a !== void 0 && (p.language = a), n !== void 0 && (p.status = Array.isArray(n) ? { in: n } : n), o !== void 0 && (p.includeRelatedContent = o), s !== void 0 && (p.sort = s);
|
|
508
|
+
const T = await l.getRawContentList(p), C = {
|
|
509
|
+
items: T,
|
|
510
|
+
skip: E,
|
|
511
|
+
take: c,
|
|
512
|
+
hasMore: T.length === c
|
|
513
|
+
};
|
|
514
|
+
return {
|
|
515
|
+
content: [{ type: "text", text: JSON.stringify(C, null, 2) }]
|
|
516
|
+
};
|
|
517
|
+
} catch (l) {
|
|
518
|
+
return h(l);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
);
|
|
522
|
+
}, $ = (e) => e?.type === "entity", b = (e) => !!(e == null || typeof e == "string" && e === "" || Array.isArray(e) && e.length === 0), N = (e, t) => t && Array.isArray(e) || typeof e == "string" ? e.length : null, V = (e) => {
|
|
523
|
+
const t = e.lastIndexOf(".");
|
|
524
|
+
return t === -1 ? null : e.slice(t);
|
|
525
|
+
}, q = (e, t) => {
|
|
526
|
+
const a = typeof e == "string" ? e : e?.url;
|
|
527
|
+
if (typeof a != "string")
|
|
528
|
+
return !1;
|
|
529
|
+
const n = V(a);
|
|
530
|
+
return n !== null && t.includes(n);
|
|
531
|
+
}, K = (e, t, a) => {
|
|
532
|
+
switch (e.name) {
|
|
533
|
+
case "required":
|
|
534
|
+
case "unique":
|
|
535
|
+
return null;
|
|
536
|
+
case "min-length": {
|
|
537
|
+
const n = N(t, a.isArray), o = e.customArgs?.length;
|
|
538
|
+
return n === null || typeof o != "number" ? null : n < o ? `expected min length ${o}, got ${n}` : null;
|
|
539
|
+
}
|
|
540
|
+
case "max-length": {
|
|
541
|
+
const n = N(t, a.isArray), o = e.customArgs?.length;
|
|
542
|
+
return n === null || typeof o != "number" ? null : n > o ? `expected max length ${o}, got ${n}` : null;
|
|
543
|
+
}
|
|
544
|
+
case "media-type": {
|
|
545
|
+
const n = (e.customArgs?.allowedExtensions ?? []).map((r) => r.name);
|
|
546
|
+
return n.length === 0 ? null : (a.isArray && Array.isArray(t) ? t : [t]).some((r) => !q(r, n)) ? `media file extension not in allowed list (${n.join(", ")})` : null;
|
|
547
|
+
}
|
|
548
|
+
default:
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
}, W = (e, t) => {
|
|
552
|
+
const a = (e.contentTypes ?? []).find((r) => $(r) && r.name === t.contentType.name);
|
|
553
|
+
if (!a)
|
|
325
554
|
return {
|
|
326
|
-
|
|
555
|
+
ok: !1,
|
|
556
|
+
errors: [`content type "${t.contentType.name}" not found in project schema`]
|
|
327
557
|
};
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
558
|
+
const n = [], o = e.languages ?? [], s = t.fields ?? [];
|
|
559
|
+
for (const r of a.fields)
|
|
560
|
+
if (r.isRequired)
|
|
561
|
+
for (const d of o) {
|
|
562
|
+
const l = s.find((c) => c.name === r.name && c.language === d);
|
|
563
|
+
(!l || b(l.value)) && n.push(`required field "${r.name}" is empty in language "${d}"`);
|
|
564
|
+
}
|
|
565
|
+
for (const r of s) {
|
|
566
|
+
const d = a.fields.find((l) => l.name === r.name);
|
|
567
|
+
if (!(!d || b(r.value)))
|
|
568
|
+
for (const l of d.validations ?? []) {
|
|
569
|
+
const c = K(l, r.value, d);
|
|
570
|
+
c && n.push(`field "${r.name}" [${r.language}]: ${c}`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return n.length === 0 ? { ok: !0 } : { ok: !1, errors: n };
|
|
574
|
+
}, B = (e) => `Cannot publish content — preflight validation failed:
|
|
575
|
+
${e.map((t) => ` - ${t}`).join(`
|
|
576
|
+
`)}
|
|
577
|
+
|
|
578
|
+
Fix the issues (e.g. via update-content-island-field-value) and retry. Use list-content-island-contents with the content id to inspect the current draft state.`, Y = () => {
|
|
579
|
+
u.tool(
|
|
580
|
+
"publish-content-island-content",
|
|
581
|
+
`Publish an existing content in the Content Island project. This promotes the current draft state to the live state visible to consumers without the PREVIEW_ token prefix. Requires a write token.
|
|
582
|
+
|
|
583
|
+
What this tool does:
|
|
584
|
+
- Runs a preflight that REFUSES to publish if the content is not valid (see "Preflight" below).
|
|
585
|
+
- Snapshots the content's current draft fieldValues into publishedValues.
|
|
586
|
+
- Sets the content's status to "published".
|
|
587
|
+
- Idempotent: publishing a content that is already published refreshes the published snapshot from the current draft (useful after editing a previously published content).
|
|
588
|
+
|
|
589
|
+
Preflight (server-side, ENFORCED — you cannot bypass it):
|
|
590
|
+
The tool fetches the project schema and the current draft of the content, and rejects the call with an error listing every issue if any of the following fails:
|
|
591
|
+
- Required fields: every field with isRequired: true must have a non-empty value in every project language. If a translation is missing, publish is blocked.
|
|
592
|
+
- Field validations: every populated value must satisfy its "min-length", "max-length", and "media-type" validations. "unique" is enforced by the backend (not preflighted here).
|
|
593
|
+
|
|
594
|
+
If the preflight rejects, the response lists all problems in one pass. Fix them (e.g. with update-content-island-field-value) and retry.
|
|
595
|
+
|
|
596
|
+
WORKFLOW (recommended BEFORE calling this tool, to give the user feedback before the preflight runs):
|
|
597
|
+
1. Call get-content-island-project to read the project schema (required fields, validations, languages).
|
|
598
|
+
2. Call list-content-island-contents with id = contentId to inspect the current draft values.
|
|
599
|
+
3. If you can already see that something is incomplete, ASK the user before attempting to publish — preflight will reject anyway but the user gets context sooner.
|
|
600
|
+
4. Otherwise call this tool; preflight will confirm the content is valid and publish it.
|
|
601
|
+
|
|
602
|
+
Errors:
|
|
603
|
+
- 400 (preflight) if required fields are empty or validations fail. The message lists every issue.
|
|
604
|
+
- 404 if the contentId does not exist.
|
|
605
|
+
- 401/403 if the configured token is not a write token.`,
|
|
606
|
+
{
|
|
607
|
+
contentId: i.string().describe("The id of the existing content entry to publish.")
|
|
608
|
+
},
|
|
609
|
+
async ({ contentId: e }) => {
|
|
610
|
+
try {
|
|
611
|
+
const t = g(), [a, n] = await Promise.all([t.getProject(), t.getRawContent({ id: e })]);
|
|
612
|
+
if (!n)
|
|
613
|
+
return {
|
|
614
|
+
content: [{ type: "text", text: `Content "${e}" not found.` }],
|
|
615
|
+
isError: !0
|
|
616
|
+
};
|
|
617
|
+
const o = W(a, n);
|
|
618
|
+
return o.ok === !1 ? {
|
|
619
|
+
content: [{ type: "text", text: B(o.errors) }],
|
|
620
|
+
isError: !0
|
|
621
|
+
} : (await t.publishContent(e), {
|
|
622
|
+
content: [{ type: "text", text: JSON.stringify({ contentId: e, status: "published" }, null, 2) }]
|
|
623
|
+
});
|
|
624
|
+
} catch (t) {
|
|
625
|
+
return h(t);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
);
|
|
629
|
+
}, G = () => {
|
|
630
|
+
u.tool(
|
|
631
|
+
"update-content-island-field-value",
|
|
632
|
+
`Update or add a single field value of an existing content in the Content Island project. Requires a write token.
|
|
633
|
+
|
|
634
|
+
Use this tool to:
|
|
635
|
+
- Change the value of a field for an existing language (e.g. fix a typo in the English title).
|
|
636
|
+
- Add a translation: insert the value of a field in a language that does not yet exist on the content.
|
|
637
|
+
|
|
638
|
+
WORKFLOW (perform BEFORE calling this tool):
|
|
639
|
+
1. Call get-content-island-project to fetch the project schema. Locate the field by name (case-sensitive) and read its "type", "isArray", "validations" array (each entry is { name, customArgs? }), and the project's "languages" list.
|
|
640
|
+
2. Validate the user's intended value against the field's "validations":
|
|
641
|
+
- "required": value must be non-empty / non-null.
|
|
642
|
+
- "min-length" / "max-length": value.length (or array length when isArray) must satisfy customArgs.
|
|
643
|
+
- "unique": warn the user that the API will reject the call if another content of this type already has that value.
|
|
644
|
+
- "media-type": only file extensions listed in customArgs are allowed.
|
|
645
|
+
If any constraint would fail, do NOT call this tool — ask the user for a valid value first.
|
|
646
|
+
3. Language coverage:
|
|
647
|
+
- If project.languages has more than one language and the user specified only one (or none), STOP and ASK the user:
|
|
648
|
+
"This project supports [list languages]. Should I update only the [user's language] value, or apply the change to every language?"
|
|
649
|
+
- If the user picks "every language", ASK whether to:
|
|
650
|
+
(a) reuse the same value in every language, or
|
|
651
|
+
(b) translate the value into each language.
|
|
652
|
+
For (b), produce a translation per language before calling.
|
|
653
|
+
- Call this tool once per language, each call with its corresponding value.
|
|
654
|
+
4. Only then call this tool.
|
|
655
|
+
|
|
656
|
+
Field type rules (must match the field "type"):
|
|
657
|
+
- short-text: string. If isArray is true, an array of strings.
|
|
658
|
+
- long-text: markdown string. CRITICAL: If the markdown contains image references with local file paths or external URLs, you MUST first upload EACH image via upload-content-island-media and replace the original path/URL with the "url" returned by the upload. Never leave local file paths in the markdown — they will not work. Code blocks must specify the language. Cannot be an array.
|
|
659
|
+
- number: numeric value. If isArray is true, an array of numbers.
|
|
660
|
+
- boolean: true or false. Cannot be an array.
|
|
661
|
+
- date: string in "yyyy-mm-dd" format. Cannot be an array.
|
|
662
|
+
- date-time: ISO 8601 string. Cannot be an array.
|
|
663
|
+
- color: hexadecimal color string (e.g. "#FF5733"). Cannot be an array.
|
|
664
|
+
- media: object with { name: string, url: string }. Upload first via upload-content-island-media. If isArray is true, an array of media objects.
|
|
665
|
+
|
|
666
|
+
Fields whose "type" contains "|" (e.g. "abc123|Foo") are either ENUMS or RELATIONS — both look identical in "type", so you MUST disambiguate using "tsType":
|
|
667
|
+
- ENUM (type contains "|" AND tsType starts with a single quote, e.g. "'M' | 'L' | 'XL'"): send a plain string equal to one of the values inside the union (e.g. "M"). If isArray is true, an array of those strings. NEVER send a content id or an object.
|
|
668
|
+
- RELATION (type contains "|" AND tsType is exactly "string"): send the 24-char hex content id of an existing content of the related content type. If isArray is true, an array of ids. NEVER send a name.
|
|
669
|
+
|
|
670
|
+
The tool always identifies the field by its name within the content type. If the field is renamed in the schema between calls, this tool will return a 404; refresh the schema with get-content-island-project and retry.`,
|
|
671
|
+
{
|
|
672
|
+
contentId: i.string().describe("The id of the existing content entry to update."),
|
|
673
|
+
fieldName: i.string().describe("Name of the field as defined in the content type schema (from get-content-island-project)."),
|
|
674
|
+
language: i.string().describe('Language code of the field value (e.g. "en", "es"). Must be one of the project languages.'),
|
|
675
|
+
value: i.any().describe(
|
|
676
|
+
"The new field value. Must match the type and isArray of the field as described in the tool description above."
|
|
677
|
+
)
|
|
678
|
+
},
|
|
679
|
+
async ({ contentId: e, fieldName: t, language: a, value: n }) => {
|
|
680
|
+
try {
|
|
681
|
+
return await g().updateContentFieldValue(e, { fieldName: t, language: a }, n), {
|
|
682
|
+
content: [
|
|
683
|
+
{ type: "text", text: JSON.stringify({ contentId: e, fieldName: t, language: a, status: "updated" }, null, 2) }
|
|
684
|
+
]
|
|
685
|
+
};
|
|
686
|
+
} catch (o) {
|
|
687
|
+
return h(o);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
);
|
|
691
|
+
}, H = {
|
|
692
|
+
".png": "image/png",
|
|
693
|
+
".jpg": "image/jpeg",
|
|
694
|
+
".jpeg": "image/jpeg",
|
|
695
|
+
".gif": "image/gif",
|
|
696
|
+
".webp": "image/webp",
|
|
697
|
+
".avif": "image/avif",
|
|
698
|
+
".svg": "image/svg+xml",
|
|
699
|
+
".pdf": "application/pdf",
|
|
700
|
+
".mp4": "video/mp4",
|
|
701
|
+
".webm": "video/webm",
|
|
702
|
+
".mov": "video/quicktime",
|
|
703
|
+
".mp3": "audio/mpeg",
|
|
704
|
+
".wav": "audio/wav"
|
|
705
|
+
}, J = (e) => /^https?:\/\//i.test(e), X = async (e) => {
|
|
706
|
+
const t = await fetch(e);
|
|
707
|
+
if (!t.ok)
|
|
708
|
+
throw new Error(`Failed to fetch ${e}: ${t.status} ${t.statusText}`);
|
|
709
|
+
const a = await t.blob(), n = new URL(e).pathname, o = A(n) || "download";
|
|
710
|
+
return { blob: a, fileName: o };
|
|
711
|
+
}, z = async (e) => {
|
|
712
|
+
const t = await S(e), a = R(e).toLowerCase(), n = H[a] ?? "application/octet-stream", o = new Blob([t], { type: n }), s = A(e);
|
|
713
|
+
return { blob: o, fileName: s };
|
|
714
|
+
}, Q = () => {
|
|
715
|
+
u.tool(
|
|
716
|
+
"upload-content-island-media",
|
|
717
|
+
"Upload a media file to the Content Island project. Accepts a local file path or a URL (http/https). Requires a write token. Note: file attachments in the chat cannot be uploaded directly — provide the file path on disk or a public URL instead.",
|
|
718
|
+
{
|
|
719
|
+
source: i.string().describe(
|
|
720
|
+
'Path to a local file or a URL (http/https) pointing to the media to upload. Examples: "./assets/hero.png", "https://example.com/photo.jpg". File attachments in the conversation cannot be used directly — ask the user for the file path or URL.'
|
|
721
|
+
),
|
|
722
|
+
fileName: i.string().optional().describe(
|
|
723
|
+
"Override the file name used in Content Island. Defaults to the file name from the source path or URL."
|
|
724
|
+
)
|
|
725
|
+
},
|
|
726
|
+
async ({ source: e, fileName: t }) => {
|
|
727
|
+
try {
|
|
728
|
+
const { blob: a, fileName: n } = J(e) ? await X(e) : await z(e), s = await g().uploadMedia({ file: a, fileName: t ?? n });
|
|
729
|
+
return {
|
|
730
|
+
content: [{ type: "text", text: JSON.stringify(s, null, 2) }]
|
|
731
|
+
};
|
|
732
|
+
} catch (a) {
|
|
733
|
+
return h(a);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
);
|
|
737
|
+
}, Z = () => {
|
|
738
|
+
D(), _(), F(), G(), Y(), Q();
|
|
331
739
|
};
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const
|
|
335
|
-
await
|
|
740
|
+
P();
|
|
741
|
+
Z();
|
|
742
|
+
const ee = new O();
|
|
743
|
+
await u.connect(ee);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@content-island/mcp",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Content Island - MCP (Model Context Protocol) server",
|
|
5
5
|
"private": false,
|
|
6
6
|
"sideEffects": false,
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"type-check": "tsc --noEmit --preserveWatchOutput"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@content-island/api-client": "0.
|
|
36
|
+
"@content-island/api-client": "0.20.0",
|
|
37
37
|
"@modelcontextprotocol/sdk": "1.13.3",
|
|
38
38
|
"zod": "3.25.71"
|
|
39
39
|
},
|