@content-island/mcp 0.2.0 → 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.
Files changed (2) hide show
  1. package/dist/index.js +123 -135
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,38 +1,38 @@
1
- import { z as i } from "zod";
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 R } from "node:path";
6
- import { StdioServerTransport as O } from "@modelcontextprotocol/sdk/server/stdio.js";
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
- }, I = "PREVIEW_", x = (e) => e.startsWith(I) ? e : `${I}${e}`, g = () => v({
13
- accessToken: x(f.CONTENT_ISLAND_ACCESS_TOKEN),
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
- }), L = "0.2.0", k = {
18
- version: L
17
+ }), k = "0.2.2", P = {
18
+ version: k
19
19
  }, u = new w({
20
20
  name: "Content Island",
21
- version: k.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: 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)")
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: a, location: n, styling: o, design: s } = e;
35
- return t && a && n && o && s ? {
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: ${a}
48
+ - Pages: ${i}
49
49
  - Location: ${n}
50
50
  - Styling: ${o}
51
- - Design: ${s}
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: ${a}
144
+ - Pages requested: ${i}
145
145
  - Styling: ${o}
146
- - Design approach: ${s}
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 (${a}) are implemented
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
- }, P = () => {
322
+ }, U = () => {
323
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 = () => {
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: i.string().describe(
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: i.string().describe("Display name for the new content entry."),
405
- content: i.array(
406
- i.object({
407
- language: i.string().optional().describe(
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: i.array(
411
- i.object({
412
- name: i.string().describe(
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: i.any().describe(
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: a }) => {
413
+ async ({ contentType: e, name: t, content: i }) => {
426
414
  try {
427
- const n = g(), o = { contentType: e, name: t, content: a }, s = await n.createContent(o);
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(s, null, 2) }]
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
- }, D = () => {
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: 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(
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: i.union([i.enum(["draft", "changed", "published"]), i.array(i.enum(["draft", "changed", "published"]))]).optional().describe(
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: 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()
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: 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.")
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: a, status: n, includeRelatedContent: o, sort: s, take: r, skip: d }) => {
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 = r ?? m, E = d ?? 0, p = {
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), 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,
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: T.length === c
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
- }, $ = (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) => {
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
- }, q = (e, t) => {
526
- const a = typeof e == "string" ? e : e?.url;
527
- if (typeof a != "string")
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 = V(a);
517
+ const n = W(i);
530
518
  return n !== null && t.includes(n);
531
- }, K = (e, t, a) => {
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 = N(t, a.isArray), o = e.customArgs?.length;
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 = N(t, a.isArray), o = e.customArgs?.length;
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((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;
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
- }, W = (e, t) => {
552
- const a = (e.contentTypes ?? []).find((r) => $(r) && r.name === t.contentType.name);
553
- if (!a)
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 ?? [], s = t.fields ?? [];
559
- for (const r of a.fields)
560
- if (r.isRequired)
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 = 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}"`);
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 r of s) {
566
- const d = a.fields.find((l) => l.name === r.name);
567
- if (!(!d || b(r.value)))
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 = K(l, r.value, d);
570
- c && n.push(`field "${r.name}" [${r.language}]: ${c}`);
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
- }, B = (e) => `Cannot publish content — preflight validation failed:
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.`, Y = () => {
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: i.string().describe("The id of the existing content entry to publish.")
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(), [a, n] = await Promise.all([t.getProject(), t.getRawContent({ id: e })]);
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 = W(a, n);
605
+ const o = Y(i, n);
618
606
  return o.ok === !1 ? {
619
- content: [{ type: "text", text: B(o.errors) }],
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
- }, G = () => {
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: 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(
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: a, value: n }) => {
667
+ async ({ contentId: e, fieldName: t, language: i, value: n }) => {
680
668
  try {
681
- return await g().updateContentFieldValue(e, { fieldName: t, language: a }, n), {
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: a, status: "updated" }, null, 2) }
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
- }, H = {
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
- }, J = (e) => /^https?:\/\//i.test(e), X = async (e) => {
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 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 = () => {
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: i.string().describe(
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: i.string().optional().describe(
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: a, fileName: n } = J(e) ? await X(e) : await z(e), s = await g().uploadMedia({ file: a, fileName: t ?? n });
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(s, null, 2) }]
718
+ content: [{ type: "text", text: JSON.stringify(r, null, 2) }]
731
719
  };
732
- } catch (a) {
733
- return h(a);
720
+ } catch (i) {
721
+ return h(i);
734
722
  }
735
723
  }
736
724
  );
737
- }, Z = () => {
738
- D(), _(), F(), G(), Y(), Q();
725
+ }, ne = () => {
726
+ V(), q(), $(), z(), X(), te();
739
727
  };
740
- P();
741
- Z();
742
- const ee = new O();
743
- await u.connect(ee);
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.0",
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.19.0",
36
+ "@content-island/api-client": "0.21.0",
37
37
  "@modelcontextprotocol/sdk": "1.13.3",
38
38
  "zod": "3.25.71"
39
39
  },