@content-island/mcp 0.2.1 → 0.2.2
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/dist/index.js +123 -135
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
import { z as
|
|
1
|
+
import { z as a } from "zod";
|
|
2
2
|
import { McpServer as w } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
-
import { createClient as v } from "@content-island/api-client";
|
|
3
|
+
import { createClient as v, isApiClientError as R } from "@content-island/api-client";
|
|
4
4
|
import { readFile as S } from "node:fs/promises";
|
|
5
|
-
import { basename as A, extname as
|
|
6
|
-
import { StdioServerTransport as
|
|
5
|
+
import { basename as A, extname as O } from "node:path";
|
|
6
|
+
import { StdioServerTransport as x } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
7
|
const f = {
|
|
8
8
|
CONTENT_ISLAND_ACCESS_TOKEN: process.env.CONTENT_ISLAND_ACCESS_TOKEN,
|
|
9
9
|
CONTENT_ISLAND_DOMAIN: process.env.CONTENT_ISLAND_DOMAIN,
|
|
10
10
|
CONTENT_ISLAND_SECURE_PROTOCOL: process.env.CONTENT_ISLAND_SECURE_PROTOCOL !== "false",
|
|
11
11
|
CONTENT_ISLAND_API_VERSION: process.env.CONTENT_ISLAND_API_VERSION
|
|
12
|
-
},
|
|
13
|
-
accessToken:
|
|
12
|
+
}, T = "PREVIEW_", L = (e) => e.startsWith(T) ? e : `${T}${e}`, g = () => v({
|
|
13
|
+
accessToken: L(f.CONTENT_ISLAND_ACCESS_TOKEN),
|
|
14
14
|
domain: f.CONTENT_ISLAND_DOMAIN,
|
|
15
15
|
secureProtocol: f.CONTENT_ISLAND_SECURE_PROTOCOL,
|
|
16
16
|
apiVersion: f.CONTENT_ISLAND_API_VERSION
|
|
17
|
-
}),
|
|
18
|
-
version:
|
|
17
|
+
}), k = "0.2.2", P = {
|
|
18
|
+
version: k
|
|
19
19
|
}, u = new w({
|
|
20
20
|
name: "Content Island",
|
|
21
|
-
version:
|
|
21
|
+
version: P.version
|
|
22
22
|
}), j = () => {
|
|
23
23
|
u.prompt(
|
|
24
24
|
"create-content-island-project",
|
|
25
25
|
"Professional MCP Server prompt for creating modern frontend projects integrated with Content Island CMS",
|
|
26
26
|
{
|
|
27
|
-
framework:
|
|
28
|
-
pages:
|
|
29
|
-
location:
|
|
30
|
-
styling:
|
|
31
|
-
design:
|
|
27
|
+
framework: a.string().describe("Framework choice (Next.js, Astro, Nuxt, etc.)"),
|
|
28
|
+
pages: a.string().describe("Pages needed (Homepage, Blog, Contact, etc.)"),
|
|
29
|
+
location: a.string().describe("Project location (root directory or subfolder name)"),
|
|
30
|
+
styling: a.string().describe("Styling preference (Tailwind CSS or custom)"),
|
|
31
|
+
design: a.string().describe("Design assets (mockups, wireframes, or none)")
|
|
32
32
|
},
|
|
33
33
|
async (e) => {
|
|
34
|
-
const { framework: t, pages:
|
|
35
|
-
return t &&
|
|
34
|
+
const { framework: t, pages: i, location: n, styling: o, design: r } = e;
|
|
35
|
+
return t && i && n && o && r ? {
|
|
36
36
|
description: "Content Island project generator",
|
|
37
37
|
messages: [
|
|
38
38
|
{
|
|
@@ -45,10 +45,10 @@ I'll create a professional ${t} application integrated with your Content Island
|
|
|
45
45
|
|
|
46
46
|
**Configuration:**
|
|
47
47
|
- Framework: **${t}**
|
|
48
|
-
- Pages: ${
|
|
48
|
+
- Pages: ${i}
|
|
49
49
|
- Location: ${n}
|
|
50
50
|
- Styling: ${o}
|
|
51
|
-
- Design: ${
|
|
51
|
+
- Design: ${r}
|
|
52
52
|
|
|
53
53
|
# CRITICAL IMPLEMENTATION INSTRUCTIONS - FOLLOW EXACTLY
|
|
54
54
|
|
|
@@ -141,9 +141,9 @@ ${t === "Nuxt" ? `
|
|
|
141
141
|
### STEP 6: PROJECT STRUCTURE GENERATION (FRAMEWORK-SPECIFIC)
|
|
142
142
|
- Framework: ${t}
|
|
143
143
|
- Location: ${n}
|
|
144
|
-
- Pages requested: ${
|
|
144
|
+
- Pages requested: ${i}
|
|
145
145
|
- Styling: ${o}
|
|
146
|
-
- Design approach: ${
|
|
146
|
+
- Design approach: ${r}
|
|
147
147
|
|
|
148
148
|
#### FRAMEWORK-SPECIFIC REQUIREMENTS:
|
|
149
149
|
${t === "Next.js" ? `
|
|
@@ -273,7 +273,7 @@ ${t === "Nuxt" ? `
|
|
|
273
273
|
□ All content types have corresponding API functions using official client
|
|
274
274
|
`}
|
|
275
275
|
□ TypeScript interfaces exactly match project structure
|
|
276
|
-
□ All requested pages (${
|
|
276
|
+
□ All requested pages (${i}) are implemented
|
|
277
277
|
□ Error handling implemented for all API calls
|
|
278
278
|
□ Loading states implemented for all data fetching
|
|
279
279
|
□ Responsive design implemented
|
|
@@ -319,34 +319,22 @@ Please provide all this information so I can create your project.`
|
|
|
319
319
|
};
|
|
320
320
|
}
|
|
321
321
|
);
|
|
322
|
-
},
|
|
322
|
+
}, U = () => {
|
|
323
323
|
j();
|
|
324
|
-
},
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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 = () => {
|
|
324
|
+
}, D = {
|
|
325
|
+
UNAUTHORIZED: "Invalid or expired token. Check your CONTENT_ISLAND_ACCESS_TOKEN configuration.",
|
|
326
|
+
FORBIDDEN: "This token does not have write permissions. Configure a write token in CONTENT_ISLAND_ACCESS_TOKEN.",
|
|
327
|
+
NOT_FOUND: "Resource not found. Verify that the content type, content ID, or project exists.",
|
|
328
|
+
VALIDATION_ERROR: "Invalid input. See details below.",
|
|
329
|
+
NETWORK_ERROR: "Could not reach Content Island. Check your network and the API endpoint configuration."
|
|
330
|
+
}, F = (e) => D[e.code] ?? `API error: ${e.status} ${e.code}`, M = (e) => {
|
|
331
|
+
const t = e.details?.fields;
|
|
332
|
+
return Array.isArray(t) ? t.map((i) => ` - ${i.field}: ${i.message}`) : [];
|
|
333
|
+
}, _ = (e) => {
|
|
334
|
+
const t = [F(e), e.message, ...M(e)];
|
|
335
|
+
return e.requestId && t.push(`(requestId: ${e.requestId})`), t.join(`
|
|
336
|
+
`);
|
|
337
|
+
}, h = (e) => R(e) ? { content: [{ type: "text", text: _(e) }], isError: !0 } : { content: [{ type: "text", text: e instanceof Error ? e.message : String(e) }], isError: !0 }, $ = () => {
|
|
350
338
|
u.tool(
|
|
351
339
|
"create-content-island-content",
|
|
352
340
|
`Create a new content entry in the Content Island project. Requires a write token.
|
|
@@ -398,21 +386,21 @@ If the user asks you to create content from a web URL, follow this workflow:
|
|
|
398
386
|
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
387
|
5. Only then call this tool with the fully processed payload.`,
|
|
400
388
|
{
|
|
401
|
-
contentType:
|
|
389
|
+
contentType: a.string().describe(
|
|
402
390
|
'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
391
|
),
|
|
404
|
-
name:
|
|
405
|
-
content:
|
|
406
|
-
|
|
407
|
-
language:
|
|
392
|
+
name: a.string().describe("Display name for the new content entry."),
|
|
393
|
+
content: a.array(
|
|
394
|
+
a.object({
|
|
395
|
+
language: a.string().optional().describe(
|
|
408
396
|
`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
397
|
),
|
|
410
|
-
fields:
|
|
411
|
-
|
|
412
|
-
name:
|
|
398
|
+
fields: a.array(
|
|
399
|
+
a.object({
|
|
400
|
+
name: a.string().describe(
|
|
413
401
|
"Field name exactly as defined in the content type schema from get-content-island-project."
|
|
414
402
|
),
|
|
415
|
-
value:
|
|
403
|
+
value: a.any().describe(
|
|
416
404
|
"Field value. Must match the type and constraints of the field as described in the tool description above."
|
|
417
405
|
)
|
|
418
406
|
})
|
|
@@ -422,18 +410,18 @@ If the user asks you to create content from a web URL, follow this workflow:
|
|
|
422
410
|
"Content entries per language. Provide one entry per language in the project. If omitted, creates an empty content with no field values."
|
|
423
411
|
)
|
|
424
412
|
},
|
|
425
|
-
async ({ contentType: e, name: t, content:
|
|
413
|
+
async ({ contentType: e, name: t, content: i }) => {
|
|
426
414
|
try {
|
|
427
|
-
const n = g(), o = { contentType: e, name: t, content:
|
|
415
|
+
const n = g(), o = { contentType: e, name: t, content: i }, r = await n.createContent(o);
|
|
428
416
|
return {
|
|
429
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
417
|
+
content: [{ type: "text", text: JSON.stringify(r, null, 2) }]
|
|
430
418
|
};
|
|
431
419
|
} catch (n) {
|
|
432
420
|
return h(n);
|
|
433
421
|
}
|
|
434
422
|
}
|
|
435
423
|
);
|
|
436
|
-
},
|
|
424
|
+
}, V = () => {
|
|
437
425
|
u.tool(
|
|
438
426
|
"get-content-island-project",
|
|
439
427
|
`Get the user's Content Island project schema (languages and contentTypes).
|
|
@@ -457,7 +445,7 @@ The "languages" array at the project root lists every language the project suppo
|
|
|
457
445
|
};
|
|
458
446
|
}
|
|
459
447
|
);
|
|
460
|
-
}, m = 25, y = 100,
|
|
448
|
+
}, m = 25, y = 100, q = () => {
|
|
461
449
|
u.tool(
|
|
462
450
|
"list-content-island-contents",
|
|
463
451
|
`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.
|
|
@@ -483,33 +471,33 @@ Sort (optional): object with any of:
|
|
|
483
471
|
- contentType: "asc" | "desc"
|
|
484
472
|
- lastUpdate: "asc" | "desc"`,
|
|
485
473
|
{
|
|
486
|
-
contentType:
|
|
487
|
-
id:
|
|
488
|
-
language:
|
|
474
|
+
contentType: a.string().optional().describe("Filter by content type name. Must match an existing content type from get-content-island-project."),
|
|
475
|
+
id: a.union([a.string(), a.array(a.string())]).optional().describe("A single content id, or an array of ids to fetch a specific subset of contents."),
|
|
476
|
+
language: a.string().optional().describe(
|
|
489
477
|
'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
478
|
),
|
|
491
|
-
status:
|
|
479
|
+
status: a.union([a.enum(["draft", "changed", "published"]), a.array(a.enum(["draft", "changed", "published"]))]).optional().describe(
|
|
492
480
|
'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
481
|
),
|
|
494
|
-
includeRelatedContent:
|
|
495
|
-
sort:
|
|
496
|
-
contentType:
|
|
497
|
-
lastUpdate:
|
|
482
|
+
includeRelatedContent: a.boolean().optional().describe("When true, related content references are expanded inline in fieldValues."),
|
|
483
|
+
sort: a.object({
|
|
484
|
+
contentType: a.enum(["asc", "desc"]).optional(),
|
|
485
|
+
lastUpdate: a.enum(["asc", "desc"]).optional()
|
|
498
486
|
}).optional().describe('Sort order. Each field is independently set to "asc" or "desc".'),
|
|
499
|
-
take:
|
|
500
|
-
skip:
|
|
487
|
+
take: a.number().int().min(1).max(y).optional().describe(`Maximum number of items to return. Default ${m}, maximum ${y}.`),
|
|
488
|
+
skip: a.number().int().min(0).optional().describe("Number of items to skip from the start of the result set. Use for pagination.")
|
|
501
489
|
},
|
|
502
|
-
async ({ contentType: e, id: t, language:
|
|
490
|
+
async ({ contentType: e, id: t, language: i, status: n, includeRelatedContent: o, sort: r, take: s, skip: d }) => {
|
|
503
491
|
try {
|
|
504
|
-
const l = g(), c =
|
|
492
|
+
const l = g(), c = s ?? m, E = d ?? 0, p = {
|
|
505
493
|
pagination: { take: c, skip: E }
|
|
506
494
|
};
|
|
507
|
-
e !== void 0 && (p.contentType = e), t !== void 0 && (p.id = Array.isArray(t) ? { in: t } : t),
|
|
508
|
-
const
|
|
509
|
-
items:
|
|
495
|
+
e !== void 0 && (p.contentType = e), t !== void 0 && (p.id = Array.isArray(t) ? { in: t } : t), i !== void 0 && (p.language = i), n !== void 0 && (p.status = Array.isArray(n) ? { in: n } : n), o !== void 0 && (p.includeRelatedContent = o), r !== void 0 && (p.sort = r);
|
|
496
|
+
const I = await l.getRawContentList(p), C = {
|
|
497
|
+
items: I,
|
|
510
498
|
skip: E,
|
|
511
499
|
take: c,
|
|
512
|
-
hasMore:
|
|
500
|
+
hasMore: I.length === c
|
|
513
501
|
};
|
|
514
502
|
return {
|
|
515
503
|
content: [{ type: "text", text: JSON.stringify(C, null, 2) }]
|
|
@@ -519,63 +507,63 @@ Sort (optional): object with any of:
|
|
|
519
507
|
}
|
|
520
508
|
}
|
|
521
509
|
);
|
|
522
|
-
},
|
|
510
|
+
}, K = (e) => e?.type === "entity", N = (e) => !!(e == null || typeof e == "string" && e === "" || Array.isArray(e) && e.length === 0), b = (e, t) => t && Array.isArray(e) || typeof e == "string" ? e.length : null, W = (e) => {
|
|
523
511
|
const t = e.lastIndexOf(".");
|
|
524
512
|
return t === -1 ? null : e.slice(t);
|
|
525
|
-
},
|
|
526
|
-
const
|
|
527
|
-
if (typeof
|
|
513
|
+
}, B = (e, t) => {
|
|
514
|
+
const i = typeof e == "string" ? e : e?.url;
|
|
515
|
+
if (typeof i != "string")
|
|
528
516
|
return !1;
|
|
529
|
-
const n =
|
|
517
|
+
const n = W(i);
|
|
530
518
|
return n !== null && t.includes(n);
|
|
531
|
-
},
|
|
519
|
+
}, H = (e, t, i) => {
|
|
532
520
|
switch (e.name) {
|
|
533
521
|
case "required":
|
|
534
522
|
case "unique":
|
|
535
523
|
return null;
|
|
536
524
|
case "min-length": {
|
|
537
|
-
const n =
|
|
525
|
+
const n = b(t, i.isArray), o = e.customArgs?.length;
|
|
538
526
|
return n === null || typeof o != "number" ? null : n < o ? `expected min length ${o}, got ${n}` : null;
|
|
539
527
|
}
|
|
540
528
|
case "max-length": {
|
|
541
|
-
const n =
|
|
529
|
+
const n = b(t, i.isArray), o = e.customArgs?.length;
|
|
542
530
|
return n === null || typeof o != "number" ? null : n > o ? `expected max length ${o}, got ${n}` : null;
|
|
543
531
|
}
|
|
544
532
|
case "media-type": {
|
|
545
|
-
const n = (e.customArgs?.allowedExtensions ?? []).map((
|
|
546
|
-
return n.length === 0 ? null : (
|
|
533
|
+
const n = (e.customArgs?.allowedExtensions ?? []).map((s) => s.name);
|
|
534
|
+
return n.length === 0 ? null : (i.isArray && Array.isArray(t) ? t : [t]).some((s) => !B(s, n)) ? `media file extension not in allowed list (${n.join(", ")})` : null;
|
|
547
535
|
}
|
|
548
536
|
default:
|
|
549
537
|
return null;
|
|
550
538
|
}
|
|
551
|
-
},
|
|
552
|
-
const
|
|
553
|
-
if (!
|
|
539
|
+
}, Y = (e, t) => {
|
|
540
|
+
const i = (e.contentTypes ?? []).find((s) => K(s) && s.name === t.contentType.name);
|
|
541
|
+
if (!i)
|
|
554
542
|
return {
|
|
555
543
|
ok: !1,
|
|
556
544
|
errors: [`content type "${t.contentType.name}" not found in project schema`]
|
|
557
545
|
};
|
|
558
|
-
const n = [], o = e.languages ?? [],
|
|
559
|
-
for (const
|
|
560
|
-
if (
|
|
546
|
+
const n = [], o = e.languages ?? [], r = t.fields ?? [];
|
|
547
|
+
for (const s of i.fields)
|
|
548
|
+
if (s.isRequired)
|
|
561
549
|
for (const d of o) {
|
|
562
|
-
const l =
|
|
563
|
-
(!l ||
|
|
550
|
+
const l = r.find((c) => c.name === s.name && c.language === d);
|
|
551
|
+
(!l || N(l.value)) && n.push(`required field "${s.name}" is empty in language "${d}"`);
|
|
564
552
|
}
|
|
565
|
-
for (const
|
|
566
|
-
const d =
|
|
567
|
-
if (!(!d ||
|
|
553
|
+
for (const s of r) {
|
|
554
|
+
const d = i.fields.find((l) => l.name === s.name);
|
|
555
|
+
if (!(!d || N(s.value)))
|
|
568
556
|
for (const l of d.validations ?? []) {
|
|
569
|
-
const c =
|
|
570
|
-
c && n.push(`field "${
|
|
557
|
+
const c = H(l, s.value, d);
|
|
558
|
+
c && n.push(`field "${s.name}" [${s.language}]: ${c}`);
|
|
571
559
|
}
|
|
572
560
|
}
|
|
573
561
|
return n.length === 0 ? { ok: !0 } : { ok: !1, errors: n };
|
|
574
|
-
},
|
|
562
|
+
}, G = (e) => `Cannot publish content — preflight validation failed:
|
|
575
563
|
${e.map((t) => ` - ${t}`).join(`
|
|
576
564
|
`)}
|
|
577
565
|
|
|
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.`,
|
|
566
|
+
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.`, X = () => {
|
|
579
567
|
u.tool(
|
|
580
568
|
"publish-content-island-content",
|
|
581
569
|
`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.
|
|
@@ -604,19 +592,19 @@ Errors:
|
|
|
604
592
|
- 404 if the contentId does not exist.
|
|
605
593
|
- 401/403 if the configured token is not a write token.`,
|
|
606
594
|
{
|
|
607
|
-
contentId:
|
|
595
|
+
contentId: a.string().describe("The id of the existing content entry to publish.")
|
|
608
596
|
},
|
|
609
597
|
async ({ contentId: e }) => {
|
|
610
598
|
try {
|
|
611
|
-
const t = g(), [
|
|
599
|
+
const t = g(), [i, n] = await Promise.all([t.getProject(), t.getRawContent({ id: e })]);
|
|
612
600
|
if (!n)
|
|
613
601
|
return {
|
|
614
602
|
content: [{ type: "text", text: `Content "${e}" not found.` }],
|
|
615
603
|
isError: !0
|
|
616
604
|
};
|
|
617
|
-
const o =
|
|
605
|
+
const o = Y(i, n);
|
|
618
606
|
return o.ok === !1 ? {
|
|
619
|
-
content: [{ type: "text", text:
|
|
607
|
+
content: [{ type: "text", text: G(o.errors) }],
|
|
620
608
|
isError: !0
|
|
621
609
|
} : (await t.publishContent(e), {
|
|
622
610
|
content: [{ type: "text", text: JSON.stringify({ contentId: e, status: "published" }, null, 2) }]
|
|
@@ -626,7 +614,7 @@ Errors:
|
|
|
626
614
|
}
|
|
627
615
|
}
|
|
628
616
|
);
|
|
629
|
-
},
|
|
617
|
+
}, z = () => {
|
|
630
618
|
u.tool(
|
|
631
619
|
"update-content-island-field-value",
|
|
632
620
|
`Update or add a single field value of an existing content in the Content Island project. Requires a write token.
|
|
@@ -669,18 +657,18 @@ Fields whose "type" contains "|" (e.g. "abc123|Foo") are either ENUMS or RELATIO
|
|
|
669
657
|
|
|
670
658
|
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
659
|
{
|
|
672
|
-
contentId:
|
|
673
|
-
fieldName:
|
|
674
|
-
language:
|
|
675
|
-
value:
|
|
660
|
+
contentId: a.string().describe("The id of the existing content entry to update."),
|
|
661
|
+
fieldName: a.string().describe("Name of the field as defined in the content type schema (from get-content-island-project)."),
|
|
662
|
+
language: a.string().describe('Language code of the field value (e.g. "en", "es"). Must be one of the project languages.'),
|
|
663
|
+
value: a.any().describe(
|
|
676
664
|
"The new field value. Must match the type and isArray of the field as described in the tool description above."
|
|
677
665
|
)
|
|
678
666
|
},
|
|
679
|
-
async ({ contentId: e, fieldName: t, language:
|
|
667
|
+
async ({ contentId: e, fieldName: t, language: i, value: n }) => {
|
|
680
668
|
try {
|
|
681
|
-
return await g().updateContentFieldValue(e, { fieldName: t, language:
|
|
669
|
+
return await g().updateContentFieldValue(e, { fieldName: t, language: i }, n), {
|
|
682
670
|
content: [
|
|
683
|
-
{ type: "text", text: JSON.stringify({ contentId: e, fieldName: t, language:
|
|
671
|
+
{ type: "text", text: JSON.stringify({ contentId: e, fieldName: t, language: i, status: "updated" }, null, 2) }
|
|
684
672
|
]
|
|
685
673
|
};
|
|
686
674
|
} catch (o) {
|
|
@@ -688,7 +676,7 @@ The tool always identifies the field by its name within the content type. If the
|
|
|
688
676
|
}
|
|
689
677
|
}
|
|
690
678
|
);
|
|
691
|
-
},
|
|
679
|
+
}, J = {
|
|
692
680
|
".png": "image/png",
|
|
693
681
|
".jpg": "image/jpeg",
|
|
694
682
|
".jpeg": "image/jpeg",
|
|
@@ -702,42 +690,42 @@ The tool always identifies the field by its name within the content type. If the
|
|
|
702
690
|
".mov": "video/quicktime",
|
|
703
691
|
".mp3": "audio/mpeg",
|
|
704
692
|
".wav": "audio/wav"
|
|
705
|
-
},
|
|
693
|
+
}, Q = (e) => /^https?:\/\//i.test(e), Z = async (e) => {
|
|
706
694
|
const t = await fetch(e);
|
|
707
695
|
if (!t.ok)
|
|
708
696
|
throw new Error(`Failed to fetch ${e}: ${t.status} ${t.statusText}`);
|
|
709
|
-
const
|
|
710
|
-
return { blob:
|
|
711
|
-
},
|
|
712
|
-
const t = await S(e),
|
|
713
|
-
return { blob: o, fileName:
|
|
714
|
-
},
|
|
697
|
+
const i = await t.blob(), n = new URL(e).pathname, o = A(n) || "download";
|
|
698
|
+
return { blob: i, fileName: o };
|
|
699
|
+
}, ee = async (e) => {
|
|
700
|
+
const t = await S(e), i = O(e).toLowerCase(), n = J[i] ?? "application/octet-stream", o = new Blob([t], { type: n }), r = A(e);
|
|
701
|
+
return { blob: o, fileName: r };
|
|
702
|
+
}, te = () => {
|
|
715
703
|
u.tool(
|
|
716
704
|
"upload-content-island-media",
|
|
717
705
|
"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
706
|
{
|
|
719
|
-
source:
|
|
707
|
+
source: a.string().describe(
|
|
720
708
|
'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
709
|
),
|
|
722
|
-
fileName:
|
|
710
|
+
fileName: a.string().optional().describe(
|
|
723
711
|
"Override the file name used in Content Island. Defaults to the file name from the source path or URL."
|
|
724
712
|
)
|
|
725
713
|
},
|
|
726
714
|
async ({ source: e, fileName: t }) => {
|
|
727
715
|
try {
|
|
728
|
-
const { blob:
|
|
716
|
+
const { blob: i, fileName: n } = Q(e) ? await Z(e) : await ee(e), r = await g().uploadMedia({ file: i, fileName: t ?? n });
|
|
729
717
|
return {
|
|
730
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
718
|
+
content: [{ type: "text", text: JSON.stringify(r, null, 2) }]
|
|
731
719
|
};
|
|
732
|
-
} catch (
|
|
733
|
-
return h(
|
|
720
|
+
} catch (i) {
|
|
721
|
+
return h(i);
|
|
734
722
|
}
|
|
735
723
|
}
|
|
736
724
|
);
|
|
737
|
-
},
|
|
738
|
-
|
|
725
|
+
}, ne = () => {
|
|
726
|
+
V(), q(), $(), z(), X(), te();
|
|
739
727
|
};
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
const
|
|
743
|
-
await u.connect(
|
|
728
|
+
U();
|
|
729
|
+
ne();
|
|
730
|
+
const ae = new x();
|
|
731
|
+
await u.connect(ae);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@content-island/mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
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.21.0",
|
|
37
37
|
"@modelcontextprotocol/sdk": "1.13.3",
|
|
38
38
|
"zod": "3.25.71"
|
|
39
39
|
},
|