@mangerik/wordpress-mcp 0.1.0
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/.env.example +32 -0
- package/CHANGELOG.md +23 -0
- package/LICENSE +21 -0
- package/README.md +256 -0
- package/dist/config.d.ts +80 -0
- package/dist/config.js +84 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +145 -0
- package/dist/prompts.d.ts +7 -0
- package/dist/prompts.js +104 -0
- package/dist/resources.d.ts +13 -0
- package/dist/resources.js +64 -0
- package/dist/tools/batch.d.ts +14 -0
- package/dist/tools/batch.js +49 -0
- package/dist/tools/blocks.d.ts +4 -0
- package/dist/tools/blocks.js +202 -0
- package/dist/tools/comments.d.ts +4 -0
- package/dist/tools/comments.js +80 -0
- package/dist/tools/cpt.d.ts +16 -0
- package/dist/tools/cpt.js +97 -0
- package/dist/tools/jwt.d.ts +9 -0
- package/dist/tools/jwt.js +17 -0
- package/dist/tools/media.d.ts +4 -0
- package/dist/tools/media.js +101 -0
- package/dist/tools/multisite.d.ts +17 -0
- package/dist/tools/multisite.js +111 -0
- package/dist/tools/pages.d.ts +4 -0
- package/dist/tools/pages.js +101 -0
- package/dist/tools/posts.d.ts +4 -0
- package/dist/tools/posts.js +160 -0
- package/dist/tools/seo.d.ts +4 -0
- package/dist/tools/seo.js +269 -0
- package/dist/tools/site.d.ts +4 -0
- package/dist/tools/site.js +96 -0
- package/dist/tools/taxonomy.d.ts +4 -0
- package/dist/tools/taxonomy.js +147 -0
- package/dist/tools/users.d.ts +4 -0
- package/dist/tools/users.js +99 -0
- package/dist/tools/woocommerce.d.ts +4 -0
- package/dist/tools/woocommerce.js +400 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.js +2 -0
- package/dist/wordpress-client.d.ts +223 -0
- package/dist/wordpress-client.js +519 -0
- package/package.json +67 -0
package/dist/prompts.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Reusable prompt templates that turn common WordPress workflows into one-click
|
|
4
|
+
* actions for the LLM client.
|
|
5
|
+
*/
|
|
6
|
+
export function registerPrompts(server) {
|
|
7
|
+
server.registerPrompt("summarize_post", {
|
|
8
|
+
title: "Summarize a post",
|
|
9
|
+
description: "Generate a concise summary of a WordPress post.",
|
|
10
|
+
argsSchema: {
|
|
11
|
+
post_id: z.string().describe("Post ID to summarize"),
|
|
12
|
+
length: z.enum(["short", "medium", "long"]).optional(),
|
|
13
|
+
},
|
|
14
|
+
}, ({ post_id, length = "medium" }) => ({
|
|
15
|
+
messages: [
|
|
16
|
+
{
|
|
17
|
+
role: "user",
|
|
18
|
+
content: {
|
|
19
|
+
type: "text",
|
|
20
|
+
text: `Read the post at resource wp://post/${post_id} and write a ${length} summary. ` +
|
|
21
|
+
`Cover: main thesis, key points, target audience. Use plain prose.`,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
}));
|
|
26
|
+
server.registerPrompt("seo_rewrite", {
|
|
27
|
+
title: "SEO rewrite",
|
|
28
|
+
description: "Rewrite a post for SEO without changing the substance.",
|
|
29
|
+
argsSchema: {
|
|
30
|
+
post_id: z.string(),
|
|
31
|
+
primary_keyword: z.string(),
|
|
32
|
+
target_audience: z.string().optional(),
|
|
33
|
+
},
|
|
34
|
+
}, ({ post_id, primary_keyword, target_audience }) => ({
|
|
35
|
+
messages: [
|
|
36
|
+
{
|
|
37
|
+
role: "user",
|
|
38
|
+
content: {
|
|
39
|
+
type: "text",
|
|
40
|
+
text: `Rewrite the post at resource wp://post/${post_id} for SEO.\n` +
|
|
41
|
+
`Primary keyword: "${primary_keyword}".\n` +
|
|
42
|
+
(target_audience ? `Target audience: ${target_audience}.\n` : "") +
|
|
43
|
+
`Constraints:\n` +
|
|
44
|
+
`- Preserve facts and intent.\n` +
|
|
45
|
+
`- Use the keyword in title, first paragraph, and one H2.\n` +
|
|
46
|
+
`- Add a meta description ≤ 155 chars.\n` +
|
|
47
|
+
`- Output JSON: { title, content (HTML), excerpt, meta_description, suggested_slug }.\n` +
|
|
48
|
+
`Then call wp_update_post to apply the changes (status stays the same).`,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
}));
|
|
53
|
+
server.registerPrompt("translate_page", {
|
|
54
|
+
title: "Translate a page",
|
|
55
|
+
description: "Translate a page into another language while preserving HTML structure.",
|
|
56
|
+
argsSchema: {
|
|
57
|
+
page_id: z.string(),
|
|
58
|
+
target_language: z.string().describe("e.g. 'Indonesian', 'fr-FR'"),
|
|
59
|
+
create_new: z
|
|
60
|
+
.enum(["true", "false"])
|
|
61
|
+
.optional()
|
|
62
|
+
.describe("If 'true', create a new page instead of updating in place."),
|
|
63
|
+
},
|
|
64
|
+
}, ({ page_id, target_language, create_new }) => ({
|
|
65
|
+
messages: [
|
|
66
|
+
{
|
|
67
|
+
role: "user",
|
|
68
|
+
content: {
|
|
69
|
+
type: "text",
|
|
70
|
+
text: `Translate the page at resource wp://page/${page_id} into ${target_language}. ` +
|
|
71
|
+
`Preserve all HTML tags, attributes, and shortcodes. Translate alt text and titles. ` +
|
|
72
|
+
(create_new === "true"
|
|
73
|
+
? `Then call wp_create_page (status=draft) with the translated content.`
|
|
74
|
+
: `Then call wp_update_page on the same id with the translated content.`),
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
}));
|
|
79
|
+
server.registerPrompt("draft_post", {
|
|
80
|
+
title: "Draft a new post",
|
|
81
|
+
description: "Outline and draft a new blog post in WordPress (status=draft).",
|
|
82
|
+
argsSchema: {
|
|
83
|
+
topic: z.string(),
|
|
84
|
+
tone: z.string().optional(),
|
|
85
|
+
word_count: z.string().optional(),
|
|
86
|
+
},
|
|
87
|
+
}, ({ topic, tone = "informative", word_count = "800" }) => ({
|
|
88
|
+
messages: [
|
|
89
|
+
{
|
|
90
|
+
role: "user",
|
|
91
|
+
content: {
|
|
92
|
+
type: "text",
|
|
93
|
+
text: `Draft a WordPress post about: ${topic}.\n` +
|
|
94
|
+
`Tone: ${tone}. Target length: ~${word_count} words.\n` +
|
|
95
|
+
`Steps:\n` +
|
|
96
|
+
`1. Use wp_get_categories and wp_get_tags to find relevant taxonomies.\n` +
|
|
97
|
+
`2. Produce title, excerpt, slug, body (HTML with H2/H3 + paragraphs).\n` +
|
|
98
|
+
`3. Call wp_create_post with status=draft. Return the new post id.`,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=prompts.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { WordPressClient } from "./wordpress-client.js";
|
|
3
|
+
/**
|
|
4
|
+
* Register MCP resources so clients can attach WordPress content as context
|
|
5
|
+
* without burning a tool call. URIs:
|
|
6
|
+
*
|
|
7
|
+
* wp://site → REST root summary
|
|
8
|
+
* wp://post/{id} → single post (rendered HTML + meta)
|
|
9
|
+
* wp://page/{id} → single page
|
|
10
|
+
* wp://media/{id} → media metadata + source_url
|
|
11
|
+
*/
|
|
12
|
+
export declare function registerResources(server: McpServer, wp: WordPressClient): void;
|
|
13
|
+
//# sourceMappingURL=resources.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { ResourceTemplate, } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
/**
|
|
3
|
+
* Register MCP resources so clients can attach WordPress content as context
|
|
4
|
+
* without burning a tool call. URIs:
|
|
5
|
+
*
|
|
6
|
+
* wp://site → REST root summary
|
|
7
|
+
* wp://post/{id} → single post (rendered HTML + meta)
|
|
8
|
+
* wp://page/{id} → single page
|
|
9
|
+
* wp://media/{id} → media metadata + source_url
|
|
10
|
+
*/
|
|
11
|
+
export function registerResources(server, wp) {
|
|
12
|
+
server.registerResource("site", "wp://site", {
|
|
13
|
+
title: "WordPress site info",
|
|
14
|
+
description: "Site name, description, and discovered REST namespaces.",
|
|
15
|
+
mimeType: "application/json",
|
|
16
|
+
}, async (uri) => {
|
|
17
|
+
const data = await wp.siteInfo();
|
|
18
|
+
return {
|
|
19
|
+
contents: [
|
|
20
|
+
{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(data, null, 2) },
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
server.registerResource("post", new ResourceTemplate("wp://post/{id}", { list: undefined }), {
|
|
25
|
+
title: "WordPress post",
|
|
26
|
+
description: "Single post by ID with rendered content.",
|
|
27
|
+
mimeType: "application/json",
|
|
28
|
+
}, async (uri, vars) => {
|
|
29
|
+
const id = Number(vars.id);
|
|
30
|
+
const data = await wp.posts.get(id, { context: "view", _embed: true });
|
|
31
|
+
return {
|
|
32
|
+
contents: [
|
|
33
|
+
{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(data, null, 2) },
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
server.registerResource("page", new ResourceTemplate("wp://page/{id}", { list: undefined }), {
|
|
38
|
+
title: "WordPress page",
|
|
39
|
+
description: "Single page by ID with rendered content.",
|
|
40
|
+
mimeType: "application/json",
|
|
41
|
+
}, async (uri, vars) => {
|
|
42
|
+
const id = Number(vars.id);
|
|
43
|
+
const data = await wp.pages.get(id, { context: "view", _embed: true });
|
|
44
|
+
return {
|
|
45
|
+
contents: [
|
|
46
|
+
{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(data, null, 2) },
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
server.registerResource("media", new ResourceTemplate("wp://media/{id}", { list: undefined }), {
|
|
51
|
+
title: "WordPress media item",
|
|
52
|
+
description: "Single media item metadata, including source URL.",
|
|
53
|
+
mimeType: "application/json",
|
|
54
|
+
}, async (uri, vars) => {
|
|
55
|
+
const id = Number(vars.id);
|
|
56
|
+
const data = await wp.media.get(id);
|
|
57
|
+
return {
|
|
58
|
+
contents: [
|
|
59
|
+
{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(data, null, 2) },
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=resources.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { WordPressClient } from "../wordpress-client.js";
|
|
2
|
+
import type { ToolDef } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Tools for the WP 5.6+ batch endpoint (/batch/v1).
|
|
5
|
+
*
|
|
6
|
+
* Notes:
|
|
7
|
+
* - GET requests are NOT supported by core; only POST/PUT/PATCH/DELETE.
|
|
8
|
+
* - Default max 25 requests per batch (filterable server-side).
|
|
9
|
+
* - Response is HTTP 207 with parallel `responses` array, same order as input.
|
|
10
|
+
* - The route being batched must declare `'allow_batch' => array('v1' => true)`.
|
|
11
|
+
* Most built-in routes do; some plugins do not yet.
|
|
12
|
+
*/
|
|
13
|
+
export declare const batchTools: (wp: WordPressClient) => ToolDef[];
|
|
14
|
+
//# sourceMappingURL=batch.d.ts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Tools for the WP 5.6+ batch endpoint (/batch/v1).
|
|
4
|
+
*
|
|
5
|
+
* Notes:
|
|
6
|
+
* - GET requests are NOT supported by core; only POST/PUT/PATCH/DELETE.
|
|
7
|
+
* - Default max 25 requests per batch (filterable server-side).
|
|
8
|
+
* - Response is HTTP 207 with parallel `responses` array, same order as input.
|
|
9
|
+
* - The route being batched must declare `'allow_batch' => array('v1' => true)`.
|
|
10
|
+
* Most built-in routes do; some plugins do not yet.
|
|
11
|
+
*/
|
|
12
|
+
export const batchTools = (wp) => [
|
|
13
|
+
{
|
|
14
|
+
name: "wp_batch_options",
|
|
15
|
+
title: "Batch: discover capabilities",
|
|
16
|
+
description: "OPTIONS /batch/v1 — returns the maximum requests per batch and accepted methods.",
|
|
17
|
+
inputSchema: {},
|
|
18
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
19
|
+
handler: async () => wp.batchOptions(),
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: "wp_batch",
|
|
23
|
+
title: "Batch: execute write operations in one round-trip",
|
|
24
|
+
description: "POST /batch/v1 — execute multiple write operations in a single request. " +
|
|
25
|
+
"GET is not supported. Use validation='require-all-validate' to abort the whole batch if any request is invalid.",
|
|
26
|
+
inputSchema: {
|
|
27
|
+
validation: z.enum(["normal", "require-all-validate"]).optional(),
|
|
28
|
+
requests: z
|
|
29
|
+
.array(z.object({
|
|
30
|
+
method: z.enum(["POST", "PUT", "PATCH", "DELETE"]).optional(),
|
|
31
|
+
path: z
|
|
32
|
+
.string()
|
|
33
|
+
.min(1)
|
|
34
|
+
.describe("Route path beginning with /, e.g. '/wp/v2/posts' or '/wp/v2/posts/123'"),
|
|
35
|
+
headers: z.record(z.string(), z.union([z.string(), z.array(z.string())])).optional(),
|
|
36
|
+
body: z.record(z.string(), z.unknown()).optional(),
|
|
37
|
+
}))
|
|
38
|
+
.min(1)
|
|
39
|
+
.max(100)
|
|
40
|
+
.describe("Up to ~25 by default; some sites raise the cap. Check wp_batch_options."),
|
|
41
|
+
},
|
|
42
|
+
annotations: { openWorldHint: true },
|
|
43
|
+
handler: async (input) => {
|
|
44
|
+
const { requests, validation = "normal" } = input;
|
|
45
|
+
return wp.batch(requests, validation);
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
//# sourceMappingURL=batch.js.map
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Tools for WordPress block themes (WP 5.9+):
|
|
4
|
+
* - wp_template (full templates)
|
|
5
|
+
* - wp_template_part (header / footer / etc.)
|
|
6
|
+
* - block-patterns / block-pattern-categories
|
|
7
|
+
* - block-types
|
|
8
|
+
* - global-styles
|
|
9
|
+
* - menus / menu-items / menu-locations (WP 6.6+ menus REST in core; many sites
|
|
10
|
+
* also have Gutenberg's nav blocks via wp/v2/navigation)
|
|
11
|
+
*
|
|
12
|
+
* Templates use string IDs of the form `theme//slug`, e.g. `twentytwentyfour//single`.
|
|
13
|
+
*/
|
|
14
|
+
const orderEnum = z.enum(["asc", "desc"]);
|
|
15
|
+
export const blockTools = (wp) => [
|
|
16
|
+
// ── Templates (full block templates) ───────────────────────────────────
|
|
17
|
+
{
|
|
18
|
+
name: "wp_list_templates",
|
|
19
|
+
title: "Block themes: list templates",
|
|
20
|
+
description: "List block theme templates (wp_template). IDs look like 'theme//slug'.",
|
|
21
|
+
inputSchema: {
|
|
22
|
+
theme: z.string().optional().describe("Filter by theme slug"),
|
|
23
|
+
area: z.string().optional().describe("Filter by template area (e.g. 'header')"),
|
|
24
|
+
post_type: z.string().optional().describe("Filter by post type the template applies to"),
|
|
25
|
+
_fields: z.string().optional(),
|
|
26
|
+
},
|
|
27
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
28
|
+
handler: async (input) => {
|
|
29
|
+
const r = await wp.list("wp/v2/templates", input);
|
|
30
|
+
return { templates: r.data, total: r.total };
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "wp_get_template",
|
|
35
|
+
title: "Block themes: get template",
|
|
36
|
+
description: "Get a single template by 'theme//slug' ID.",
|
|
37
|
+
inputSchema: { id: z.string().min(1) },
|
|
38
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
39
|
+
handler: async (input) => {
|
|
40
|
+
const { id } = input;
|
|
41
|
+
return wp.raw({
|
|
42
|
+
method: "GET",
|
|
43
|
+
path: `/wp/v2/templates/${encodeURIComponent(id)}`,
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "wp_update_template",
|
|
49
|
+
title: "Block themes: update template",
|
|
50
|
+
description: "Update a block template by 'theme//slug' ID. Pass content (block markup) " +
|
|
51
|
+
"and/or title/description.",
|
|
52
|
+
inputSchema: {
|
|
53
|
+
id: z.string().min(1),
|
|
54
|
+
content: z.string().optional(),
|
|
55
|
+
title: z.string().optional(),
|
|
56
|
+
description: z.string().optional(),
|
|
57
|
+
status: z.enum(["publish", "draft", "auto-draft", "trash"]).optional(),
|
|
58
|
+
},
|
|
59
|
+
annotations: { openWorldHint: true },
|
|
60
|
+
handler: async (input) => {
|
|
61
|
+
const { id, ...body } = input;
|
|
62
|
+
return wp.raw({
|
|
63
|
+
method: "POST",
|
|
64
|
+
path: `/wp/v2/templates/${encodeURIComponent(id)}`,
|
|
65
|
+
body,
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
// ── Template parts ─────────────────────────────────────────────────────
|
|
70
|
+
{
|
|
71
|
+
name: "wp_list_template_parts",
|
|
72
|
+
title: "Block themes: list template parts",
|
|
73
|
+
description: "List template parts (wp_template_part), e.g. header / footer fragments.",
|
|
74
|
+
inputSchema: {
|
|
75
|
+
theme: z.string().optional(),
|
|
76
|
+
area: z.string().optional(),
|
|
77
|
+
_fields: z.string().optional(),
|
|
78
|
+
},
|
|
79
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
80
|
+
handler: async (input) => {
|
|
81
|
+
const r = await wp.list("wp/v2/template-parts", input);
|
|
82
|
+
return { template_parts: r.data, total: r.total };
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "wp_get_template_part",
|
|
87
|
+
title: "Block themes: get template part",
|
|
88
|
+
description: "Get a single template part by 'theme//slug' ID.",
|
|
89
|
+
inputSchema: { id: z.string().min(1) },
|
|
90
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
91
|
+
handler: async (input) => {
|
|
92
|
+
const { id } = input;
|
|
93
|
+
return wp.raw({
|
|
94
|
+
method: "GET",
|
|
95
|
+
path: `/wp/v2/template-parts/${encodeURIComponent(id)}`,
|
|
96
|
+
});
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: "wp_update_template_part",
|
|
101
|
+
title: "Block themes: update template part",
|
|
102
|
+
description: "Update a template part by 'theme//slug' ID.",
|
|
103
|
+
inputSchema: {
|
|
104
|
+
id: z.string().min(1),
|
|
105
|
+
content: z.string().optional(),
|
|
106
|
+
title: z.string().optional(),
|
|
107
|
+
description: z.string().optional(),
|
|
108
|
+
area: z.string().optional(),
|
|
109
|
+
},
|
|
110
|
+
annotations: { openWorldHint: true },
|
|
111
|
+
handler: async (input) => {
|
|
112
|
+
const { id, ...body } = input;
|
|
113
|
+
return wp.raw({
|
|
114
|
+
method: "POST",
|
|
115
|
+
path: `/wp/v2/template-parts/${encodeURIComponent(id)}`,
|
|
116
|
+
body,
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
// ── Block patterns ─────────────────────────────────────────────────────
|
|
121
|
+
{
|
|
122
|
+
name: "wp_list_block_patterns",
|
|
123
|
+
title: "Block themes: list patterns",
|
|
124
|
+
description: "List block patterns (registered via PHP, theme.json, or pattern directory).",
|
|
125
|
+
inputSchema: {},
|
|
126
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
127
|
+
handler: async () => {
|
|
128
|
+
const r = await wp.list("wp/v2/block-patterns/patterns");
|
|
129
|
+
return { patterns: r.data, total: r.total };
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "wp_list_block_pattern_categories",
|
|
134
|
+
title: "Block themes: list pattern categories",
|
|
135
|
+
description: "List the categories used to group block patterns.",
|
|
136
|
+
inputSchema: {},
|
|
137
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
138
|
+
handler: async () => {
|
|
139
|
+
const r = await wp.list("wp/v2/block-patterns/categories");
|
|
140
|
+
return { categories: r.data, total: r.total };
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
// ── Block types ────────────────────────────────────────────────────────
|
|
144
|
+
{
|
|
145
|
+
name: "wp_list_block_types",
|
|
146
|
+
title: "Block themes: list block types",
|
|
147
|
+
description: "List registered Gutenberg block types.",
|
|
148
|
+
inputSchema: {
|
|
149
|
+
namespace: z.string().optional().describe("Filter by block namespace, e.g. 'core'"),
|
|
150
|
+
},
|
|
151
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
152
|
+
handler: async (input) => {
|
|
153
|
+
const r = await wp.list("wp/v2/block-types", input);
|
|
154
|
+
return { block_types: r.data, total: r.total };
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
// ── Global styles ──────────────────────────────────────────────────────
|
|
158
|
+
{
|
|
159
|
+
name: "wp_get_global_styles",
|
|
160
|
+
title: "Block themes: get global styles for a theme",
|
|
161
|
+
description: "Get the active global styles record (theme.json overrides) for a given stylesheet.",
|
|
162
|
+
inputSchema: {
|
|
163
|
+
stylesheet: z.string().describe("Theme stylesheet slug, e.g. 'twentytwentyfour'"),
|
|
164
|
+
},
|
|
165
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
166
|
+
handler: async (input) => {
|
|
167
|
+
const { stylesheet } = input;
|
|
168
|
+
return wp.raw({
|
|
169
|
+
method: "GET",
|
|
170
|
+
path: `/wp/v2/global-styles/themes/${encodeURIComponent(stylesheet)}`,
|
|
171
|
+
});
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
// ── Menus (WP 5.9+ classic menus REST) ─────────────────────────────────
|
|
175
|
+
{
|
|
176
|
+
name: "wp_list_menus",
|
|
177
|
+
title: "Menus: list",
|
|
178
|
+
description: "List navigation menus.",
|
|
179
|
+
inputSchema: { _fields: z.string().optional() },
|
|
180
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
181
|
+
handler: async (input) => {
|
|
182
|
+
const r = await wp.list("wp/v2/menus", input);
|
|
183
|
+
return { menus: r.data, total: r.total };
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: "wp_list_menu_items",
|
|
188
|
+
title: "Menus: list items",
|
|
189
|
+
description: "List items inside menus, optionally filtered by menu ID.",
|
|
190
|
+
inputSchema: {
|
|
191
|
+
menus: z.array(z.number().int()).optional(),
|
|
192
|
+
per_page: z.number().int().min(1).max(100).optional(),
|
|
193
|
+
_fields: z.string().optional(),
|
|
194
|
+
},
|
|
195
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
196
|
+
handler: async (input) => {
|
|
197
|
+
const r = await wp.list("wp/v2/menu-items", input);
|
|
198
|
+
return { menu_items: r.data, total: r.total };
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
];
|
|
202
|
+
//# sourceMappingURL=blocks.js.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const commentTools = (wp) => [
|
|
3
|
+
{
|
|
4
|
+
name: "wp_get_comments",
|
|
5
|
+
title: "List comments",
|
|
6
|
+
description: "List WordPress comments with filters.",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
page: z.number().int().positive().optional(),
|
|
9
|
+
per_page: z.number().int().min(1).max(100).optional(),
|
|
10
|
+
search: z.string().optional(),
|
|
11
|
+
post: z.number().int().optional(),
|
|
12
|
+
status: z.enum(["approved", "hold", "spam", "trash", "any"]).optional(),
|
|
13
|
+
author_email: z.string().email().optional(),
|
|
14
|
+
after: z.string().optional(),
|
|
15
|
+
before: z.string().optional(),
|
|
16
|
+
orderby: z.enum(["date", "id", "post"]).optional(),
|
|
17
|
+
order: z.enum(["asc", "desc"]).optional(),
|
|
18
|
+
_fields: z.string().optional(),
|
|
19
|
+
},
|
|
20
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
21
|
+
handler: async (input) => {
|
|
22
|
+
const result = await wp.comments.list(input);
|
|
23
|
+
return { comments: result.data, total: result.total };
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: "wp_get_comment",
|
|
28
|
+
title: "Get a comment",
|
|
29
|
+
description: "Get a single comment by ID.",
|
|
30
|
+
inputSchema: { id: z.number().int().positive() },
|
|
31
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
32
|
+
handler: async (input) => wp.comments.get(input.id),
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "wp_create_comment",
|
|
36
|
+
title: "Create a comment",
|
|
37
|
+
description: "Create a comment on a post.",
|
|
38
|
+
inputSchema: {
|
|
39
|
+
post: z.number().int().positive(),
|
|
40
|
+
content: z.string().min(1),
|
|
41
|
+
author_name: z.string().optional(),
|
|
42
|
+
author_email: z.string().email().optional(),
|
|
43
|
+
parent: z.number().int().optional(),
|
|
44
|
+
},
|
|
45
|
+
annotations: { openWorldHint: true },
|
|
46
|
+
handler: async (input) => wp.comments.create(input),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "wp_update_comment",
|
|
50
|
+
title: "Update / moderate a comment",
|
|
51
|
+
description: "Update or moderate a comment. Use status to approve / hold / spam / trash.",
|
|
52
|
+
inputSchema: {
|
|
53
|
+
id: z.number().int().positive(),
|
|
54
|
+
content: z.string().optional(),
|
|
55
|
+
status: z.enum(["approved", "hold", "spam", "trash"]).optional(),
|
|
56
|
+
author_name: z.string().optional(),
|
|
57
|
+
author_email: z.string().email().optional(),
|
|
58
|
+
},
|
|
59
|
+
annotations: { openWorldHint: true },
|
|
60
|
+
handler: async (input) => {
|
|
61
|
+
const { id, ...data } = input;
|
|
62
|
+
return wp.comments.update(id, data);
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: "wp_delete_comment",
|
|
67
|
+
title: "Delete a comment",
|
|
68
|
+
description: "Move a comment to trash, or permanently delete with force=true.",
|
|
69
|
+
inputSchema: {
|
|
70
|
+
id: z.number().int().positive(),
|
|
71
|
+
force: z.boolean().optional(),
|
|
72
|
+
},
|
|
73
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
74
|
+
handler: async (input) => {
|
|
75
|
+
const { id, force = false } = input;
|
|
76
|
+
return wp.comments.remove(id, force);
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
//# sourceMappingURL=comments.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { WordPressClient } from "../wordpress-client.js";
|
|
2
|
+
import type { ToolDef } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Generic tools for working with custom post types and custom taxonomies.
|
|
5
|
+
*
|
|
6
|
+
* `route` examples:
|
|
7
|
+
* - "wp/v2/product" (a CPT named `product` registered with show_in_rest)
|
|
8
|
+
* - "wp/v2/product_cat" (a custom taxonomy)
|
|
9
|
+
* - "wc/v3/products" (WooCommerce — different namespace)
|
|
10
|
+
* - "yoast/v1/get_head" (Yoast)
|
|
11
|
+
*
|
|
12
|
+
* Use `wp_get_post_types` / `wp_get_taxonomies` / `wp_site_info` first to discover
|
|
13
|
+
* what's available on the target site.
|
|
14
|
+
*/
|
|
15
|
+
export declare const cptTools: (wp: WordPressClient) => ToolDef[];
|
|
16
|
+
//# sourceMappingURL=cpt.d.ts.map
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Generic tools for working with custom post types and custom taxonomies.
|
|
4
|
+
*
|
|
5
|
+
* `route` examples:
|
|
6
|
+
* - "wp/v2/product" (a CPT named `product` registered with show_in_rest)
|
|
7
|
+
* - "wp/v2/product_cat" (a custom taxonomy)
|
|
8
|
+
* - "wc/v3/products" (WooCommerce — different namespace)
|
|
9
|
+
* - "yoast/v1/get_head" (Yoast)
|
|
10
|
+
*
|
|
11
|
+
* Use `wp_get_post_types` / `wp_get_taxonomies` / `wp_site_info` first to discover
|
|
12
|
+
* what's available on the target site.
|
|
13
|
+
*/
|
|
14
|
+
export const cptTools = (wp) => [
|
|
15
|
+
{
|
|
16
|
+
name: "wp_list_items",
|
|
17
|
+
title: "Generic list (any REST route)",
|
|
18
|
+
description: "List items from any REST collection route (CPT, custom taxonomy, plugin namespace). " +
|
|
19
|
+
"Pass plugin/CPT-specific params via `query`.",
|
|
20
|
+
inputSchema: {
|
|
21
|
+
route: z
|
|
22
|
+
.string()
|
|
23
|
+
.min(1)
|
|
24
|
+
.describe("REST route, e.g. 'wp/v2/product' or 'wc/v3/products'"),
|
|
25
|
+
query: z
|
|
26
|
+
.record(z.string(), z.unknown())
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Query string parameters as key/value pairs"),
|
|
29
|
+
},
|
|
30
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
31
|
+
handler: async (input) => {
|
|
32
|
+
const { route, query } = input;
|
|
33
|
+
const result = await wp.list(route, query);
|
|
34
|
+
return { items: result.data, total: result.total, total_pages: result.pages };
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "wp_get_item",
|
|
39
|
+
title: "Generic get (any REST route)",
|
|
40
|
+
description: "Get a single item from any REST route by ID.",
|
|
41
|
+
inputSchema: {
|
|
42
|
+
route: z.string().min(1),
|
|
43
|
+
id: z.number().int().positive(),
|
|
44
|
+
query: z.record(z.string(), z.unknown()).optional(),
|
|
45
|
+
},
|
|
46
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
47
|
+
handler: async (input) => {
|
|
48
|
+
const { route, id, query } = input;
|
|
49
|
+
return wp.get(route, id, query);
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "wp_create_item",
|
|
54
|
+
title: "Generic create (any REST route)",
|
|
55
|
+
description: "Create an item on any REST route. Use `wp_get_post_types` to find supported CPT slugs.",
|
|
56
|
+
inputSchema: {
|
|
57
|
+
route: z.string().min(1),
|
|
58
|
+
body: z.record(z.string(), z.unknown()),
|
|
59
|
+
},
|
|
60
|
+
annotations: { openWorldHint: true },
|
|
61
|
+
handler: async (input) => {
|
|
62
|
+
const { route, body } = input;
|
|
63
|
+
return wp.create(route, body);
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: "wp_update_item",
|
|
68
|
+
title: "Generic update (any REST route)",
|
|
69
|
+
description: "Update an item on any REST route by ID.",
|
|
70
|
+
inputSchema: {
|
|
71
|
+
route: z.string().min(1),
|
|
72
|
+
id: z.number().int().positive(),
|
|
73
|
+
body: z.record(z.string(), z.unknown()),
|
|
74
|
+
},
|
|
75
|
+
annotations: { openWorldHint: true },
|
|
76
|
+
handler: async (input) => {
|
|
77
|
+
const { route, id, body } = input;
|
|
78
|
+
return wp.update(route, id, body);
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "wp_delete_item",
|
|
83
|
+
title: "Generic delete (any REST route)",
|
|
84
|
+
description: "Delete an item on any REST route. Many endpoints require force=true.",
|
|
85
|
+
inputSchema: {
|
|
86
|
+
route: z.string().min(1),
|
|
87
|
+
id: z.number().int().positive(),
|
|
88
|
+
force: z.boolean().optional(),
|
|
89
|
+
},
|
|
90
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
91
|
+
handler: async (input) => {
|
|
92
|
+
const { route, id, force = false } = input;
|
|
93
|
+
return wp.remove(route, id, force);
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
//# sourceMappingURL=cpt.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { WordPressClient } from "../wordpress-client.js";
|
|
2
|
+
import type { ToolDef } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* JWT auth helper tools (only useful when WP_AUTH_MODE=jwt).
|
|
5
|
+
* Most users won't need to call these directly — the server fetches a token
|
|
6
|
+
* automatically at startup. They're handy for diagnostics.
|
|
7
|
+
*/
|
|
8
|
+
export declare const jwtTools: (wp: WordPressClient) => ToolDef[];
|
|
9
|
+
//# sourceMappingURL=jwt.d.ts.map
|