@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.
- package/dist/blogTools.js +690 -0
- package/dist/client/plexoClient.js +107 -0
- package/dist/index.js +17 -3
- package/package.json +1 -1
- package/src/blogTools.ts +695 -0
- package/src/client/plexoClient.ts +130 -0
- package/src/index.ts +17 -3
|
@@ -355,4 +355,134 @@ export class PlexoClient {
|
|
|
355
355
|
|
|
356
356
|
return data;
|
|
357
357
|
}
|
|
358
|
+
|
|
359
|
+
/** Fetches a single template's full details, including designJson — used by the blog
|
|
360
|
+
* layout tools to read back a layout template's current content (there's no other
|
|
361
|
+
* templateId-scoped GET in the public API; list endpoints only return metadata). */
|
|
362
|
+
async getTemplate(templateId: string): Promise<any> {
|
|
363
|
+
this.checkAuth();
|
|
364
|
+
const res = await fetch(`${this.baseUrl}/api/v1/templates/${encodeURIComponent(templateId)}`, {
|
|
365
|
+
method: "GET",
|
|
366
|
+
headers: this.getHeaders(),
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const data = (await res.json()) as any;
|
|
370
|
+
if (!res.ok) {
|
|
371
|
+
throw new Error(data?.error || `Failed to fetch template (Status ${res.status})`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return data;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** Shared request helper for every /api/blog/{templateId}/... route below — these are
|
|
378
|
+
* the same admin endpoints the Plexo dashboard's own blog UI calls, and (like every
|
|
379
|
+
* other route in this client) accept the same Bearer/x-api-key auth as the rest of the
|
|
380
|
+
* public API (see resolveUser/resolveBlogAdminSite server-side). */
|
|
381
|
+
private async blogFetch(method: string, templateId: string, path: string, body?: unknown): Promise<any> {
|
|
382
|
+
this.checkAuth();
|
|
383
|
+
const res = await fetch(`${this.baseUrl}/api/blog/${encodeURIComponent(templateId)}${path}`, {
|
|
384
|
+
method,
|
|
385
|
+
headers: this.getHeaders(),
|
|
386
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
const data = (await res.json().catch(() => ({}))) as any;
|
|
390
|
+
if (!res.ok) {
|
|
391
|
+
throw new Error(data?.error || `Blog request failed (Status ${res.status})`);
|
|
392
|
+
}
|
|
393
|
+
return data;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** Idempotent — returns the existing layout template id if one's already attached to
|
|
397
|
+
* this kind rather than creating a duplicate. Pair with updateTemplate() to set its
|
|
398
|
+
* designJson (see blogTools.ts's designBlogLayout). */
|
|
399
|
+
async ensureBlogLayout(templateId: string, kind: "post" | "listing"): Promise<any> {
|
|
400
|
+
return this.blogFetch("POST", templateId, `/layout/${kind}`);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/** Detaches the layout (reverts to the default theme) without deleting the underlying Template row. */
|
|
404
|
+
async resetBlogLayout(templateId: string, kind: "post" | "listing"): Promise<any> {
|
|
405
|
+
return this.blogFetch("DELETE", templateId, `/layout/${kind}`);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async getBlogSite(templateId: string): Promise<any> {
|
|
409
|
+
return this.blogFetch("GET", templateId, "/site");
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async updateBlogSite(templateId: string, patch: Record<string, any>): Promise<any> {
|
|
413
|
+
return this.blogFetch("PATCH", templateId, "/site", patch);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async listBlogPosts(templateId: string, params: { status?: string; search?: string; categoryId?: string } = {}): Promise<any> {
|
|
417
|
+
const query = new URLSearchParams();
|
|
418
|
+
if (params.status) query.set("status", params.status);
|
|
419
|
+
if (params.search) query.set("search", params.search);
|
|
420
|
+
if (params.categoryId) query.set("categoryId", params.categoryId);
|
|
421
|
+
const qs = query.toString();
|
|
422
|
+
return this.blogFetch("GET", templateId, `/posts${qs ? `?${qs}` : ""}`);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async getBlogPost(templateId: string, postId: string): Promise<any> {
|
|
426
|
+
return this.blogFetch("GET", templateId, `/posts/${encodeURIComponent(postId)}`);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async createBlogPost(templateId: string, payload: Record<string, any>): Promise<any> {
|
|
430
|
+
return this.blogFetch("POST", templateId, "/posts", payload);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async updateBlogPost(templateId: string, postId: string, payload: Record<string, any>): Promise<any> {
|
|
434
|
+
return this.blogFetch("PATCH", templateId, `/posts/${encodeURIComponent(postId)}`, payload);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async deleteBlogPost(templateId: string, postId: string): Promise<any> {
|
|
438
|
+
return this.blogFetch("DELETE", templateId, `/posts/${encodeURIComponent(postId)}`);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async listBlogCategories(templateId: string): Promise<any> {
|
|
442
|
+
return this.blogFetch("GET", templateId, "/categories");
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
async createBlogCategory(templateId: string, payload: { name: string; description?: string; parentId?: string | null }): Promise<any> {
|
|
446
|
+
return this.blogFetch("POST", templateId, "/categories", payload);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
async deleteBlogCategory(templateId: string, categoryId: string): Promise<any> {
|
|
450
|
+
return this.blogFetch("DELETE", templateId, `/categories/${encodeURIComponent(categoryId)}`);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async listBlogTags(templateId: string): Promise<any> {
|
|
454
|
+
return this.blogFetch("GET", templateId, "/tags");
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
async createBlogTag(templateId: string, name: string): Promise<any> {
|
|
458
|
+
return this.blogFetch("POST", templateId, "/tags", { name });
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async deleteBlogTag(templateId: string, tagId: string): Promise<any> {
|
|
462
|
+
return this.blogFetch("DELETE", templateId, `/tags/${encodeURIComponent(tagId)}`);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
async listBlogAuthors(templateId: string): Promise<any> {
|
|
466
|
+
return this.blogFetch("GET", templateId, "/authors");
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
async createBlogAuthor(templateId: string, payload: { name: string; bio?: string; avatarUrl?: string }): Promise<any> {
|
|
470
|
+
return this.blogFetch("POST", templateId, "/authors", payload);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
async deleteBlogAuthor(templateId: string, authorId: string): Promise<any> {
|
|
474
|
+
return this.blogFetch("DELETE", templateId, `/authors/${encodeURIComponent(authorId)}`);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
async listBlogComments(templateId: string, status?: string): Promise<any> {
|
|
478
|
+
return this.blogFetch("GET", templateId, `/comments${status ? `?status=${encodeURIComponent(status)}` : ""}`);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async moderateBlogComment(templateId: string, commentId: string, status: string): Promise<any> {
|
|
482
|
+
return this.blogFetch("PATCH", templateId, `/comments/${encodeURIComponent(commentId)}`, { status });
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
async deleteBlogComment(templateId: string, commentId: string): Promise<any> {
|
|
486
|
+
return this.blogFetch("DELETE", templateId, `/comments/${encodeURIComponent(commentId)}`);
|
|
487
|
+
}
|
|
358
488
|
}
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
ListToolsRequestSchema,
|
|
8
8
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
9
9
|
import { PlexoClient } from "./client/plexoClient.js";
|
|
10
|
+
import { BLOG_TOOL_DEFS, BLOG_TOOL_NAMES, handleBlogToolCall } from "./blogTools.js";
|
|
10
11
|
|
|
11
12
|
const server = new Server(
|
|
12
13
|
{
|
|
@@ -136,7 +137,7 @@ EXAMPLE VALID designJson PAYLOAD (hero row, plus a 3-item product grid row):
|
|
|
136
137
|
properties: {
|
|
137
138
|
type: {
|
|
138
139
|
type: "string",
|
|
139
|
-
enum: ["heading", "paragraph", "text", "button", "card", "image", "menu", "social", "divider", "spacer", "form_container", "input", "textarea", "select", "carousel", "html", "icon", "table", "timer", "video"],
|
|
140
|
+
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"],
|
|
140
141
|
},
|
|
141
142
|
style: { type: "object" },
|
|
142
143
|
attributes: { type: "object", description: "Component attributes (text, href, src, title, description, links, fields). For type 'html', raw markup goes under 'htmlContent' — NOT 'text'." },
|
|
@@ -228,7 +229,7 @@ Uses the SAME fully-hydrated layout schema as publish_landing_page — designJso
|
|
|
228
229
|
properties: {
|
|
229
230
|
type: {
|
|
230
231
|
type: "string",
|
|
231
|
-
enum: ["heading", "paragraph", "text", "button", "card", "image", "menu", "social", "divider", "spacer", "form_container", "input", "textarea", "select", "carousel", "html", "icon", "table", "timer", "video"],
|
|
232
|
+
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"],
|
|
232
233
|
},
|
|
233
234
|
style: { type: "object" },
|
|
234
235
|
attributes: {
|
|
@@ -503,6 +504,7 @@ To edit a page's CONTENT (designJson), use update_template with the same templat
|
|
|
503
504
|
},
|
|
504
505
|
},
|
|
505
506
|
},
|
|
507
|
+
...BLOG_TOOL_DEFS,
|
|
506
508
|
],
|
|
507
509
|
};
|
|
508
510
|
});
|
|
@@ -720,8 +722,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
720
722
|
};
|
|
721
723
|
}
|
|
722
724
|
|
|
723
|
-
default:
|
|
725
|
+
default: {
|
|
726
|
+
if (BLOG_TOOL_NAMES.has(name)) {
|
|
727
|
+
const result = await handleBlogToolCall(name, (args as any) || {}, client);
|
|
728
|
+
return {
|
|
729
|
+
content: [
|
|
730
|
+
{
|
|
731
|
+
type: "text",
|
|
732
|
+
text: JSON.stringify(result, null, 2),
|
|
733
|
+
},
|
|
734
|
+
],
|
|
735
|
+
};
|
|
736
|
+
}
|
|
724
737
|
throw new Error(`Unknown tool: ${name}`);
|
|
738
|
+
}
|
|
725
739
|
}
|
|
726
740
|
} catch (error) {
|
|
727
741
|
return {
|