@gravitykit/block-mcp 2.0.1 → 2.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 (42) hide show
  1. package/dist/index.cjs +311 -195
  2. package/package.json +8 -4
  3. package/src/__tests__/coerce.test.ts +57 -0
  4. package/src/__tests__/fixtures/error-envelopes.ts +19 -7
  5. package/src/__tests__/integration/error-envelopes.test.ts +2 -2
  6. package/src/__tests__/tools/discovery/list_patterns.test.ts +25 -6
  7. package/src/__tests__/tools/mutate/edit_block_tree.test.ts +53 -0
  8. package/src/__tests__/tools/patterns/insert_pattern.test.ts +16 -0
  9. package/src/__tests__/tools/posts/update_post.test.ts +14 -0
  10. package/src/__tests__/tools/read/get_block.test.ts +35 -0
  11. package/src/__tests__/tools/read/get_page_blocks.test.ts +18 -0
  12. package/src/__tests__/tools/write/delete_block.test.ts +18 -0
  13. package/src/__tests__/tools/write/insert_blocks.test.ts +21 -0
  14. package/src/__tests__/tools/write/replace_block_range.test.ts +71 -0
  15. package/src/__tests__/tools/write/rewrite_post_blocks.test.ts +39 -0
  16. package/src/__tests__/tools/write/update_block.test.ts +16 -0
  17. package/src/__tests__/tools/write/update_blocks.test.ts +21 -0
  18. package/src/__tests__/tools/yoast/yoast_get_seo.test.ts +11 -3
  19. package/src/__tests__/tools/yoast/yoast_update_seo.test.ts +16 -0
  20. package/src/__tests__/unit/enrichers/cbp-enricher.test.ts +21 -0
  21. package/src/__tests__/unit/error-translator/translate-wp-error.test.ts +100 -29
  22. package/src/__tests__/unit/instructions.test.ts +3 -3
  23. package/src/__tests__/unit/preferences/enrich-pattern-list.test.ts +26 -10
  24. package/src/__tests__/unit/rest-url.test.ts +23 -0
  25. package/src/client.ts +53 -18
  26. package/src/coerce.ts +41 -0
  27. package/src/config.ts +96 -0
  28. package/src/connect.ts +17 -3
  29. package/src/enrichers.ts +6 -2
  30. package/src/error-translator.ts +62 -10
  31. package/src/index.ts +35 -27
  32. package/src/instructions.ts +3 -3
  33. package/src/preferences.ts +56 -43
  34. package/src/rest-url.ts +18 -0
  35. package/src/tools/discovery.ts +10 -14
  36. package/src/tools/mutate.ts +18 -12
  37. package/src/tools/patterns.ts +3 -2
  38. package/src/tools/posts.ts +26 -26
  39. package/src/tools/read.ts +7 -6
  40. package/src/tools/write.ts +26 -31
  41. package/src/tools/yoast.ts +30 -31
  42. package/src/types.ts +23 -13
@@ -11,27 +11,24 @@
11
11
  */
12
12
 
13
13
  import type { WordPressBlockClient } from '../client.js';
14
- import type {
15
- YoastSchemaPageType,
16
- YoastSchemaArticleType,
17
- YoastRobotsAdvanced,
18
- YoastUpdateRequest,
19
- YoastBulkUpdateItem,
14
+ import { coercePostId } from '../coerce.js';
15
+ import {
16
+ YOAST_SCHEMA_PAGE_TYPES,
17
+ YOAST_SCHEMA_ARTICLE_TYPES,
18
+ YOAST_ROBOTS_ADVANCED,
19
+ type YoastSchemaPageType,
20
+ type YoastSchemaArticleType,
21
+ type YoastRobotsAdvanced,
22
+ type YoastUpdateRequest,
23
+ type YoastBulkUpdateItem,
20
24
  } from '../types.js';
21
25
 
22
- const SCHEMA_PAGE_TYPES: YoastSchemaPageType[] = [
23
- 'WebPage', 'ItemPage', 'AboutPage', 'FAQPage', 'QAPage',
24
- 'ProfilePage', 'ContactPage', 'MedicalWebPage', 'CollectionPage',
25
- 'CheckoutPage', 'RealEstateListing', 'SearchResultsPage',
26
- ];
27
-
28
- const SCHEMA_ARTICLE_TYPES: YoastSchemaArticleType[] = [
29
- 'Article', 'BlogPosting', 'SocialMediaPosting', 'NewsArticle',
30
- 'AdvertiserContentArticle', 'SatiricalArticle', 'ScholarlyArticle',
31
- 'TechArticle', 'Report', 'None',
32
- ];
33
-
34
- const ROBOTS_ADVANCED: YoastRobotsAdvanced[] = ['noimageindex', 'noarchive', 'nosnippet'];
26
+ // Local aliases: the enum values themselves live in types.ts (single source
27
+ // shared with the TS union types); these names keep the rest of this file
28
+ // unchanged.
29
+ const SCHEMA_PAGE_TYPES = YOAST_SCHEMA_PAGE_TYPES;
30
+ const SCHEMA_ARTICLE_TYPES = YOAST_SCHEMA_ARTICLE_TYPES;
31
+ const ROBOTS_ADVANCED = YOAST_ROBOTS_ADVANCED;
35
32
 
36
33
  /** Field-level schema reused by yoast_update_seo and yoast_bulk_update_seo. */
37
34
  const YOAST_FIELD_PROPERTIES = {
@@ -40,8 +37,8 @@ const YOAST_FIELD_PROPERTIES = {
40
37
  canonical: { type: 'string', description: 'Canonical URL override.' },
41
38
  focus_keyword: { type: 'string', description: 'Focus keyphrase.' },
42
39
  noindex: {
43
- type: ['boolean', 'null'],
44
- description: 'Tri-state: true=noindex, false=explicit index, null=post-type default.',
40
+ type: 'boolean',
41
+ description: 'true=noindex, false=explicit index. Pass null to reset to the post-type default; omit to leave the current value unchanged. (Advertised as a single boolean type because some AI clients — e.g. Google Gemini — reject a "null" member in a type array; the handler still accepts an explicit null.)',
45
42
  },
46
43
  nofollow: { type: 'boolean', description: 'true=nofollow, false=follow.' },
47
44
  robots_advanced: {
@@ -132,17 +129,17 @@ export async function handleYoastTool(
132
129
  ): Promise<unknown> {
133
130
  switch (toolName) {
134
131
  case 'yoast_get_seo': {
135
- const postId = args.post_id;
136
- if (typeof postId !== 'number') {
137
- throw new Error('yoast_get_seo: "post_id" (number) is required');
132
+ const postId = coercePostId(args.post_id, 'yoast_get_seo');
133
+ if (postId === undefined) {
134
+ throw new Error('yoast_get_seo: "post_id" is required');
138
135
  }
139
136
  return client.getYoastSEO(postId);
140
137
  }
141
138
 
142
139
  case 'yoast_update_seo': {
143
- const postId = args.post_id;
144
- if (typeof postId !== 'number') {
145
- throw new Error('yoast_update_seo: "post_id" (number) is required');
140
+ const postId = coercePostId(args.post_id, 'yoast_update_seo');
141
+ if (postId === undefined) {
142
+ throw new Error('yoast_update_seo: "post_id" is required');
146
143
  }
147
144
  const { post_id: _omit, ...rest } = args;
148
145
  void _omit;
@@ -163,11 +160,13 @@ export async function handleYoastTool(
163
160
  throw new Error('yoast_bulk_update_seo: each item in `posts` must be an object');
164
161
  }
165
162
  const obj = raw as Record<string, unknown>;
166
- if (typeof obj.post_id !== 'number') {
167
- throw new Error('yoast_bulk_update_seo: each item requires `post_id` (number)');
163
+ const id = coercePostId(obj.post_id, 'yoast_bulk_update_seo');
164
+ if (id === undefined) {
165
+ throw new Error('yoast_bulk_update_seo: each item requires `post_id`');
168
166
  }
169
- const { post_id: id, ...rest } = obj;
170
- items.push({ post_id: id as number, ...narrowYoastFields(rest) });
167
+ const { post_id: _omit, ...rest } = obj;
168
+ void _omit;
169
+ items.push({ post_id: id, ...narrowYoastFields(rest) });
171
170
  }
172
171
  return client.bulkUpdateYoastSEO(items);
173
172
  }
package/src/types.ts CHANGED
@@ -83,8 +83,8 @@ export interface BlockType {
83
83
  export interface PatternPreference {
84
84
  /** Computed preference score */
85
85
  score: number;
86
- /** Human-readable tier */
87
- tier: 'recommended' | 'acceptable' | 'avoid' | 'legacy';
86
+ /** Human-readable tier (matches the server: preferred/acceptable/avoid/legacy) */
87
+ tier: 'preferred' | 'acceptable' | 'avoid' | 'legacy';
88
88
  /** Reasons contributing to the score */
89
89
  reasons: string[];
90
90
  }
@@ -799,17 +799,27 @@ export interface UploadMediaResponse {
799
799
  // Routes only register when Yoast SEO is active on the target site.
800
800
  // ============================================
801
801
 
802
- export type YoastSchemaPageType =
803
- | 'WebPage' | 'ItemPage' | 'AboutPage' | 'FAQPage' | 'QAPage'
804
- | 'ProfilePage' | 'ContactPage' | 'MedicalWebPage' | 'CollectionPage'
805
- | 'CheckoutPage' | 'RealEstateListing' | 'SearchResultsPage';
806
-
807
- export type YoastSchemaArticleType =
808
- | 'Article' | 'BlogPosting' | 'SocialMediaPosting' | 'NewsArticle'
809
- | 'AdvertiserContentArticle' | 'SatiricalArticle' | 'ScholarlyArticle'
810
- | 'TechArticle' | 'Report' | 'None';
811
-
812
- export type YoastRobotsAdvanced = 'noimageindex' | 'noarchive' | 'nosnippet';
802
+ /**
803
+ * Single source of truth for the Yoast schema-page-type enum — the const
804
+ * array backs both the runtime value (JSON Schema `enum`, narrowing filters
805
+ * in src/tools/yoast.ts) and the TS union type below, so the two can't drift.
806
+ */
807
+ export const YOAST_SCHEMA_PAGE_TYPES = [
808
+ 'WebPage', 'ItemPage', 'AboutPage', 'FAQPage', 'QAPage',
809
+ 'ProfilePage', 'ContactPage', 'MedicalWebPage', 'CollectionPage',
810
+ 'CheckoutPage', 'RealEstateListing', 'SearchResultsPage',
811
+ ] as const;
812
+ export type YoastSchemaPageType = typeof YOAST_SCHEMA_PAGE_TYPES[number];
813
+
814
+ export const YOAST_SCHEMA_ARTICLE_TYPES = [
815
+ 'Article', 'BlogPosting', 'SocialMediaPosting', 'NewsArticle',
816
+ 'AdvertiserContentArticle', 'SatiricalArticle', 'ScholarlyArticle',
817
+ 'TechArticle', 'Report', 'None',
818
+ ] as const;
819
+ export type YoastSchemaArticleType = typeof YOAST_SCHEMA_ARTICLE_TYPES[number];
820
+
821
+ export const YOAST_ROBOTS_ADVANCED = ['noimageindex', 'noarchive', 'nosnippet'] as const;
822
+ export type YoastRobotsAdvanced = typeof YOAST_ROBOTS_ADVANCED[number];
813
823
 
814
824
  /**
815
825
  * Writable Yoast SEO fields. All optional in update payloads — only the