@charisol/plexo-mcp 1.0.7 → 1.0.8

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.
@@ -273,5 +273,112 @@ class PlexoClient {
273
273
  }
274
274
  return data;
275
275
  }
276
+ /** Fetches a single template's full details, including designJson — used by the blog
277
+ * layout tools to read back a layout template's current content (there's no other
278
+ * templateId-scoped GET in the public API; list endpoints only return metadata). */
279
+ async getTemplate(templateId) {
280
+ this.checkAuth();
281
+ const res = await fetch(`${this.baseUrl}/api/v1/templates/${encodeURIComponent(templateId)}`, {
282
+ method: "GET",
283
+ headers: this.getHeaders(),
284
+ });
285
+ const data = (await res.json());
286
+ if (!res.ok) {
287
+ throw new Error(data?.error || `Failed to fetch template (Status ${res.status})`);
288
+ }
289
+ return data;
290
+ }
291
+ /** Shared request helper for every /api/blog/{templateId}/... route below — these are
292
+ * the same admin endpoints the Plexo dashboard's own blog UI calls, and (like every
293
+ * other route in this client) accept the same Bearer/x-api-key auth as the rest of the
294
+ * public API (see resolveUser/resolveBlogAdminSite server-side). */
295
+ async blogFetch(method, templateId, path, body) {
296
+ this.checkAuth();
297
+ const res = await fetch(`${this.baseUrl}/api/blog/${encodeURIComponent(templateId)}${path}`, {
298
+ method,
299
+ headers: this.getHeaders(),
300
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
301
+ });
302
+ const data = (await res.json().catch(() => ({})));
303
+ if (!res.ok) {
304
+ throw new Error(data?.error || `Blog request failed (Status ${res.status})`);
305
+ }
306
+ return data;
307
+ }
308
+ /** Idempotent — returns the existing layout template id if one's already attached to
309
+ * this kind rather than creating a duplicate. Pair with updateTemplate() to set its
310
+ * designJson (see blogTools.ts's designBlogLayout). */
311
+ async ensureBlogLayout(templateId, kind) {
312
+ return this.blogFetch("POST", templateId, `/layout/${kind}`);
313
+ }
314
+ /** Detaches the layout (reverts to the default theme) without deleting the underlying Template row. */
315
+ async resetBlogLayout(templateId, kind) {
316
+ return this.blogFetch("DELETE", templateId, `/layout/${kind}`);
317
+ }
318
+ async getBlogSite(templateId) {
319
+ return this.blogFetch("GET", templateId, "/site");
320
+ }
321
+ async updateBlogSite(templateId, patch) {
322
+ return this.blogFetch("PATCH", templateId, "/site", patch);
323
+ }
324
+ async listBlogPosts(templateId, params = {}) {
325
+ const query = new URLSearchParams();
326
+ if (params.status)
327
+ query.set("status", params.status);
328
+ if (params.search)
329
+ query.set("search", params.search);
330
+ if (params.categoryId)
331
+ query.set("categoryId", params.categoryId);
332
+ const qs = query.toString();
333
+ return this.blogFetch("GET", templateId, `/posts${qs ? `?${qs}` : ""}`);
334
+ }
335
+ async getBlogPost(templateId, postId) {
336
+ return this.blogFetch("GET", templateId, `/posts/${encodeURIComponent(postId)}`);
337
+ }
338
+ async createBlogPost(templateId, payload) {
339
+ return this.blogFetch("POST", templateId, "/posts", payload);
340
+ }
341
+ async updateBlogPost(templateId, postId, payload) {
342
+ return this.blogFetch("PATCH", templateId, `/posts/${encodeURIComponent(postId)}`, payload);
343
+ }
344
+ async deleteBlogPost(templateId, postId) {
345
+ return this.blogFetch("DELETE", templateId, `/posts/${encodeURIComponent(postId)}`);
346
+ }
347
+ async listBlogCategories(templateId) {
348
+ return this.blogFetch("GET", templateId, "/categories");
349
+ }
350
+ async createBlogCategory(templateId, payload) {
351
+ return this.blogFetch("POST", templateId, "/categories", payload);
352
+ }
353
+ async deleteBlogCategory(templateId, categoryId) {
354
+ return this.blogFetch("DELETE", templateId, `/categories/${encodeURIComponent(categoryId)}`);
355
+ }
356
+ async listBlogTags(templateId) {
357
+ return this.blogFetch("GET", templateId, "/tags");
358
+ }
359
+ async createBlogTag(templateId, name) {
360
+ return this.blogFetch("POST", templateId, "/tags", { name });
361
+ }
362
+ async deleteBlogTag(templateId, tagId) {
363
+ return this.blogFetch("DELETE", templateId, `/tags/${encodeURIComponent(tagId)}`);
364
+ }
365
+ async listBlogAuthors(templateId) {
366
+ return this.blogFetch("GET", templateId, "/authors");
367
+ }
368
+ async createBlogAuthor(templateId, payload) {
369
+ return this.blogFetch("POST", templateId, "/authors", payload);
370
+ }
371
+ async deleteBlogAuthor(templateId, authorId) {
372
+ return this.blogFetch("DELETE", templateId, `/authors/${encodeURIComponent(authorId)}`);
373
+ }
374
+ async listBlogComments(templateId, status) {
375
+ return this.blogFetch("GET", templateId, `/comments${status ? `?status=${encodeURIComponent(status)}` : ""}`);
376
+ }
377
+ async moderateBlogComment(templateId, commentId, status) {
378
+ return this.blogFetch("PATCH", templateId, `/comments/${encodeURIComponent(commentId)}`, { status });
379
+ }
380
+ async deleteBlogComment(templateId, commentId) {
381
+ return this.blogFetch("DELETE", templateId, `/comments/${encodeURIComponent(commentId)}`);
382
+ }
276
383
  }
277
384
  exports.PlexoClient = PlexoClient;
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
5
5
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
6
6
  const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
7
7
  const plexoClient_js_1 = require("./client/plexoClient.js");
8
+ const blogTools_js_1 = require("./blogTools.js");
8
9
  const server = new index_js_1.Server({
9
10
  name: "plexo-mcp",
10
11
  version: "1.0.0",
@@ -129,7 +130,7 @@ EXAMPLE VALID designJson PAYLOAD (hero row, plus a 3-item product grid row):
129
130
  properties: {
130
131
  type: {
131
132
  type: "string",
132
- enum: ["heading", "paragraph", "text", "button", "card", "image", "menu", "social", "divider", "spacer", "form_container", "input", "textarea", "select", "carousel", "html", "icon", "table", "timer", "video"],
133
+ enum: ["heading", "paragraph", "text", "button", "card", "image", "menu", "social", "divider", "spacer", "form_container", "input", "textarea", "select", "carousel", "html", "icon", "table", "timer", "video", "accordion", "blog_title", "blog_content", "blog_featured_image", "blog_date", "blog_author", "blog_categories", "blog_comments", "blog_post_list"],
133
134
  },
134
135
  style: { type: "object" },
135
136
  attributes: { type: "object", description: "Component attributes (text, href, src, title, description, links, fields). For type 'html', raw markup goes under 'htmlContent' — NOT 'text'." },
@@ -221,7 +222,7 @@ Uses the SAME fully-hydrated layout schema as publish_landing_page — designJso
221
222
  properties: {
222
223
  type: {
223
224
  type: "string",
224
- enum: ["heading", "paragraph", "text", "button", "card", "image", "menu", "social", "divider", "spacer", "form_container", "input", "textarea", "select", "carousel", "html", "icon", "table", "timer", "video"],
225
+ enum: ["heading", "paragraph", "text", "button", "card", "image", "menu", "social", "divider", "spacer", "form_container", "input", "textarea", "select", "carousel", "html", "icon", "table", "timer", "video", "accordion", "blog_title", "blog_content", "blog_featured_image", "blog_date", "blog_author", "blog_categories", "blog_comments", "blog_post_list"],
225
226
  },
226
227
  style: { type: "object" },
227
228
  attributes: {
@@ -496,6 +497,7 @@ To edit a page's CONTENT (designJson), use update_template with the same templat
496
497
  },
497
498
  },
498
499
  },
500
+ ...blogTools_js_1.BLOG_TOOL_DEFS,
499
501
  ],
500
502
  };
501
503
  });
@@ -687,8 +689,20 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
687
689
  ],
688
690
  };
689
691
  }
690
- default:
692
+ default: {
693
+ if (blogTools_js_1.BLOG_TOOL_NAMES.has(name)) {
694
+ const result = await (0, blogTools_js_1.handleBlogToolCall)(name, args || {}, client);
695
+ return {
696
+ content: [
697
+ {
698
+ type: "text",
699
+ text: JSON.stringify(result, null, 2),
700
+ },
701
+ ],
702
+ };
703
+ }
691
704
  throw new Error(`Unknown tool: ${name}`);
705
+ }
692
706
  }
693
707
  }
694
708
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@charisol/plexo-mcp",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "Model Context Protocol (MCP) Server for Plexo - Landing page generation, email templates, publishing, and analytics.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {