@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.
Files changed (45) hide show
  1. package/.env.example +32 -0
  2. package/CHANGELOG.md +23 -0
  3. package/LICENSE +21 -0
  4. package/README.md +256 -0
  5. package/dist/config.d.ts +80 -0
  6. package/dist/config.js +84 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.js +145 -0
  9. package/dist/prompts.d.ts +7 -0
  10. package/dist/prompts.js +104 -0
  11. package/dist/resources.d.ts +13 -0
  12. package/dist/resources.js +64 -0
  13. package/dist/tools/batch.d.ts +14 -0
  14. package/dist/tools/batch.js +49 -0
  15. package/dist/tools/blocks.d.ts +4 -0
  16. package/dist/tools/blocks.js +202 -0
  17. package/dist/tools/comments.d.ts +4 -0
  18. package/dist/tools/comments.js +80 -0
  19. package/dist/tools/cpt.d.ts +16 -0
  20. package/dist/tools/cpt.js +97 -0
  21. package/dist/tools/jwt.d.ts +9 -0
  22. package/dist/tools/jwt.js +17 -0
  23. package/dist/tools/media.d.ts +4 -0
  24. package/dist/tools/media.js +101 -0
  25. package/dist/tools/multisite.d.ts +17 -0
  26. package/dist/tools/multisite.js +111 -0
  27. package/dist/tools/pages.d.ts +4 -0
  28. package/dist/tools/pages.js +101 -0
  29. package/dist/tools/posts.d.ts +4 -0
  30. package/dist/tools/posts.js +160 -0
  31. package/dist/tools/seo.d.ts +4 -0
  32. package/dist/tools/seo.js +269 -0
  33. package/dist/tools/site.d.ts +4 -0
  34. package/dist/tools/site.js +96 -0
  35. package/dist/tools/taxonomy.d.ts +4 -0
  36. package/dist/tools/taxonomy.js +147 -0
  37. package/dist/tools/users.d.ts +4 -0
  38. package/dist/tools/users.js +99 -0
  39. package/dist/tools/woocommerce.d.ts +4 -0
  40. package/dist/tools/woocommerce.js +400 -0
  41. package/dist/types.d.ts +26 -0
  42. package/dist/types.js +2 -0
  43. package/dist/wordpress-client.d.ts +223 -0
  44. package/dist/wordpress-client.js +519 -0
  45. package/package.json +67 -0
@@ -0,0 +1,17 @@
1
+ /**
2
+ * JWT auth helper tools (only useful when WP_AUTH_MODE=jwt).
3
+ * Most users won't need to call these directly — the server fetches a token
4
+ * automatically at startup. They're handy for diagnostics.
5
+ */
6
+ export const jwtTools = (wp) => [
7
+ {
8
+ name: "wp_jwt_validate",
9
+ title: "JWT: validate current token",
10
+ description: "POST /jwt-auth/v1/token/validate — confirms the JWT in use is valid. " +
11
+ "Only useful in JWT auth mode.",
12
+ inputSchema: {},
13
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
14
+ handler: async () => wp.validateJwt(),
15
+ },
16
+ ];
17
+ //# sourceMappingURL=jwt.js.map
@@ -0,0 +1,4 @@
1
+ import { WordPressClient } from "../wordpress-client.js";
2
+ import type { ToolDef } from "../types.js";
3
+ export declare const mediaTools: (wp: WordPressClient) => ToolDef[];
4
+ //# sourceMappingURL=media.d.ts.map
@@ -0,0 +1,101 @@
1
+ import { z } from "zod";
2
+ export const mediaTools = (wp) => [
3
+ {
4
+ name: "wp_get_media",
5
+ title: "List media",
6
+ description: "List WordPress media items (images, videos, files).",
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
+ media_type: z.enum(["image", "video", "audio", "application"]).optional(),
12
+ mime_type: z.string().optional(),
13
+ parent: z.number().int().optional(),
14
+ orderby: z.enum(["date", "id", "title", "modified", "slug"]).optional(),
15
+ order: z.enum(["asc", "desc"]).optional(),
16
+ _fields: z.string().optional(),
17
+ },
18
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
19
+ handler: async (input) => {
20
+ const result = await wp.media.list(input);
21
+ return { media: result.data, total: result.total, total_pages: result.pages };
22
+ },
23
+ },
24
+ {
25
+ name: "wp_get_media_item",
26
+ title: "Get a media item",
27
+ description: "Get a single media item by ID.",
28
+ inputSchema: { id: z.number().int().positive() },
29
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
30
+ handler: async (input) => wp.media.get(input.id),
31
+ },
32
+ {
33
+ name: "wp_upload_media_file",
34
+ title: "Upload media from local file",
35
+ description: "Upload a media file from an absolute local path.",
36
+ inputSchema: {
37
+ file_path: z.string().min(1),
38
+ title: z.string().optional(),
39
+ alt_text: z.string().optional(),
40
+ caption: z.string().optional(),
41
+ description: z.string().optional(),
42
+ post: z.number().int().optional(),
43
+ },
44
+ annotations: { openWorldHint: true },
45
+ handler: async (input) => {
46
+ const { file_path, ...metadata } = input;
47
+ return wp.uploadMediaFromFile(file_path, metadata);
48
+ },
49
+ },
50
+ {
51
+ name: "wp_upload_media_url",
52
+ title: "Upload media from URL",
53
+ description: "Download a remote file and upload it to WordPress.",
54
+ inputSchema: {
55
+ url: z.string().url(),
56
+ title: z.string().optional(),
57
+ alt_text: z.string().optional(),
58
+ caption: z.string().optional(),
59
+ description: z.string().optional(),
60
+ post: z.number().int().optional(),
61
+ },
62
+ annotations: { openWorldHint: true },
63
+ handler: async (input) => {
64
+ const { url, ...metadata } = input;
65
+ return wp.uploadMediaFromUrl(url, metadata);
66
+ },
67
+ },
68
+ {
69
+ name: "wp_update_media",
70
+ title: "Update media metadata",
71
+ description: "Update metadata of an existing media item.",
72
+ inputSchema: {
73
+ id: z.number().int().positive(),
74
+ title: z.string().optional(),
75
+ alt_text: z.string().optional(),
76
+ caption: z.string().optional(),
77
+ description: z.string().optional(),
78
+ post: z.number().int().optional(),
79
+ },
80
+ annotations: { openWorldHint: true },
81
+ handler: async (input) => {
82
+ const { id, ...data } = input;
83
+ return wp.media.update(id, data);
84
+ },
85
+ },
86
+ {
87
+ name: "wp_delete_media",
88
+ title: "Delete media",
89
+ description: "Permanently delete a media item. WordPress does not trash media; this is irreversible.",
90
+ inputSchema: {
91
+ id: z.number().int().positive(),
92
+ force: z.boolean().optional(),
93
+ },
94
+ annotations: { destructiveHint: true, openWorldHint: true },
95
+ handler: async (input) => {
96
+ const { id, force = true } = input;
97
+ return wp.media.remove(id, force);
98
+ },
99
+ },
100
+ ];
101
+ //# sourceMappingURL=media.js.map
@@ -0,0 +1,17 @@
1
+ import { WordPressClient } from "../wordpress-client.js";
2
+ import type { ToolDef } from "../types.js";
3
+ /**
4
+ * Multisite tools.
5
+ *
6
+ * Important: as of WordPress 6.x, **core does NOT yet ship a `/wp/v2/sites`
7
+ * endpoint** — it is on the multisite roadmap but unreleased. The tools below
8
+ * call `wp/v2/sites` (and `wp/v2/networks` where useful) on the assumption
9
+ * that a Multisite REST plugin (e.g. brettkrueger/multisite-rest-api) is
10
+ * installed. If you get a 404 with code `rest_no_route`, the plugin is
11
+ * missing or your build of WordPress doesn't expose the endpoint.
12
+ *
13
+ * Fallback: use the generic `wp_list_items` / `wp_create_item` tools on
14
+ * a custom namespace your plugin exposes.
15
+ */
16
+ export declare const multisiteTools: (wp: WordPressClient) => ToolDef[];
17
+ //# sourceMappingURL=multisite.d.ts.map
@@ -0,0 +1,111 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Multisite tools.
4
+ *
5
+ * Important: as of WordPress 6.x, **core does NOT yet ship a `/wp/v2/sites`
6
+ * endpoint** — it is on the multisite roadmap but unreleased. The tools below
7
+ * call `wp/v2/sites` (and `wp/v2/networks` where useful) on the assumption
8
+ * that a Multisite REST plugin (e.g. brettkrueger/multisite-rest-api) is
9
+ * installed. If you get a 404 with code `rest_no_route`, the plugin is
10
+ * missing or your build of WordPress doesn't expose the endpoint.
11
+ *
12
+ * Fallback: use the generic `wp_list_items` / `wp_create_item` tools on
13
+ * a custom namespace your plugin exposes.
14
+ */
15
+ export const multisiteTools = (wp) => [
16
+ {
17
+ name: "ms_list_sites",
18
+ title: "Multisite: list sites",
19
+ description: "List all sites in the network. Requires a Multisite REST plugin to be installed. " +
20
+ "Returns 404 (rest_no_route) on plain core WP.",
21
+ inputSchema: {
22
+ page: z.number().int().positive().optional(),
23
+ per_page: z.number().int().min(1).max(100).optional(),
24
+ search: z.string().optional(),
25
+ network: z.number().int().optional(),
26
+ _fields: z.string().optional(),
27
+ },
28
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
29
+ handler: async (input) => {
30
+ const r = await wp.list("wp/v2/sites", input);
31
+ return { sites: r.data, total: r.total };
32
+ },
33
+ },
34
+ {
35
+ name: "ms_get_site",
36
+ title: "Multisite: get site",
37
+ description: "Get a single network site by blog ID.",
38
+ inputSchema: { id: z.number().int().positive() },
39
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
40
+ handler: async (input) => wp.get("wp/v2/sites", input.id),
41
+ },
42
+ {
43
+ name: "ms_create_site",
44
+ title: "Multisite: create site",
45
+ description: "Create a new site (subsite) in the network. Field names follow the multisite-rest-api plugin conventions; adjust if your plugin differs.",
46
+ inputSchema: {
47
+ domain: z.string().min(1),
48
+ path: z.string().default("/"),
49
+ title: z.string().optional(),
50
+ network_id: z.number().int().optional(),
51
+ meta: z.record(z.string(), z.unknown()).optional(),
52
+ },
53
+ annotations: { openWorldHint: true },
54
+ handler: async (input) => wp.create("wp/v2/sites", input),
55
+ },
56
+ {
57
+ name: "ms_update_site",
58
+ title: "Multisite: update site",
59
+ description: "Update a network site by blog ID.",
60
+ inputSchema: {
61
+ id: z.number().int().positive(),
62
+ domain: z.string().optional(),
63
+ path: z.string().optional(),
64
+ title: z.string().optional(),
65
+ meta: z.record(z.string(), z.unknown()).optional(),
66
+ },
67
+ annotations: { openWorldHint: true },
68
+ handler: async (input) => {
69
+ const { id, ...data } = input;
70
+ return wp.update("wp/v2/sites", id, data);
71
+ },
72
+ },
73
+ {
74
+ name: "ms_delete_site",
75
+ title: "Multisite: delete site",
76
+ description: "Delete a network site. Always destructive — there is no trash for sites.",
77
+ inputSchema: {
78
+ id: z.number().int().positive(),
79
+ force: z.boolean().optional(),
80
+ },
81
+ annotations: { destructiveHint: true, openWorldHint: true },
82
+ handler: async (input) => {
83
+ const { id, force = true } = input;
84
+ return wp.remove("wp/v2/sites", id, force);
85
+ },
86
+ },
87
+ // ── Networks ───────────────────────────────────────────────────────────
88
+ {
89
+ name: "ms_list_networks",
90
+ title: "Multisite: list networks",
91
+ description: "List networks (multi-network installs). Same plugin requirement as sites.",
92
+ inputSchema: {
93
+ page: z.number().int().positive().optional(),
94
+ per_page: z.number().int().min(1).max(100).optional(),
95
+ },
96
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
97
+ handler: async (input) => {
98
+ const r = await wp.list("wp/v2/networks", input);
99
+ return { networks: r.data, total: r.total };
100
+ },
101
+ },
102
+ {
103
+ name: "ms_get_network",
104
+ title: "Multisite: get network",
105
+ description: "Get a single network by ID.",
106
+ inputSchema: { id: z.number().int().positive() },
107
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
108
+ handler: async (input) => wp.get("wp/v2/networks", input.id),
109
+ },
110
+ ];
111
+ //# sourceMappingURL=multisite.js.map
@@ -0,0 +1,4 @@
1
+ import { WordPressClient } from "../wordpress-client.js";
2
+ import type { ToolDef } from "../types.js";
3
+ export declare const pageTools: (wp: WordPressClient) => ToolDef[];
4
+ //# sourceMappingURL=pages.d.ts.map
@@ -0,0 +1,101 @@
1
+ import { z } from "zod";
2
+ export const pageTools = (wp) => [
3
+ {
4
+ name: "wp_get_pages",
5
+ title: "List pages",
6
+ description: "List WordPress pages.",
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
+ status: z.enum(["publish", "draft", "private", "pending", "any"]).optional(),
12
+ parent: z.number().int().optional(),
13
+ orderby: z
14
+ .enum(["date", "id", "title", "modified", "menu_order", "slug"])
15
+ .optional(),
16
+ order: z.enum(["asc", "desc"]).optional(),
17
+ _fields: z.string().optional(),
18
+ _embed: z.boolean().optional(),
19
+ context: z.enum(["view", "edit"]).optional(),
20
+ },
21
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
22
+ handler: async (input) => {
23
+ const result = await wp.pages.list(input);
24
+ return { pages: result.data, total: result.total, total_pages: result.pages };
25
+ },
26
+ },
27
+ {
28
+ name: "wp_get_page",
29
+ title: "Get a page",
30
+ description: "Get a single WordPress page by ID.",
31
+ inputSchema: {
32
+ id: z.number().int().positive(),
33
+ context: z.enum(["view", "edit"]).optional(),
34
+ _fields: z.string().optional(),
35
+ _embed: z.boolean().optional(),
36
+ },
37
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
38
+ handler: async (input) => {
39
+ const { id, ...rest } = input;
40
+ return wp.pages.get(id, rest);
41
+ },
42
+ },
43
+ {
44
+ name: "wp_create_page",
45
+ title: "Create a page",
46
+ description: "Create a new WordPress page (default status=draft).",
47
+ inputSchema: {
48
+ title: z.string().min(1),
49
+ content: z.string(),
50
+ excerpt: z.string().optional(),
51
+ status: z.enum(["publish", "draft", "private", "pending"]).optional(),
52
+ slug: z.string().optional(),
53
+ parent: z.number().int().optional(),
54
+ menu_order: z.number().int().optional(),
55
+ featured_media: z.number().int().optional(),
56
+ template: z.string().optional(),
57
+ comment_status: z.enum(["open", "closed"]).optional(),
58
+ meta: z.record(z.string(), z.unknown()).optional(),
59
+ },
60
+ annotations: { openWorldHint: true },
61
+ handler: async (input) => wp.pages.create({ status: "draft", ...input }),
62
+ },
63
+ {
64
+ name: "wp_update_page",
65
+ title: "Update a page",
66
+ description: "Update an existing WordPress page.",
67
+ inputSchema: {
68
+ id: z.number().int().positive(),
69
+ title: z.string().optional(),
70
+ content: z.string().optional(),
71
+ excerpt: z.string().optional(),
72
+ status: z.enum(["publish", "draft", "private", "pending"]).optional(),
73
+ slug: z.string().optional(),
74
+ parent: z.number().int().optional(),
75
+ menu_order: z.number().int().optional(),
76
+ featured_media: z.number().int().optional(),
77
+ template: z.string().optional(),
78
+ meta: z.record(z.string(), z.unknown()).optional(),
79
+ },
80
+ annotations: { openWorldHint: true },
81
+ handler: async (input) => {
82
+ const { id, ...data } = input;
83
+ return wp.pages.update(id, data);
84
+ },
85
+ },
86
+ {
87
+ name: "wp_delete_page",
88
+ title: "Delete a page",
89
+ description: "Move a page to trash, or permanently delete with force=true.",
90
+ inputSchema: {
91
+ id: z.number().int().positive(),
92
+ force: z.boolean().optional(),
93
+ },
94
+ annotations: { destructiveHint: true, openWorldHint: true },
95
+ handler: async (input) => {
96
+ const { id, force = false } = input;
97
+ return wp.pages.remove(id, force);
98
+ },
99
+ },
100
+ ];
101
+ //# sourceMappingURL=pages.js.map
@@ -0,0 +1,4 @@
1
+ import { WordPressClient } from "../wordpress-client.js";
2
+ import type { ToolDef } from "../types.js";
3
+ export declare const postTools: (wp: WordPressClient) => ToolDef[];
4
+ //# sourceMappingURL=posts.d.ts.map
@@ -0,0 +1,160 @@
1
+ import { z } from "zod";
2
+ const statusEnum = z.enum([
3
+ "publish",
4
+ "draft",
5
+ "private",
6
+ "pending",
7
+ "future",
8
+ ]);
9
+ const statusFilter = z.enum([...statusEnum.options, "any"]);
10
+ const orderEnum = z.enum(["asc", "desc"]);
11
+ export const postTools = (wp) => [
12
+ {
13
+ name: "wp_get_posts",
14
+ title: "List posts",
15
+ description: "List WordPress posts with filters. Use `_fields` to keep responses small (e.g. 'id,title,slug').",
16
+ inputSchema: {
17
+ page: z.number().int().positive().optional(),
18
+ per_page: z.number().int().min(1).max(100).optional(),
19
+ search: z.string().optional(),
20
+ status: statusFilter.optional(),
21
+ categories: z.array(z.number().int()).optional(),
22
+ tags: z.array(z.number().int()).optional(),
23
+ author: z.number().int().optional(),
24
+ orderby: z
25
+ .enum(["date", "id", "title", "modified", "relevance", "rand", "slug"])
26
+ .optional(),
27
+ order: orderEnum.optional(),
28
+ after: z.string().optional().describe("ISO8601 lower bound (inclusive)"),
29
+ before: z.string().optional().describe("ISO8601 upper bound (inclusive)"),
30
+ slug: z.array(z.string()).optional(),
31
+ _fields: z.string().optional(),
32
+ _embed: z.boolean().optional(),
33
+ context: z.enum(["view", "edit"]).optional(),
34
+ },
35
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
36
+ handler: async (input) => {
37
+ const result = await wp.posts.list(input);
38
+ return { posts: result.data, total: result.total, total_pages: result.pages };
39
+ },
40
+ },
41
+ {
42
+ name: "wp_get_post",
43
+ title: "Get a post",
44
+ description: "Get a single WordPress post by ID.",
45
+ inputSchema: {
46
+ id: z.number().int().positive(),
47
+ context: z.enum(["view", "edit"]).optional(),
48
+ _fields: z.string().optional(),
49
+ _embed: z.boolean().optional(),
50
+ },
51
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
52
+ handler: async (input) => {
53
+ const { id, ...rest } = input;
54
+ return wp.posts.get(id, rest);
55
+ },
56
+ },
57
+ {
58
+ name: "wp_create_post",
59
+ title: "Create a post",
60
+ description: "Create a new WordPress post. Defaults to status=draft for safety. " +
61
+ "Set `meta` to write custom fields (must be registered server-side).",
62
+ inputSchema: {
63
+ title: z.string().min(1),
64
+ content: z.string(),
65
+ excerpt: z.string().optional(),
66
+ status: statusEnum.optional(),
67
+ slug: z.string().optional(),
68
+ categories: z.array(z.number().int()).optional(),
69
+ tags: z.array(z.number().int()).optional(),
70
+ featured_media: z.number().int().optional(),
71
+ date: z.string().optional(),
72
+ comment_status: z.enum(["open", "closed"]).optional(),
73
+ ping_status: z.enum(["open", "closed"]).optional(),
74
+ format: z
75
+ .enum([
76
+ "standard",
77
+ "aside",
78
+ "chat",
79
+ "gallery",
80
+ "link",
81
+ "image",
82
+ "quote",
83
+ "status",
84
+ "video",
85
+ "audio",
86
+ ])
87
+ .optional(),
88
+ sticky: z.boolean().optional(),
89
+ author: z.number().int().optional(),
90
+ meta: z.record(z.string(), z.unknown()).optional(),
91
+ },
92
+ annotations: { openWorldHint: true },
93
+ handler: async (input) => {
94
+ const body = { status: "draft", ...input };
95
+ return wp.posts.create(body);
96
+ },
97
+ },
98
+ {
99
+ name: "wp_update_post",
100
+ title: "Update a post",
101
+ description: "Update an existing WordPress post.",
102
+ inputSchema: {
103
+ id: z.number().int().positive(),
104
+ title: z.string().optional(),
105
+ content: z.string().optional(),
106
+ excerpt: z.string().optional(),
107
+ status: statusEnum.optional(),
108
+ slug: z.string().optional(),
109
+ categories: z.array(z.number().int()).optional(),
110
+ tags: z.array(z.number().int()).optional(),
111
+ featured_media: z.number().int().optional(),
112
+ date: z.string().optional(),
113
+ comment_status: z.enum(["open", "closed"]).optional(),
114
+ ping_status: z.enum(["open", "closed"]).optional(),
115
+ sticky: z.boolean().optional(),
116
+ author: z.number().int().optional(),
117
+ meta: z.record(z.string(), z.unknown()).optional(),
118
+ },
119
+ annotations: { openWorldHint: true },
120
+ handler: async (input) => {
121
+ const { id, ...data } = input;
122
+ return wp.posts.update(id, data);
123
+ },
124
+ },
125
+ {
126
+ name: "wp_delete_post",
127
+ title: "Delete a post",
128
+ description: "Move a post to trash, or permanently delete with `force=true`. " +
129
+ "Force-delete is irreversible.",
130
+ inputSchema: {
131
+ id: z.number().int().positive(),
132
+ force: z
133
+ .boolean()
134
+ .optional()
135
+ .describe("Permanently delete (skip trash). Default false."),
136
+ },
137
+ annotations: { destructiveHint: true, openWorldHint: true },
138
+ handler: async (input) => {
139
+ const { id, force = false } = input;
140
+ return wp.posts.remove(id, force);
141
+ },
142
+ },
143
+ {
144
+ name: "wp_get_post_revisions",
145
+ title: "List post revisions",
146
+ description: "List revisions of a given post (audit trail).",
147
+ inputSchema: {
148
+ id: z.number().int().positive(),
149
+ per_page: z.number().int().min(1).max(100).optional(),
150
+ _fields: z.string().optional(),
151
+ },
152
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
153
+ handler: async (input) => {
154
+ const { id, ...rest } = input;
155
+ const result = await wp.posts.revisions(id, rest);
156
+ return { revisions: result.data, total: result.total };
157
+ },
158
+ },
159
+ ];
160
+ //# sourceMappingURL=posts.js.map
@@ -0,0 +1,4 @@
1
+ import { WordPressClient } from "../wordpress-client.js";
2
+ import type { ToolDef } from "../types.js";
3
+ export declare const seoTools: (wp: WordPressClient) => ToolDef[];
4
+ //# sourceMappingURL=seo.d.ts.map