claude-plugin-wordpress-manager 2.6.0 → 2.9.1

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 (50) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/agents/wp-monitoring-agent.md +44 -0
  3. package/agents/wp-site-manager.md +19 -0
  4. package/docs/GUIDE.md +145 -14
  5. package/docs/plans/2026-03-01-tier4-5-implementation.md +1783 -0
  6. package/docs/plans/2026-03-01-tier4-5-observability-automation-design.md +426 -0
  7. package/docs/plans/2026-03-01-wcop-reassessment-v2.6.0.md +403 -0
  8. package/hooks/hooks.json +9 -0
  9. package/package.json +10 -3
  10. package/servers/wp-rest-bridge/build/tools/cwv.d.ts +3 -0
  11. package/servers/wp-rest-bridge/build/tools/cwv.js +196 -0
  12. package/servers/wp-rest-bridge/build/tools/ga4.d.ts +3 -0
  13. package/servers/wp-rest-bridge/build/tools/ga4.js +323 -0
  14. package/servers/wp-rest-bridge/build/tools/index.js +15 -0
  15. package/servers/wp-rest-bridge/build/tools/plausible.d.ts +3 -0
  16. package/servers/wp-rest-bridge/build/tools/plausible.js +207 -0
  17. package/servers/wp-rest-bridge/build/tools/slack.d.ts +3 -0
  18. package/servers/wp-rest-bridge/build/tools/slack.js +129 -0
  19. package/servers/wp-rest-bridge/build/tools/wc-workflows.d.ts +3 -0
  20. package/servers/wp-rest-bridge/build/tools/wc-workflows.js +222 -0
  21. package/servers/wp-rest-bridge/build/wordpress.d.ts +18 -0
  22. package/servers/wp-rest-bridge/build/wordpress.js +139 -0
  23. package/skills/wordpress-router/SKILL.md +1 -1
  24. package/skills/wordpress-router/references/decision-tree.md +8 -2
  25. package/skills/wp-alerting/SKILL.md +142 -0
  26. package/skills/wp-alerting/references/alert-thresholds.md +79 -0
  27. package/skills/wp-alerting/references/escalation-paths.md +92 -0
  28. package/skills/wp-alerting/references/report-scheduling.md +142 -0
  29. package/skills/wp-alerting/references/slack-integration.md +109 -0
  30. package/skills/wp-alerting/scripts/alerting_inspect.mjs +150 -0
  31. package/skills/wp-analytics/SKILL.md +158 -0
  32. package/skills/wp-analytics/references/analytics-dashboards.md +83 -0
  33. package/skills/wp-analytics/references/cwv-monitoring.md +101 -0
  34. package/skills/wp-analytics/references/ga4-integration.md +76 -0
  35. package/skills/wp-analytics/references/plausible-setup.md +92 -0
  36. package/skills/wp-analytics/references/traffic-attribution.md +92 -0
  37. package/skills/wp-analytics/scripts/analytics_inspect.mjs +153 -0
  38. package/skills/wp-content-attribution/SKILL.md +1 -0
  39. package/skills/wp-content-optimization/SKILL.md +1 -0
  40. package/skills/wp-content-workflows/SKILL.md +142 -0
  41. package/skills/wp-content-workflows/references/content-lifecycle-hooks.md +131 -0
  42. package/skills/wp-content-workflows/references/multi-channel-actions.md +151 -0
  43. package/skills/wp-content-workflows/references/schedule-triggers.md +118 -0
  44. package/skills/wp-content-workflows/references/trigger-management.md +159 -0
  45. package/skills/wp-content-workflows/references/wp-action-hooks.md +114 -0
  46. package/skills/wp-content-workflows/scripts/workflow_inspect.mjs +202 -0
  47. package/skills/wp-monitoring/SKILL.md +2 -0
  48. package/skills/wp-search-console/SKILL.md +1 -0
  49. package/skills/wp-social-email/SKILL.md +1 -0
  50. package/skills/wp-webhooks/SKILL.md +1 -0
@@ -0,0 +1,1783 @@
1
+ # Tier 4+5 WCOP Implementation Plan (v2.7.0 → v2.9.0)
2
+
3
+ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
+
5
+ **Goal:** Add Observability (analytics + CWV + alerting) and Automation (workflow templates + event triggers) to the wordpress-manager plugin, raising WCOP score from 8/10 to 8.8/10.
6
+
7
+ **Architecture:** Three incremental releases adding MCP tool files (TypeScript in `servers/wp-rest-bridge/src/tools/`), skills (SKILL.md + reference files), detection scripts (.mjs), agent updates, router updates, and safety hooks. All follow existing Tier 3 patterns exactly (Zod schemas → Tool[] → handlers Record with has*/make* guards).
8
+
9
+ **Tech Stack:** TypeScript, `googleapis` npm package (GA4 Data API, CrUX API), `axios` (Plausible, PageSpeed Insights, Slack), Zod, MCP SDK.
10
+
11
+ **Design doc:** `docs/plans/2026-03-01-tier4-5-observability-automation-design.md`
12
+
13
+ ---
14
+
15
+ ## Release 1: v2.7.0 — Analytics (GA4 + Plausible + CWV)
16
+
17
+ ### Task 1: Extend SiteConfig with analytics fields
18
+
19
+ **Files:**
20
+ - Modify: `servers/wp-rest-bridge/src/wordpress.ts:6-27` (SiteConfig interface)
21
+ - Modify: `servers/wp-rest-bridge/src/wordpress.ts:57-63` (module state — add client maps)
22
+ - Modify: `servers/wp-rest-bridge/src/wordpress.ts:114-141` (initWordPress loop — add GA4/Plausible init)
23
+
24
+ **Step 1: Add SiteConfig fields**
25
+
26
+ Add after the `gsc_site_url` line (line 26) in `wordpress.ts`:
27
+
28
+ ```typescript
29
+ // Google Analytics 4 (optional)
30
+ ga4_property_id?: string; // GA4 property (e.g., "properties/123456789")
31
+ ga4_service_account_key?: string; // Path to service account JSON (can reuse GSC key)
32
+ // Plausible Analytics (optional)
33
+ plausible_api_key?: string; // Plausible API key (Bearer token)
34
+ plausible_base_url?: string; // Default: "https://plausible.io" (or self-hosted)
35
+ // Google API key for public APIs (PageSpeed Insights, CrUX)
36
+ google_api_key?: string;
37
+ ```
38
+
39
+ **Step 2: Add module state maps**
40
+
41
+ Add after `const sgSiteClients` (around line 63):
42
+
43
+ ```typescript
44
+ const plSiteClients = new Map<string, AxiosInstance>();
45
+ ```
46
+
47
+ **Step 3: Add Plausible client init function**
48
+
49
+ Add after `initSendGridClient` function:
50
+
51
+ ```typescript
52
+ async function initPlausibleClient(id: string, apiKey: string, baseUrl?: string) {
53
+ const client = axios.create({
54
+ baseURL: (baseUrl || 'https://plausible.io') + '/api/v1/',
55
+ headers: {
56
+ 'Authorization': `Bearer ${apiKey}`,
57
+ },
58
+ timeout: DEFAULT_TIMEOUT_MS,
59
+ });
60
+ plSiteClients.set(id, client);
61
+ }
62
+ ```
63
+
64
+ **Step 4: Add init call in initWordPress loop**
65
+
66
+ In the `for (const site of sites)` loop (after SendGrid init, ~line 137):
67
+
68
+ ```typescript
69
+ if (site.plausible_api_key) {
70
+ await initPlausibleClient(site.id, site.plausible_api_key, site.plausible_base_url);
71
+ logToStderr(`Initialized Plausible for site: ${site.id}`);
72
+ }
73
+ ```
74
+
75
+ **Step 5: Add has/make/get functions for GA4, Plausible, CWV**
76
+
77
+ Add before the `// ── Plugin Repository` section (~line 670):
78
+
79
+ ```typescript
80
+ // ── Google Analytics 4 Interface ─────────────────────────────────
81
+
82
+ const ga4AuthClients = new Map<string, any>();
83
+
84
+ export function hasGA4(siteId?: string): boolean {
85
+ const id = siteId || activeSiteId;
86
+ const sites = JSON.parse(process.env.WP_SITES_CONFIG || '[]');
87
+ const site = sites.find((s: SiteConfig) => s.id === id);
88
+ return !!(site?.ga4_property_id && site?.ga4_service_account_key);
89
+ }
90
+
91
+ export function getGA4PropertyId(siteId?: string): string {
92
+ const id = siteId || activeSiteId;
93
+ const sites = JSON.parse(process.env.WP_SITES_CONFIG || '[]');
94
+ const site = sites.find((s: SiteConfig) => s.id === id);
95
+ if (!site?.ga4_property_id) {
96
+ throw new Error(`GA4 property not configured for site "${id}". Add ga4_property_id to WP_SITES_CONFIG.`);
97
+ }
98
+ return site.ga4_property_id;
99
+ }
100
+
101
+ export async function getGA4Auth(siteId?: string) {
102
+ const id = siteId || activeSiteId;
103
+ if (ga4AuthClients.has(id)) return ga4AuthClients.get(id);
104
+
105
+ const sites = JSON.parse(process.env.WP_SITES_CONFIG || '[]');
106
+ const site = sites.find((s: SiteConfig) => s.id === id);
107
+ if (!site?.ga4_service_account_key) {
108
+ throw new Error(`GA4 not configured for site "${id}". Add ga4_service_account_key to WP_SITES_CONFIG.`);
109
+ }
110
+
111
+ const keyContent = JSON.parse(readFileSync(site.ga4_service_account_key, 'utf-8'));
112
+ const auth = new google.auth.GoogleAuth({
113
+ credentials: keyContent,
114
+ scopes: ['https://www.googleapis.com/auth/analytics.readonly'],
115
+ });
116
+ const authClient = await auth.getClient();
117
+ ga4AuthClients.set(id, authClient);
118
+ return authClient;
119
+ }
120
+
121
+ // ── Plausible Analytics Interface ────────────────────────────────
122
+
123
+ export function hasPlausible(siteId?: string): boolean {
124
+ const id = siteId || activeSiteId;
125
+ return plSiteClients.has(id);
126
+ }
127
+
128
+ export async function makePlausibleRequest(
129
+ method: string,
130
+ endpoint: string,
131
+ params?: Record<string, any>,
132
+ siteId?: string
133
+ ): Promise<any> {
134
+ const id = siteId || activeSiteId;
135
+ const client = plSiteClients.get(id);
136
+ if (!client) {
137
+ throw new Error(`Plausible not configured for site "${id}". Add plausible_api_key to WP_SITES_CONFIG.`);
138
+ }
139
+ const limiter = getLimiter(id);
140
+ await limiter.acquire();
141
+ try {
142
+ const response = await client.request({ method, url: endpoint, params });
143
+ return response.data;
144
+ } finally {
145
+ limiter.release();
146
+ }
147
+ }
148
+
149
+ // ── Core Web Vitals Interface (Google API Key) ───────────────────
150
+
151
+ export function hasGoogleApiKey(siteId?: string): boolean {
152
+ const id = siteId || activeSiteId;
153
+ const sites = JSON.parse(process.env.WP_SITES_CONFIG || '[]');
154
+ const site = sites.find((s: SiteConfig) => s.id === id);
155
+ return !!site?.google_api_key;
156
+ }
157
+
158
+ export function getGoogleApiKey(siteId?: string): string {
159
+ const id = siteId || activeSiteId;
160
+ const sites = JSON.parse(process.env.WP_SITES_CONFIG || '[]');
161
+ const site = sites.find((s: SiteConfig) => s.id === id);
162
+ if (!site?.google_api_key) {
163
+ throw new Error(`Google API key not configured for site "${id}". Add google_api_key to WP_SITES_CONFIG.`);
164
+ }
165
+ return site.google_api_key;
166
+ }
167
+ ```
168
+
169
+ **Step 6: Build to verify**
170
+
171
+ ```bash
172
+ cd servers/wp-rest-bridge && npx tsc
173
+ ```
174
+ Expected: No errors.
175
+
176
+ **Step 7: Commit**
177
+
178
+ ```bash
179
+ git add servers/wp-rest-bridge/src/wordpress.ts
180
+ git commit -m "feat(wp-rest-bridge): extend SiteConfig with GA4, Plausible, CWV fields
181
+
182
+ - Add ga4_property_id, ga4_service_account_key to SiteConfig
183
+ - Add plausible_api_key, plausible_base_url to SiteConfig
184
+ - Add google_api_key for PageSpeed Insights / CrUX
185
+ - Add initPlausibleClient + plSiteClients map
186
+ - Add hasGA4/getGA4Auth/getGA4PropertyId (googleapis Service Account)
187
+ - Add hasPlausible/makePlausibleRequest (axios Bearer token)
188
+ - Add hasGoogleApiKey/getGoogleApiKey
189
+
190
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
191
+ ```
192
+
193
+ ---
194
+
195
+ ### Task 2: Create GA4 tool file (6 tools)
196
+
197
+ **Files:**
198
+ - Create: `servers/wp-rest-bridge/src/tools/ga4.ts`
199
+
200
+ **Step 1: Write the GA4 tool file**
201
+
202
+ Follow the exact pattern from `gsc.ts`: import from `../wordpress.js`, Zod schemas, Tool[] array, handlers Record.
203
+
204
+ ```typescript
205
+ // src/tools/ga4.ts
206
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
207
+ import { hasGA4, getGA4Auth, getGA4PropertyId } from '../wordpress.js';
208
+ import { z } from 'zod';
209
+
210
+ // ── Zod Schemas ─────────────────────────────────────────────────
211
+
212
+ const ga4RunReportSchema = z.object({
213
+ dimensions: z.array(z.string()).describe('Dimension names (e.g., ["date", "country", "pagePath"])'),
214
+ metrics: z.array(z.string()).describe('Metric names (e.g., ["sessions", "activeUsers", "screenPageViews"])'),
215
+ start_date: z.string().describe('Start date (YYYY-MM-DD or relative like "30daysAgo")'),
216
+ end_date: z.string().describe('End date (YYYY-MM-DD or "today")'),
217
+ limit: z.number().optional().default(100).describe('Max rows (default 100)'),
218
+ }).strict();
219
+
220
+ const ga4GetRealtimeSchema = z.object({
221
+ metrics: z.array(z.string()).optional().default(['activeUsers'])
222
+ .describe('Realtime metrics (default: ["activeUsers"])'),
223
+ dimensions: z.array(z.string()).optional()
224
+ .describe('Optional realtime dimensions (e.g., ["country", "unifiedScreenName"])'),
225
+ }).strict();
226
+
227
+ const ga4TopPagesSchema = z.object({
228
+ start_date: z.string().describe('Start date (YYYY-MM-DD or "30daysAgo")'),
229
+ end_date: z.string().describe('End date (YYYY-MM-DD or "today")'),
230
+ limit: z.number().optional().default(25).describe('Number of top pages (default 25)'),
231
+ }).strict();
232
+
233
+ const ga4TrafficSourcesSchema = z.object({
234
+ start_date: z.string().describe('Start date'),
235
+ end_date: z.string().describe('End date'),
236
+ limit: z.number().optional().default(25).describe('Number of sources (default 25)'),
237
+ }).strict();
238
+
239
+ const ga4UserDemographicsSchema = z.object({
240
+ start_date: z.string().describe('Start date'),
241
+ end_date: z.string().describe('End date'),
242
+ breakdown: z.enum(['country', 'deviceCategory', 'browser']).optional().default('country')
243
+ .describe('Breakdown dimension (default: country)'),
244
+ limit: z.number().optional().default(25).describe('Max rows (default 25)'),
245
+ }).strict();
246
+
247
+ const ga4ConversionEventsSchema = z.object({
248
+ start_date: z.string().describe('Start date'),
249
+ end_date: z.string().describe('End date'),
250
+ limit: z.number().optional().default(25).describe('Max events (default 25)'),
251
+ }).strict();
252
+
253
+ // ── Tool Definitions ────────────────────────────────────────────
254
+
255
+ export const ga4Tools: Tool[] = [
256
+ {
257
+ name: "ga4_run_report",
258
+ description: "Runs a custom GA4 report with specified dimensions and metrics",
259
+ inputSchema: {
260
+ type: "object",
261
+ properties: {
262
+ dimensions: { type: "array", items: { type: "string" }, description: "Dimension names (e.g., date, country, pagePath)" },
263
+ metrics: { type: "array", items: { type: "string" }, description: "Metric names (e.g., sessions, activeUsers)" },
264
+ start_date: { type: "string", description: "Start date (YYYY-MM-DD or 30daysAgo)" },
265
+ end_date: { type: "string", description: "End date (YYYY-MM-DD or today)" },
266
+ limit: { type: "number", description: "Max rows to return (default 100)" },
267
+ },
268
+ required: ["dimensions", "metrics", "start_date", "end_date"],
269
+ },
270
+ },
271
+ {
272
+ name: "ga4_get_realtime",
273
+ description: "Gets real-time active users and optional dimensions from GA4",
274
+ inputSchema: {
275
+ type: "object",
276
+ properties: {
277
+ metrics: { type: "array", items: { type: "string" }, description: "Realtime metrics (default: activeUsers)" },
278
+ dimensions: { type: "array", items: { type: "string" }, description: "Realtime dimensions (e.g., country)" },
279
+ },
280
+ },
281
+ },
282
+ {
283
+ name: "ga4_top_pages",
284
+ description: "Gets top pages by pageviews from GA4 (convenience shortcut)",
285
+ inputSchema: {
286
+ type: "object",
287
+ properties: {
288
+ start_date: { type: "string", description: "Start date" },
289
+ end_date: { type: "string", description: "End date" },
290
+ limit: { type: "number", description: "Number of top pages (default 25)" },
291
+ },
292
+ required: ["start_date", "end_date"],
293
+ },
294
+ },
295
+ {
296
+ name: "ga4_traffic_sources",
297
+ description: "Gets traffic sources breakdown by source/medium from GA4",
298
+ inputSchema: {
299
+ type: "object",
300
+ properties: {
301
+ start_date: { type: "string", description: "Start date" },
302
+ end_date: { type: "string", description: "End date" },
303
+ limit: { type: "number", description: "Number of sources (default 25)" },
304
+ },
305
+ required: ["start_date", "end_date"],
306
+ },
307
+ },
308
+ {
309
+ name: "ga4_user_demographics",
310
+ description: "Gets user demographic breakdown (country, device, browser) from GA4",
311
+ inputSchema: {
312
+ type: "object",
313
+ properties: {
314
+ start_date: { type: "string", description: "Start date" },
315
+ end_date: { type: "string", description: "End date" },
316
+ breakdown: { type: "string", enum: ["country", "deviceCategory", "browser"], description: "Breakdown type (default: country)" },
317
+ limit: { type: "number", description: "Max rows (default 25)" },
318
+ },
319
+ required: ["start_date", "end_date"],
320
+ },
321
+ },
322
+ {
323
+ name: "ga4_conversion_events",
324
+ description: "Gets conversion events and rates from GA4",
325
+ inputSchema: {
326
+ type: "object",
327
+ properties: {
328
+ start_date: { type: "string", description: "Start date" },
329
+ end_date: { type: "string", description: "End date" },
330
+ limit: { type: "number", description: "Max events (default 25)" },
331
+ },
332
+ required: ["start_date", "end_date"],
333
+ },
334
+ },
335
+ ];
336
+
337
+ // ── Handlers ────────────────────────────────────────────────────
338
+
339
+ export const ga4Handlers: Record<string, Function> = {
340
+ ga4_run_report: async (params: z.infer<typeof ga4RunReportSchema>) => {
341
+ if (!hasGA4()) {
342
+ return { toolResult: { isError: true, content: [{ type: "text", text: "GA4 not configured. Add ga4_property_id and ga4_service_account_key to WP_SITES_CONFIG." }] } };
343
+ }
344
+ try {
345
+ const auth = await getGA4Auth();
346
+ const propertyId = getGA4PropertyId();
347
+ const { dimensions, metrics, start_date, end_date, limit } = params;
348
+ const analyticsdata = google.analyticsdata({ version: 'v1beta', auth });
349
+ const response = await analyticsdata.properties.runReport({
350
+ property: propertyId,
351
+ requestBody: {
352
+ dimensions: dimensions.map(d => ({ name: d })),
353
+ metrics: metrics.map(m => ({ name: m })),
354
+ dateRanges: [{ startDate: start_date, endDate: end_date }],
355
+ limit: limit || 100,
356
+ },
357
+ });
358
+ return { toolResult: { content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }] } };
359
+ } catch (error: any) {
360
+ const errorMessage = error.response?.data?.error?.message || error.message;
361
+ return { toolResult: { isError: true, content: [{ type: "text", text: `Error running GA4 report: ${errorMessage}` }] } };
362
+ }
363
+ },
364
+
365
+ ga4_get_realtime: async (params: z.infer<typeof ga4GetRealtimeSchema>) => {
366
+ if (!hasGA4()) {
367
+ return { toolResult: { isError: true, content: [{ type: "text", text: "GA4 not configured. Add ga4_property_id and ga4_service_account_key to WP_SITES_CONFIG." }] } };
368
+ }
369
+ try {
370
+ const auth = await getGA4Auth();
371
+ const propertyId = getGA4PropertyId();
372
+ const { metrics, dimensions } = params;
373
+ const analyticsdata = google.analyticsdata({ version: 'v1beta', auth });
374
+ const requestBody: Record<string, any> = {
375
+ metrics: (metrics || ['activeUsers']).map((m: string) => ({ name: m })),
376
+ };
377
+ if (dimensions) requestBody.dimensions = dimensions.map((d: string) => ({ name: d }));
378
+ const response = await analyticsdata.properties.runRealtimeReport({
379
+ property: propertyId,
380
+ requestBody,
381
+ });
382
+ return { toolResult: { content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }] } };
383
+ } catch (error: any) {
384
+ const errorMessage = error.response?.data?.error?.message || error.message;
385
+ return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting GA4 realtime: ${errorMessage}` }] } };
386
+ }
387
+ },
388
+
389
+ ga4_top_pages: async (params: z.infer<typeof ga4TopPagesSchema>) => {
390
+ if (!hasGA4()) {
391
+ return { toolResult: { isError: true, content: [{ type: "text", text: "GA4 not configured. Add ga4_property_id and ga4_service_account_key to WP_SITES_CONFIG." }] } };
392
+ }
393
+ try {
394
+ const auth = await getGA4Auth();
395
+ const propertyId = getGA4PropertyId();
396
+ const { start_date, end_date, limit } = params;
397
+ const analyticsdata = google.analyticsdata({ version: 'v1beta', auth });
398
+ const response = await analyticsdata.properties.runReport({
399
+ property: propertyId,
400
+ requestBody: {
401
+ dimensions: [{ name: 'pagePath' }],
402
+ metrics: [{ name: 'screenPageViews' }, { name: 'activeUsers' }, { name: 'averageSessionDuration' }],
403
+ dateRanges: [{ startDate: start_date, endDate: end_date }],
404
+ orderBys: [{ metric: { metricName: 'screenPageViews' }, desc: true }],
405
+ limit: limit || 25,
406
+ },
407
+ });
408
+ return { toolResult: { content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }] } };
409
+ } catch (error: any) {
410
+ const errorMessage = error.response?.data?.error?.message || error.message;
411
+ return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting top pages: ${errorMessage}` }] } };
412
+ }
413
+ },
414
+
415
+ ga4_traffic_sources: async (params: z.infer<typeof ga4TrafficSourcesSchema>) => {
416
+ if (!hasGA4()) {
417
+ return { toolResult: { isError: true, content: [{ type: "text", text: "GA4 not configured. Add ga4_property_id and ga4_service_account_key to WP_SITES_CONFIG." }] } };
418
+ }
419
+ try {
420
+ const auth = await getGA4Auth();
421
+ const propertyId = getGA4PropertyId();
422
+ const { start_date, end_date, limit } = params;
423
+ const analyticsdata = google.analyticsdata({ version: 'v1beta', auth });
424
+ const response = await analyticsdata.properties.runReport({
425
+ property: propertyId,
426
+ requestBody: {
427
+ dimensions: [{ name: 'sessionSource' }, { name: 'sessionMedium' }],
428
+ metrics: [{ name: 'sessions' }, { name: 'activeUsers' }, { name: 'conversions' }],
429
+ dateRanges: [{ startDate: start_date, endDate: end_date }],
430
+ orderBys: [{ metric: { metricName: 'sessions' }, desc: true }],
431
+ limit: limit || 25,
432
+ },
433
+ });
434
+ return { toolResult: { content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }] } };
435
+ } catch (error: any) {
436
+ const errorMessage = error.response?.data?.error?.message || error.message;
437
+ return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting traffic sources: ${errorMessage}` }] } };
438
+ }
439
+ },
440
+
441
+ ga4_user_demographics: async (params: z.infer<typeof ga4UserDemographicsSchema>) => {
442
+ if (!hasGA4()) {
443
+ return { toolResult: { isError: true, content: [{ type: "text", text: "GA4 not configured. Add ga4_property_id and ga4_service_account_key to WP_SITES_CONFIG." }] } };
444
+ }
445
+ try {
446
+ const auth = await getGA4Auth();
447
+ const propertyId = getGA4PropertyId();
448
+ const { start_date, end_date, breakdown, limit } = params;
449
+ const analyticsdata = google.analyticsdata({ version: 'v1beta', auth });
450
+ const response = await analyticsdata.properties.runReport({
451
+ property: propertyId,
452
+ requestBody: {
453
+ dimensions: [{ name: breakdown || 'country' }],
454
+ metrics: [{ name: 'activeUsers' }, { name: 'sessions' }, { name: 'screenPageViews' }],
455
+ dateRanges: [{ startDate: start_date, endDate: end_date }],
456
+ orderBys: [{ metric: { metricName: 'activeUsers' }, desc: true }],
457
+ limit: limit || 25,
458
+ },
459
+ });
460
+ return { toolResult: { content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }] } };
461
+ } catch (error: any) {
462
+ const errorMessage = error.response?.data?.error?.message || error.message;
463
+ return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting demographics: ${errorMessage}` }] } };
464
+ }
465
+ },
466
+
467
+ ga4_conversion_events: async (params: z.infer<typeof ga4ConversionEventsSchema>) => {
468
+ if (!hasGA4()) {
469
+ return { toolResult: { isError: true, content: [{ type: "text", text: "GA4 not configured. Add ga4_property_id and ga4_service_account_key to WP_SITES_CONFIG." }] } };
470
+ }
471
+ try {
472
+ const auth = await getGA4Auth();
473
+ const propertyId = getGA4PropertyId();
474
+ const { start_date, end_date, limit } = params;
475
+ const analyticsdata = google.analyticsdata({ version: 'v1beta', auth });
476
+ const response = await analyticsdata.properties.runReport({
477
+ property: propertyId,
478
+ requestBody: {
479
+ dimensions: [{ name: 'eventName' }],
480
+ metrics: [{ name: 'eventCount' }, { name: 'conversions' }, { name: 'totalRevenue' }],
481
+ dateRanges: [{ startDate: start_date, endDate: end_date }],
482
+ orderBys: [{ metric: { metricName: 'conversions' }, desc: true }],
483
+ limit: limit || 25,
484
+ },
485
+ });
486
+ return { toolResult: { content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }] } };
487
+ } catch (error: any) {
488
+ const errorMessage = error.response?.data?.error?.message || error.message;
489
+ return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting conversion events: ${errorMessage}` }] } };
490
+ }
491
+ },
492
+ };
493
+ ```
494
+
495
+ **Step 2: Build to verify**
496
+
497
+ ```bash
498
+ cd servers/wp-rest-bridge && npx tsc
499
+ ```
500
+ Expected: No errors.
501
+
502
+ **Step 3: Commit**
503
+
504
+ ```bash
505
+ git add servers/wp-rest-bridge/src/tools/ga4.ts
506
+ git commit -m "feat(wp-rest-bridge): add GA4 Analytics tool file (6 tools)
507
+
508
+ - ga4_run_report: custom report with dimensions/metrics/date range
509
+ - ga4_get_realtime: real-time active users
510
+ - ga4_top_pages: top pages by pageviews (shortcut)
511
+ - ga4_traffic_sources: source/medium breakdown (shortcut)
512
+ - ga4_user_demographics: country/device/browser breakdown (shortcut)
513
+ - ga4_conversion_events: conversion events and rates (shortcut)
514
+ - Uses googleapis analyticsdata v1beta with Service Account auth
515
+
516
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
517
+ ```
518
+
519
+ ---
520
+
521
+ ### Task 3: Create Plausible tool file (4 tools)
522
+
523
+ **Files:**
524
+ - Create: `servers/wp-rest-bridge/src/tools/plausible.ts`
525
+
526
+ **Step 1: Write the Plausible tool file**
527
+
528
+ ```typescript
529
+ // src/tools/plausible.ts
530
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
531
+ import { hasPlausible, makePlausibleRequest } from '../wordpress.js';
532
+ import { z } from 'zod';
533
+
534
+ // ── Zod Schemas ─────────────────────────────────────────────────
535
+
536
+ const plGetStatsSchema = z.object({
537
+ site_id: z.string().describe('Plausible site domain (e.g., "mysite.com")'),
538
+ period: z.enum(['day', '7d', '30d', 'month', '6mo', '12mo', 'custom']).optional().default('30d')
539
+ .describe('Time period (default: 30d)'),
540
+ date: z.string().optional().describe('Date or date range for custom period (YYYY-MM-DD or YYYY-MM-DD,YYYY-MM-DD)'),
541
+ metrics: z.string().optional().default('visitors,pageviews,bounce_rate,visit_duration')
542
+ .describe('Comma-separated metrics'),
543
+ }).strict();
544
+
545
+ const plGetTimeseriesSchema = z.object({
546
+ site_id: z.string().describe('Plausible site domain'),
547
+ period: z.enum(['day', '7d', '30d', 'month', '6mo', '12mo', 'custom']).optional().default('30d'),
548
+ date: z.string().optional(),
549
+ metrics: z.string().optional().default('visitors,pageviews'),
550
+ interval: z.enum(['date', 'month']).optional().default('date')
551
+ .describe('Data point interval (default: date)'),
552
+ }).strict();
553
+
554
+ const plGetBreakdownSchema = z.object({
555
+ site_id: z.string().describe('Plausible site domain'),
556
+ property: z.enum(['event:page', 'visit:source', 'visit:country', 'visit:device', 'visit:browser', 'visit:os', 'visit:utm_medium', 'visit:utm_source', 'visit:utm_campaign'])
557
+ .describe('Property to break down by'),
558
+ period: z.enum(['day', '7d', '30d', 'month', '6mo', '12mo', 'custom']).optional().default('30d'),
559
+ date: z.string().optional(),
560
+ metrics: z.string().optional().default('visitors,pageviews'),
561
+ limit: z.number().optional().default(100).describe('Max results (default 100)'),
562
+ }).strict();
563
+
564
+ const plGetRealtimeSchema = z.object({
565
+ site_id: z.string().describe('Plausible site domain'),
566
+ }).strict();
567
+
568
+ // ── Tool Definitions ────────────────────────────────────────────
569
+
570
+ export const plausibleTools: Tool[] = [
571
+ {
572
+ name: "pl_get_stats",
573
+ description: "Gets aggregate statistics from Plausible Analytics (visitors, pageviews, bounce rate, visit duration)",
574
+ inputSchema: {
575
+ type: "object",
576
+ properties: {
577
+ site_id: { type: "string", description: "Plausible site domain (e.g., mysite.com)" },
578
+ period: { type: "string", enum: ["day", "7d", "30d", "month", "6mo", "12mo", "custom"], description: "Time period (default: 30d)" },
579
+ date: { type: "string", description: "Date for custom period (YYYY-MM-DD or range)" },
580
+ metrics: { type: "string", description: "Comma-separated metrics (default: visitors,pageviews,bounce_rate,visit_duration)" },
581
+ },
582
+ required: ["site_id"],
583
+ },
584
+ },
585
+ {
586
+ name: "pl_get_timeseries",
587
+ description: "Gets time-series statistics from Plausible (daily/monthly data points)",
588
+ inputSchema: {
589
+ type: "object",
590
+ properties: {
591
+ site_id: { type: "string", description: "Plausible site domain" },
592
+ period: { type: "string", enum: ["day", "7d", "30d", "month", "6mo", "12mo", "custom"], description: "Time period" },
593
+ date: { type: "string", description: "Date for custom period" },
594
+ metrics: { type: "string", description: "Comma-separated metrics" },
595
+ interval: { type: "string", enum: ["date", "month"], description: "Data interval (default: date)" },
596
+ },
597
+ required: ["site_id"],
598
+ },
599
+ },
600
+ {
601
+ name: "pl_get_breakdown",
602
+ description: "Gets breakdown statistics from Plausible by property (page, source, country, device, etc.)",
603
+ inputSchema: {
604
+ type: "object",
605
+ properties: {
606
+ site_id: { type: "string", description: "Plausible site domain" },
607
+ property: { type: "string", enum: ["event:page", "visit:source", "visit:country", "visit:device", "visit:browser", "visit:os", "visit:utm_medium", "visit:utm_source", "visit:utm_campaign"], description: "Breakdown property" },
608
+ period: { type: "string", enum: ["day", "7d", "30d", "month", "6mo", "12mo", "custom"], description: "Time period" },
609
+ date: { type: "string", description: "Date for custom period" },
610
+ metrics: { type: "string", description: "Comma-separated metrics" },
611
+ limit: { type: "number", description: "Max results (default 100)" },
612
+ },
613
+ required: ["site_id", "property"],
614
+ },
615
+ },
616
+ {
617
+ name: "pl_get_realtime",
618
+ description: "Gets the current number of visitors on the site from Plausible",
619
+ inputSchema: {
620
+ type: "object",
621
+ properties: {
622
+ site_id: { type: "string", description: "Plausible site domain" },
623
+ },
624
+ required: ["site_id"],
625
+ },
626
+ },
627
+ ];
628
+
629
+ // ── Handlers ────────────────────────────────────────────────────
630
+
631
+ export const plausibleHandlers: Record<string, Function> = {
632
+ pl_get_stats: async (params: z.infer<typeof plGetStatsSchema>) => {
633
+ if (!hasPlausible()) {
634
+ return { toolResult: { isError: true, content: [{ type: "text", text: "Plausible not configured. Add plausible_api_key to WP_SITES_CONFIG." }] } };
635
+ }
636
+ try {
637
+ const { site_id, period, date, metrics } = params;
638
+ const queryParams: Record<string, any> = { site_id, period: period || '30d' };
639
+ if (date) queryParams.date = date;
640
+ if (metrics) queryParams.metrics = metrics;
641
+ const response = await makePlausibleRequest('GET', 'stats/aggregate', queryParams);
642
+ return { toolResult: { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] } };
643
+ } catch (error: any) {
644
+ const errorMessage = error.response?.data?.error || error.message;
645
+ return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting Plausible stats: ${errorMessage}` }] } };
646
+ }
647
+ },
648
+
649
+ pl_get_timeseries: async (params: z.infer<typeof plGetTimeseriesSchema>) => {
650
+ if (!hasPlausible()) {
651
+ return { toolResult: { isError: true, content: [{ type: "text", text: "Plausible not configured. Add plausible_api_key to WP_SITES_CONFIG." }] } };
652
+ }
653
+ try {
654
+ const { site_id, period, date, metrics, interval } = params;
655
+ const queryParams: Record<string, any> = { site_id, period: period || '30d' };
656
+ if (date) queryParams.date = date;
657
+ if (metrics) queryParams.metrics = metrics;
658
+ if (interval) queryParams.interval = interval;
659
+ const response = await makePlausibleRequest('GET', 'stats/timeseries', queryParams);
660
+ return { toolResult: { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] } };
661
+ } catch (error: any) {
662
+ const errorMessage = error.response?.data?.error || error.message;
663
+ return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting Plausible timeseries: ${errorMessage}` }] } };
664
+ }
665
+ },
666
+
667
+ pl_get_breakdown: async (params: z.infer<typeof plGetBreakdownSchema>) => {
668
+ if (!hasPlausible()) {
669
+ return { toolResult: { isError: true, content: [{ type: "text", text: "Plausible not configured. Add plausible_api_key to WP_SITES_CONFIG." }] } };
670
+ }
671
+ try {
672
+ const { site_id, property, period, date, metrics, limit } = params;
673
+ const queryParams: Record<string, any> = { site_id, property, period: period || '30d' };
674
+ if (date) queryParams.date = date;
675
+ if (metrics) queryParams.metrics = metrics;
676
+ if (limit) queryParams.limit = limit;
677
+ const response = await makePlausibleRequest('GET', 'stats/breakdown', queryParams);
678
+ return { toolResult: { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] } };
679
+ } catch (error: any) {
680
+ const errorMessage = error.response?.data?.error || error.message;
681
+ return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting Plausible breakdown: ${errorMessage}` }] } };
682
+ }
683
+ },
684
+
685
+ pl_get_realtime: async (params: z.infer<typeof plGetRealtimeSchema>) => {
686
+ if (!hasPlausible()) {
687
+ return { toolResult: { isError: true, content: [{ type: "text", text: "Plausible not configured. Add plausible_api_key to WP_SITES_CONFIG." }] } };
688
+ }
689
+ try {
690
+ const { site_id } = params;
691
+ const response = await makePlausibleRequest('GET', 'stats/realtime/visitors', { site_id });
692
+ return { toolResult: { content: [{ type: "text", text: JSON.stringify({ visitors: response }, null, 2) }] } };
693
+ } catch (error: any) {
694
+ const errorMessage = error.response?.data?.error || error.message;
695
+ return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting Plausible realtime: ${errorMessage}` }] } };
696
+ }
697
+ },
698
+ };
699
+ ```
700
+
701
+ **Step 2: Build**
702
+
703
+ ```bash
704
+ cd servers/wp-rest-bridge && npx tsc
705
+ ```
706
+
707
+ **Step 3: Commit**
708
+
709
+ ```bash
710
+ git add servers/wp-rest-bridge/src/tools/plausible.ts
711
+ git commit -m "feat(wp-rest-bridge): add Plausible Analytics tool file (4 tools)
712
+
713
+ - pl_get_stats: aggregate stats (visitors, pageviews, bounce rate)
714
+ - pl_get_timeseries: daily/monthly data points
715
+ - pl_get_breakdown: by page, source, country, device, UTM
716
+ - pl_get_realtime: current visitor count
717
+ - Uses Plausible REST API v1 with Bearer token auth
718
+
719
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
720
+ ```
721
+
722
+ ---
723
+
724
+ ### Task 4: Create CWV tool file (4 tools)
725
+
726
+ **Files:**
727
+ - Create: `servers/wp-rest-bridge/src/tools/cwv.ts`
728
+
729
+ **Step 1: Write the CWV tool file**
730
+
731
+ ```typescript
732
+ // src/tools/cwv.ts
733
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
734
+ import { hasGoogleApiKey, getGoogleApiKey } from '../wordpress.js';
735
+ import axios from 'axios';
736
+ import { z } from 'zod';
737
+
738
+ // ── Zod Schemas ─────────────────────────────────────────────────
739
+
740
+ const cwvAnalyzeUrlSchema = z.object({
741
+ url: z.string().describe('URL to analyze'),
742
+ strategy: z.enum(['mobile', 'desktop']).optional().default('mobile')
743
+ .describe('Analysis strategy (default: mobile)'),
744
+ categories: z.array(z.enum(['performance', 'accessibility', 'best-practices', 'seo'])).optional()
745
+ .describe('Lighthouse categories to include (default: performance only)'),
746
+ }).strict();
747
+
748
+ const cwvBatchAnalyzeSchema = z.object({
749
+ urls: z.array(z.string()).describe('Array of URLs to analyze (max 10)'),
750
+ strategy: z.enum(['mobile', 'desktop']).optional().default('mobile'),
751
+ }).strict();
752
+
753
+ const cwvGetFieldDataSchema = z.object({
754
+ url: z.string().optional().describe('Specific URL for field data (omit for origin-level data)'),
755
+ origin: z.string().optional().describe('Origin URL (e.g., https://mysite.com)'),
756
+ form_factor: z.enum(['PHONE', 'DESKTOP', 'TABLET', 'ALL_FORM_FACTORS']).optional().default('ALL_FORM_FACTORS'),
757
+ }).strict();
758
+
759
+ const cwvComparePagesSchema = z.object({
760
+ urls: z.array(z.string()).describe('URLs to compare (2-5)'),
761
+ strategy: z.enum(['mobile', 'desktop']).optional().default('mobile'),
762
+ }).strict();
763
+
764
+ // ── Tool Definitions ────────────────────────────────────────────
765
+
766
+ export const cwvTools: Tool[] = [
767
+ {
768
+ name: "cwv_analyze_url",
769
+ description: "Analyzes Core Web Vitals for a URL via PageSpeed Insights (LCP, INP, CLS, FCP, TTFB)",
770
+ inputSchema: {
771
+ type: "object",
772
+ properties: {
773
+ url: { type: "string", description: "URL to analyze" },
774
+ strategy: { type: "string", enum: ["mobile", "desktop"], description: "Strategy (default: mobile)" },
775
+ categories: { type: "array", items: { type: "string", enum: ["performance", "accessibility", "best-practices", "seo"] }, description: "Categories to include" },
776
+ },
777
+ required: ["url"],
778
+ },
779
+ },
780
+ {
781
+ name: "cwv_batch_analyze",
782
+ description: "Analyzes Core Web Vitals for multiple URLs (max 10) via PageSpeed Insights",
783
+ inputSchema: {
784
+ type: "object",
785
+ properties: {
786
+ urls: { type: "array", items: { type: "string" }, description: "URLs to analyze (max 10)" },
787
+ strategy: { type: "string", enum: ["mobile", "desktop"], description: "Strategy (default: mobile)" },
788
+ },
789
+ required: ["urls"],
790
+ },
791
+ },
792
+ {
793
+ name: "cwv_get_field_data",
794
+ description: "Gets real-user CWV field data from Chrome UX Report (28-day aggregate)",
795
+ inputSchema: {
796
+ type: "object",
797
+ properties: {
798
+ url: { type: "string", description: "Specific URL (omit for origin-level)" },
799
+ origin: { type: "string", description: "Origin URL (e.g., https://mysite.com)" },
800
+ form_factor: { type: "string", enum: ["PHONE", "DESKTOP", "TABLET", "ALL_FORM_FACTORS"], description: "Form factor (default: ALL)" },
801
+ },
802
+ },
803
+ },
804
+ {
805
+ name: "cwv_compare_pages",
806
+ description: "Compares Core Web Vitals across multiple pages and ranks optimization priority",
807
+ inputSchema: {
808
+ type: "object",
809
+ properties: {
810
+ urls: { type: "array", items: { type: "string" }, description: "URLs to compare (2-5)" },
811
+ strategy: { type: "string", enum: ["mobile", "desktop"], description: "Strategy (default: mobile)" },
812
+ },
813
+ required: ["urls"],
814
+ },
815
+ },
816
+ ];
817
+
818
+ // ── Handlers ────────────────────────────────────────────────────
819
+
820
+ const PSI_BASE = 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed';
821
+ const CRUX_BASE = 'https://chromeuxreport.googleapis.com/v1/records:queryRecord';
822
+
823
+ function extractCWV(lighthouse: any) {
824
+ const audits = lighthouse?.audits || {};
825
+ return {
826
+ lcp: audits['largest-contentful-paint']?.numericValue,
827
+ fcp: audits['first-contentful-paint']?.numericValue,
828
+ cls: audits['cumulative-layout-shift']?.numericValue,
829
+ inp: audits['interaction-to-next-paint']?.numericValue,
830
+ ttfb: audits['server-response-time']?.numericValue,
831
+ performance_score: lighthouse?.categories?.performance?.score,
832
+ };
833
+ }
834
+
835
+ export const cwvHandlers: Record<string, Function> = {
836
+ cwv_analyze_url: async (params: z.infer<typeof cwvAnalyzeUrlSchema>) => {
837
+ if (!hasGoogleApiKey()) {
838
+ return { toolResult: { isError: true, content: [{ type: "text", text: "Google API key not configured. Add google_api_key to WP_SITES_CONFIG." }] } };
839
+ }
840
+ try {
841
+ const { url, strategy, categories } = params;
842
+ const apiKey = getGoogleApiKey();
843
+ const queryParams: Record<string, any> = { url, key: apiKey, strategy: strategy || 'mobile' };
844
+ if (categories) {
845
+ categories.forEach(c => { queryParams[`category`] = c; });
846
+ } else {
847
+ queryParams.category = 'performance';
848
+ }
849
+ const response = await axios.get(PSI_BASE, { params: queryParams, timeout: 60000 });
850
+ const cwv = extractCWV(response.data.lighthouseResult);
851
+ const result = { url, strategy: strategy || 'mobile', cwv, fieldData: response.data.loadingExperience || null };
852
+ return { toolResult: { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] } };
853
+ } catch (error: any) {
854
+ const errorMessage = error.response?.data?.error?.message || error.message;
855
+ return { toolResult: { isError: true, content: [{ type: "text", text: `Error analyzing URL: ${errorMessage}` }] } };
856
+ }
857
+ },
858
+
859
+ cwv_batch_analyze: async (params: z.infer<typeof cwvBatchAnalyzeSchema>) => {
860
+ if (!hasGoogleApiKey()) {
861
+ return { toolResult: { isError: true, content: [{ type: "text", text: "Google API key not configured. Add google_api_key to WP_SITES_CONFIG." }] } };
862
+ }
863
+ try {
864
+ const { urls, strategy } = params;
865
+ const limitedUrls = urls.slice(0, 10);
866
+ const apiKey = getGoogleApiKey();
867
+ const results = [];
868
+ for (const url of limitedUrls) {
869
+ try {
870
+ const response = await axios.get(PSI_BASE, {
871
+ params: { url, key: apiKey, strategy: strategy || 'mobile', category: 'performance' },
872
+ timeout: 60000,
873
+ });
874
+ const cwv = extractCWV(response.data.lighthouseResult);
875
+ results.push({ url, cwv, status: 'ok' });
876
+ } catch (err: any) {
877
+ results.push({ url, status: 'error', error: err.response?.data?.error?.message || err.message });
878
+ }
879
+ }
880
+ return { toolResult: { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] } };
881
+ } catch (error: any) {
882
+ return { toolResult: { isError: true, content: [{ type: "text", text: `Error in batch analysis: ${error.message}` }] } };
883
+ }
884
+ },
885
+
886
+ cwv_get_field_data: async (params: z.infer<typeof cwvGetFieldDataSchema>) => {
887
+ if (!hasGoogleApiKey()) {
888
+ return { toolResult: { isError: true, content: [{ type: "text", text: "Google API key not configured. Add google_api_key to WP_SITES_CONFIG." }] } };
889
+ }
890
+ try {
891
+ const { url, origin, form_factor } = params;
892
+ const apiKey = getGoogleApiKey();
893
+ const requestBody: Record<string, any> = {};
894
+ if (url) requestBody.url = url;
895
+ else if (origin) requestBody.origin = origin;
896
+ else return { toolResult: { isError: true, content: [{ type: "text", text: "Provide either url or origin parameter." }] } };
897
+ if (form_factor && form_factor !== 'ALL_FORM_FACTORS') requestBody.formFactor = form_factor;
898
+ const response = await axios.post(`${CRUX_BASE}?key=${apiKey}`, requestBody, { timeout: 30000 });
899
+ return { toolResult: { content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }] } };
900
+ } catch (error: any) {
901
+ const errorMessage = error.response?.data?.error?.message || error.message;
902
+ return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting CrUX data: ${errorMessage}` }] } };
903
+ }
904
+ },
905
+
906
+ cwv_compare_pages: async (params: z.infer<typeof cwvComparePagesSchema>) => {
907
+ if (!hasGoogleApiKey()) {
908
+ return { toolResult: { isError: true, content: [{ type: "text", text: "Google API key not configured. Add google_api_key to WP_SITES_CONFIG." }] } };
909
+ }
910
+ try {
911
+ const { urls, strategy } = params;
912
+ const limitedUrls = urls.slice(0, 5);
913
+ const apiKey = getGoogleApiKey();
914
+ const results = [];
915
+ for (const url of limitedUrls) {
916
+ try {
917
+ const response = await axios.get(PSI_BASE, {
918
+ params: { url, key: apiKey, strategy: strategy || 'mobile', category: 'performance' },
919
+ timeout: 60000,
920
+ });
921
+ const cwv = extractCWV(response.data.lighthouseResult);
922
+ results.push({ url, cwv });
923
+ } catch (err: any) {
924
+ results.push({ url, cwv: null, error: err.message });
925
+ }
926
+ }
927
+ // Rank by worst LCP
928
+ const ranked = results
929
+ .filter(r => r.cwv)
930
+ .sort((a, b) => (b.cwv!.lcp || 0) - (a.cwv!.lcp || 0))
931
+ .map((r, i) => ({ ...r, priority: i + 1 }));
932
+ return { toolResult: { content: [{ type: "text", text: JSON.stringify({ comparison: ranked, worst_first: true }, null, 2) }] } };
933
+ } catch (error: any) {
934
+ return { toolResult: { isError: true, content: [{ type: "text", text: `Error comparing pages: ${error.message}` }] } };
935
+ }
936
+ },
937
+ };
938
+ ```
939
+
940
+ **Step 2: Build**
941
+
942
+ ```bash
943
+ cd servers/wp-rest-bridge && npx tsc
944
+ ```
945
+
946
+ **Step 3: Commit**
947
+
948
+ ```bash
949
+ git add servers/wp-rest-bridge/src/tools/cwv.ts
950
+ git commit -m "feat(wp-rest-bridge): add Core Web Vitals tool file (4 tools)
951
+
952
+ - cwv_analyze_url: PageSpeed Insights single URL (LCP, INP, CLS, FCP, TTFB)
953
+ - cwv_batch_analyze: batch analysis up to 10 URLs
954
+ - cwv_get_field_data: Chrome UX Report real-user data (28-day aggregate)
955
+ - cwv_compare_pages: compare CWV across pages with priority ranking
956
+ - Uses Google API key (no OAuth) for PageSpeed + CrUX APIs
957
+
958
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
959
+ ```
960
+
961
+ ---
962
+
963
+ ### Task 5: Register analytics tools in index.ts and build
964
+
965
+ **Files:**
966
+ - Modify: `servers/wp-rest-bridge/src/tools/index.ts`
967
+
968
+ **Step 1: Add imports**
969
+
970
+ Add after the `gscTools` import line:
971
+
972
+ ```typescript
973
+ import { ga4Tools, ga4Handlers } from './ga4.js';
974
+ import { plausibleTools, plausibleHandlers } from './plausible.js';
975
+ import { cwvTools, cwvHandlers } from './cwv.js';
976
+ ```
977
+
978
+ **Step 2: Add to allTools array**
979
+
980
+ Add after `...gscTools`:
981
+
982
+ ```typescript
983
+ ...ga4Tools, // 6 tools
984
+ ...plausibleTools, // 4 tools
985
+ ...cwvTools, // 4 tools
986
+ ```
987
+
988
+ **Step 3: Add to toolHandlers object**
989
+
990
+ Add after `...gscHandlers`:
991
+
992
+ ```typescript
993
+ ...ga4Handlers,
994
+ ...plausibleHandlers,
995
+ ...cwvHandlers,
996
+ ```
997
+
998
+ **Step 4: Build**
999
+
1000
+ ```bash
1001
+ cd servers/wp-rest-bridge && npx tsc
1002
+ ```
1003
+
1004
+ **Step 5: Commit**
1005
+
1006
+ ```bash
1007
+ git add servers/wp-rest-bridge/src/tools/index.ts
1008
+ git commit -m "feat(wp-rest-bridge): register GA4 + Plausible + CWV tools in index
1009
+
1010
+ 14 new tools registered (6 GA4 + 4 Plausible + 4 CWV), total 125
1011
+
1012
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
1013
+ ```
1014
+
1015
+ ---
1016
+
1017
+ ### Task 6: Create analytics detection script
1018
+
1019
+ **Files:**
1020
+ - Create: `skills/wp-analytics/scripts/analytics_inspect.mjs`
1021
+
1022
+ **Step 1: Write the detection script**
1023
+
1024
+ Follow the exact pattern from `search_console_inspect.mjs`: helpers, detectors, main(), JSON report, exit code.
1025
+
1026
+ ```javascript
1027
+ /**
1028
+ * analytics_inspect.mjs — Detect analytics configuration readiness.
1029
+ *
1030
+ * Checks WP_SITES_CONFIG for GA4, Plausible, and Google API key credentials.
1031
+ * Scans for analytics plugins and tracking code.
1032
+ *
1033
+ * Usage:
1034
+ * node analytics_inspect.mjs [--cwd=/path/to/project]
1035
+ *
1036
+ * Exit codes:
1037
+ * 0 — analytics configuration found
1038
+ * 1 — no analytics configuration found
1039
+ */
1040
+
1041
+ import { readFileSync, existsSync, readdirSync } from 'node:fs';
1042
+ import { join, resolve } from 'node:path';
1043
+ import { argv, stdout, exit } from 'node:process';
1044
+
1045
+ // ---------------------------------------------------------------------------
1046
+ // Helpers
1047
+ // ---------------------------------------------------------------------------
1048
+
1049
+ function readFileSafe(filePath) {
1050
+ try { return readFileSync(filePath, 'utf-8'); } catch { return null; }
1051
+ }
1052
+
1053
+ function existsSafe(filePath) {
1054
+ try { return existsSync(filePath); } catch { return false; }
1055
+ }
1056
+
1057
+ function globDir(dirPath) {
1058
+ try { return readdirSync(dirPath); } catch { return []; }
1059
+ }
1060
+
1061
+ // ---------------------------------------------------------------------------
1062
+ // Detectors
1063
+ // ---------------------------------------------------------------------------
1064
+
1065
+ function detectGA4Config() {
1066
+ const ga4 = { configured: false, indicators: [] };
1067
+ const raw = process.env.WP_SITES_CONFIG;
1068
+ if (!raw) return ga4;
1069
+
1070
+ let sites;
1071
+ try { sites = JSON.parse(raw); } catch { return ga4; }
1072
+ if (!Array.isArray(sites)) return ga4;
1073
+
1074
+ for (const site of sites) {
1075
+ const label = site.id || site.url || 'unknown';
1076
+ if (site.ga4_property_id) {
1077
+ ga4.configured = true;
1078
+ ga4.indicators.push(`ga4_property_id configured for ${label}`);
1079
+ }
1080
+ if (site.ga4_service_account_key) {
1081
+ ga4.indicators.push(`ga4_service_account_key configured for ${label}`);
1082
+ }
1083
+ }
1084
+ return ga4;
1085
+ }
1086
+
1087
+ function detectPlausibleConfig() {
1088
+ const pl = { configured: false, indicators: [] };
1089
+ const raw = process.env.WP_SITES_CONFIG;
1090
+ if (!raw) return pl;
1091
+
1092
+ let sites;
1093
+ try { sites = JSON.parse(raw); } catch { return pl; }
1094
+ if (!Array.isArray(sites)) return pl;
1095
+
1096
+ for (const site of sites) {
1097
+ const label = site.id || site.url || 'unknown';
1098
+ if (site.plausible_api_key) {
1099
+ pl.configured = true;
1100
+ pl.indicators.push(`plausible_api_key configured for ${label}`);
1101
+ }
1102
+ if (site.plausible_base_url) {
1103
+ pl.indicators.push(`plausible_base_url: ${site.plausible_base_url}`);
1104
+ }
1105
+ }
1106
+ return pl;
1107
+ }
1108
+
1109
+ function detectGoogleApiKey() {
1110
+ const cwv = { configured: false, indicators: [] };
1111
+ const raw = process.env.WP_SITES_CONFIG;
1112
+ if (!raw) return cwv;
1113
+
1114
+ let sites;
1115
+ try { sites = JSON.parse(raw); } catch { return cwv; }
1116
+ if (!Array.isArray(sites)) return cwv;
1117
+
1118
+ for (const site of sites) {
1119
+ const label = site.id || site.url || 'unknown';
1120
+ if (site.google_api_key) {
1121
+ cwv.configured = true;
1122
+ cwv.indicators.push(`google_api_key configured for ${label}`);
1123
+ }
1124
+ }
1125
+ return cwv;
1126
+ }
1127
+
1128
+ function detectAnalyticsPlugins(cwd) {
1129
+ const indicators = [];
1130
+ const pluginsDir = join(cwd, 'wp-content', 'plugins');
1131
+ const plugins = globDir(pluginsDir);
1132
+
1133
+ const analyticsPlugins = [
1134
+ 'google-analytics-for-wordpress',
1135
+ 'google-site-kit',
1136
+ 'wp-google-analytics-events',
1137
+ 'plausible-analytics',
1138
+ 'koko-analytics',
1139
+ 'matomo',
1140
+ 'independent-analytics',
1141
+ ];
1142
+
1143
+ for (const plugin of analyticsPlugins) {
1144
+ if (plugins.includes(plugin)) {
1145
+ indicators.push(`plugin: ${plugin}`);
1146
+ }
1147
+ }
1148
+ return { found: indicators.length > 0, indicators };
1149
+ }
1150
+
1151
+ // ---------------------------------------------------------------------------
1152
+ // Main
1153
+ // ---------------------------------------------------------------------------
1154
+
1155
+ function main() {
1156
+ const cwdArg = argv.find(a => a.startsWith('--cwd='));
1157
+ const cwd = cwdArg ? resolve(cwdArg.split('=')[1]) : process.cwd();
1158
+
1159
+ const ga4 = detectGA4Config();
1160
+ const plausible = detectPlausibleConfig();
1161
+ const googleApiKey = detectGoogleApiKey();
1162
+ const plugins = detectAnalyticsPlugins(cwd);
1163
+
1164
+ const anyConfigured = ga4.configured || plausible.configured || googleApiKey.configured || plugins.found;
1165
+
1166
+ const report = {
1167
+ analytics_configured: anyConfigured,
1168
+ ga4: ga4,
1169
+ plausible: plausible,
1170
+ google_api_key: googleApiKey,
1171
+ analytics_plugins: plugins,
1172
+ cwd,
1173
+ };
1174
+
1175
+ stdout.write(JSON.stringify(report, null, 2) + '\n');
1176
+ exit(anyConfigured ? 0 : 1);
1177
+ }
1178
+
1179
+ main();
1180
+ ```
1181
+
1182
+ **Step 2: Commit**
1183
+
1184
+ ```bash
1185
+ git add skills/wp-analytics/scripts/analytics_inspect.mjs
1186
+ git commit -m "feat(wp-analytics): add analytics detection script
1187
+
1188
+ Detects GA4, Plausible, Google API key config in WP_SITES_CONFIG.
1189
+ Scans for analytics plugins (Site Kit, Plausible, Matomo, etc.).
1190
+ JSON report to stdout, exit 0 if any config found.
1191
+
1192
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
1193
+ ```
1194
+
1195
+ ---
1196
+
1197
+ ### Task 7: Create wp-analytics skill (SKILL.md + 5 references)
1198
+
1199
+ **Files:**
1200
+ - Create: `skills/wp-analytics/SKILL.md`
1201
+ - Create: `skills/wp-analytics/references/ga4-integration.md`
1202
+ - Create: `skills/wp-analytics/references/plausible-setup.md`
1203
+ - Create: `skills/wp-analytics/references/cwv-monitoring.md`
1204
+ - Create: `skills/wp-analytics/references/analytics-dashboards.md`
1205
+ - Create: `skills/wp-analytics/references/traffic-attribution.md`
1206
+
1207
+ **Step 1: Write SKILL.md**
1208
+
1209
+ Follow the 10-section pattern from existing skills (frontmatter, overview, detection, prerequisites, procedures, references, recommended agent, related skills, cross-references, troubleshooting).
1210
+
1211
+ The SKILL.md should define:
1212
+ - **Frontmatter**: name `wp-analytics`, version 1.0.0, description, tags
1213
+ - **Section 1 — Overview**: Unified analytics (GA4 + Plausible + CWV) for WordPress
1214
+ - **Section 2 — Detection**: Run `analytics_inspect.mjs`
1215
+ - **Section 3 — Prerequisites**: GA4 Data API enabled, Service Account, Plausible API key, Google API key
1216
+ - **Section 4 — Procedures**: 6 procedures (GA4 setup, Plausible setup, CWV analysis, traffic report, performance dashboard, cross-platform comparison)
1217
+ - **Section 5 — MCP Tools Reference**: 14 tools table (6 GA4 + 4 PL + 4 CWV)
1218
+ - **Section 6 — Reference Files**: 5 files listed
1219
+ - **Section 7 — Recommended Agent**: wp-monitoring-agent
1220
+ - **Section 8 — Related Skills**: wp-search-console, wp-content-optimization, wp-content-attribution, wp-monitoring
1221
+ - **Section 9 — Cross-references**: Links to related skills
1222
+ - **Section 10 — Troubleshooting**: Common issues (quota limits, missing scopes, field data not available)
1223
+
1224
+ **Step 2: Write 5 reference files**
1225
+
1226
+ Each reference file should be 50-100 lines covering the topic from the design doc table.
1227
+
1228
+ **Step 3: Commit**
1229
+
1230
+ ```bash
1231
+ git add skills/wp-analytics/
1232
+ git commit -m "feat(wp-analytics): add skill with 5 reference files
1233
+
1234
+ Unified analytics skill covering GA4, Plausible, and Core Web Vitals.
1235
+ 14 MCP tools documented across 6 procedures.
1236
+ References: ga4-integration, plausible-setup, cwv-monitoring,
1237
+ analytics-dashboards, traffic-attribution.
1238
+
1239
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
1240
+ ```
1241
+
1242
+ ---
1243
+
1244
+ ### Task 8: Update wp-monitoring-agent (Analytics Monitoring + CWV)
1245
+
1246
+ **Files:**
1247
+ - Modify: `agents/wp-monitoring-agent.md`
1248
+
1249
+ **Step 1: Add Analytics Monitoring procedure**
1250
+
1251
+ Add after "Procedure 7: Fleet Monitoring" section:
1252
+
1253
+ ```markdown
1254
+ ### Procedure 8: Analytics Monitoring (Performance Dashboard)
1255
+
1256
+ 1. Fetch traffic data from GA4 (`ga4_top_pages`, `ga4_traffic_sources`) or Plausible (`pl_get_stats`, `pl_get_breakdown`)
1257
+ 2. Fetch CWV for top pages (`cwv_batch_analyze`)
1258
+ 3. Fetch keyword data from GSC (`gsc_top_queries`) if available
1259
+ 4. Correlate: pages with high traffic + Poor CWV = optimization priority
1260
+ 5. Generate Performance Dashboard Report
1261
+ 6. If CWV Poor on top pages → recommend delegation to `wp-performance-optimizer`
1262
+
1263
+ ### Procedure 9: CWV Trend Check
1264
+
1265
+ 1. Run `cwv_analyze_url` on homepage and top landing pages
1266
+ 2. Compare with CWV thresholds (Good: LCP<2.5s, INP<200ms, CLS<0.1)
1267
+ 3. If available, fetch field data via `cwv_get_field_data` for real-user metrics
1268
+ 4. Report status (Good/Needs Improvement/Poor) per metric
1269
+ 5. If any metric is Poor → alert with specific pages and metrics
1270
+ ```
1271
+
1272
+ **Step 2: Update Available Tools section**
1273
+
1274
+ Add after WP REST Bridge tools:
1275
+
1276
+ ```markdown
1277
+ ### Analytics MCP Tools (`mcp__wp-rest-bridge__ga4_*`, `mcp__wp-rest-bridge__pl_*`, `mcp__wp-rest-bridge__cwv_*`)
1278
+ - **GA4**: `ga4_run_report`, `ga4_get_realtime`, `ga4_top_pages`, `ga4_traffic_sources`, `ga4_user_demographics`, `ga4_conversion_events`
1279
+ - **Plausible**: `pl_get_stats`, `pl_get_timeseries`, `pl_get_breakdown`, `pl_get_realtime`
1280
+ - **CWV**: `cwv_analyze_url`, `cwv_batch_analyze`, `cwv_get_field_data`, `cwv_compare_pages`
1281
+ ```
1282
+
1283
+ **Step 3: Update Related Skills section**
1284
+
1285
+ Add:
1286
+ ```markdown
1287
+ - **`wp-analytics` skill** — analytics setup, traffic reports, CWV monitoring
1288
+ ```
1289
+
1290
+ **Step 4: Commit**
1291
+
1292
+ ```bash
1293
+ git add agents/wp-monitoring-agent.md
1294
+ git commit -m "feat(wp-monitoring-agent): add Analytics Monitoring + CWV procedures
1295
+
1296
+ - Procedure 8: Performance Dashboard (GA4/Plausible + CWV + GSC correlation)
1297
+ - Procedure 9: CWV Trend Check (thresholds, field data comparison)
1298
+ - Added 14 analytics MCP tools to Available Tools
1299
+ - Updated Related Skills with wp-analytics
1300
+
1301
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
1302
+ ```
1303
+
1304
+ ---
1305
+
1306
+ ### Task 9: Update router to v14 + cross-references
1307
+
1308
+ **Files:**
1309
+ - Modify: `skills/wordpress-router/SKILL.md` (version bump in frontmatter)
1310
+ - Modify: `skills/wordpress-router/references/decision-tree.md` (v14 header + new keywords + new route)
1311
+ - Modify: `skills/wp-search-console/SKILL.md` (add cross-ref to wp-analytics)
1312
+ - Modify: `skills/wp-content-attribution/SKILL.md` (add cross-ref)
1313
+ - Modify: `skills/wp-content-optimization/SKILL.md` (add cross-ref)
1314
+
1315
+ **Step 1: Update decision-tree.md**
1316
+
1317
+ - Change header from v13 to v14 (add `+ analytics`)
1318
+ - Add to Step 0 operations keywords: `Google Analytics, GA4, traffic analytics, pageviews, sessions, user analytics, Plausible, privacy analytics, Core Web Vitals, CWV, LCP, INP, CLS, PageSpeed, page speed, site speed, performance score`
1319
+ - Add new route in Step 2b after the content optimization entry:
1320
+ ```
1321
+ - **Google Analytics / GA4 / Plausible / traffic analytics / pageviews / sessions / user analytics / Core Web Vitals / CWV / PageSpeed / site speed / performance score**
1322
+ → `wp-analytics` skill + `wp-monitoring-agent` agent
1323
+ ```
1324
+
1325
+ **Step 2: Add cross-references in related skills**
1326
+
1327
+ - In `wp-search-console/SKILL.md` cross-references section, add: "Per correlare keyword GSC con traffico GA4, vedi `wp-analytics`"
1328
+ - In `wp-content-attribution/SKILL.md`: "Per correlazione contenuto→conversione completa, combina `wp-analytics` + `wp-content-attribution`"
1329
+ - In `wp-content-optimization/SKILL.md`: "Per prioritizzare ottimizzazione con dati CWV, combina `wp-analytics` + `wp-content-optimization`"
1330
+
1331
+ **Step 3: Commit**
1332
+
1333
+ ```bash
1334
+ git add skills/wordpress-router/ skills/wp-search-console/SKILL.md skills/wp-content-attribution/SKILL.md skills/wp-content-optimization/SKILL.md
1335
+ git commit -m "feat(router): update to v14 with analytics routing
1336
+
1337
+ - Add GA4, Plausible, CWV keywords to Step 0 operations
1338
+ - Add wp-analytics + wp-monitoring-agent route in Step 2b
1339
+ - Add cross-references in wp-search-console, wp-content-attribution,
1340
+ wp-content-optimization
1341
+
1342
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
1343
+ ```
1344
+
1345
+ ---
1346
+
1347
+ ### Task 10: Version bump to v2.7.0 + CHANGELOG
1348
+
1349
+ **Files:**
1350
+ - Modify: `package.json` (version, description)
1351
+ - Modify: `CHANGELOG.md` (add v2.7.0 entry)
1352
+
1353
+ **Step 1: Bump version to 2.7.0**
1354
+
1355
+ In `package.json`, change `"version": "2.6.0"` to `"version": "2.7.0"` and update description to mention analytics count.
1356
+
1357
+ **Step 2: Add CHANGELOG entry**
1358
+
1359
+ Add at top of CHANGELOG (after header), following existing format:
1360
+
1361
+ ```markdown
1362
+ ## [2.7.0] — 2026-03-XX
1363
+
1364
+ ### Added — Analytics (WCOP Tier 4a)
1365
+ - **wp-analytics skill** — unified analytics: GA4, Plausible, Core Web Vitals
1366
+ - **14 new MCP tools**: 6 GA4 (`ga4_run_report`, `ga4_get_realtime`, `ga4_top_pages`, `ga4_traffic_sources`, `ga4_user_demographics`, `ga4_conversion_events`), 4 Plausible (`pl_get_stats`, `pl_get_timeseries`, `pl_get_breakdown`, `pl_get_realtime`), 4 CWV (`cwv_analyze_url`, `cwv_batch_analyze`, `cwv_get_field_data`, `cwv_compare_pages`)
1367
+ - **5 reference files**: ga4-integration, plausible-setup, cwv-monitoring, analytics-dashboards, traffic-attribution
1368
+ - **Detection script**: `analytics_inspect.mjs` — detects GA4, Plausible, Google API key config
1369
+ - **SiteConfig extension**: `ga4_property_id`, `ga4_service_account_key`, `plausible_api_key`, `plausible_base_url`, `google_api_key`
1370
+
1371
+ ### Changed
1372
+ - **wp-monitoring-agent**: added Procedure 8 (Analytics Monitoring) and Procedure 9 (CWV Trend Check) with 14 analytics MCP tools
1373
+ - **Router v14**: added GA4, Plausible, CWV keywords and route
1374
+ - **Cross-references**: wp-search-console, wp-content-attribution, wp-content-optimization → wp-analytics
1375
+
1376
+ ### Metrics
1377
+ - Skills: 37 (+1) | MCP tools: 125 (+14) | Reference files: 183 (+5) | Detection scripts: 25 (+1)
1378
+ ```
1379
+
1380
+ **Step 3: Build final**
1381
+
1382
+ ```bash
1383
+ cd servers/wp-rest-bridge && npx tsc
1384
+ ```
1385
+
1386
+ **Step 4: Commit**
1387
+
1388
+ ```bash
1389
+ git add package.json CHANGELOG.md
1390
+ git commit -m "chore: bump version to v2.7.0 — Analytics (GA4 + Plausible + CWV)
1391
+
1392
+ WCOP Tier 4a: 14 new MCP tools, wp-analytics skill, router v14.
1393
+ Skills: 37 | MCP tools: 125 | Agents: 12 | Detection scripts: 25
1394
+
1395
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
1396
+ ```
1397
+
1398
+ ---
1399
+
1400
+ ## Release 2: v2.8.0 — Smart Alerting (Slack + Email)
1401
+
1402
+ ### Task 11: Create Slack tool file (3 tools)
1403
+
1404
+ **Files:**
1405
+ - Modify: `servers/wp-rest-bridge/src/wordpress.ts` (SiteConfig + init + has/make)
1406
+ - Create: `servers/wp-rest-bridge/src/tools/slack.ts`
1407
+
1408
+ **Step 1: Add SiteConfig fields for Slack**
1409
+
1410
+ In `wordpress.ts` SiteConfig, add after `google_api_key`:
1411
+
1412
+ ```typescript
1413
+ // Slack Integration (optional)
1414
+ slack_webhook_url?: string; // Incoming Webhook URL for alerts
1415
+ slack_bot_token?: string; // Bot Token (xoxb-...) for advanced messaging
1416
+ ```
1417
+
1418
+ **Step 2: Add Slack client map and init**
1419
+
1420
+ Add `slackSiteClients` map. Add `initSlackClient` function (axios with Bearer token). Add init call in loop for `slack_bot_token`.
1421
+
1422
+ **Step 3: Add has/make functions**
1423
+
1424
+ ```typescript
1425
+ export function hasSlackWebhook(siteId?: string): boolean { ... }
1426
+ export function hasSlackBot(siteId?: string): boolean { ... }
1427
+ export async function makeSlackBotRequest(method, endpoint, data, siteId): Promise<any> { ... }
1428
+ ```
1429
+
1430
+ **Step 4: Write `slack.ts` tool file**
1431
+
1432
+ 3 tools: `slack_send_alert` (POST to webhook URL), `slack_send_message` (Web API chat.postMessage), `slack_list_channels` (Web API conversations.list).
1433
+
1434
+ **Step 5: Register in index.ts**
1435
+
1436
+ Add imports and spread into allTools/toolHandlers.
1437
+
1438
+ **Step 6: Build and commit**
1439
+
1440
+ ```bash
1441
+ cd servers/wp-rest-bridge && npx tsc
1442
+ git add servers/wp-rest-bridge/
1443
+ git commit -m "feat(wp-rest-bridge): add Slack tool file (3 tools)
1444
+
1445
+ - slack_send_alert: send via incoming webhook (zero-config)
1446
+ - slack_send_message: send to channel via Bot Token (Block Kit)
1447
+ - slack_list_channels: list workspace channels via Bot Token
1448
+ - SiteConfig: slack_webhook_url, slack_bot_token
1449
+ - Total MCP tools: 128
1450
+
1451
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
1452
+ ```
1453
+
1454
+ ---
1455
+
1456
+ ### Task 12: Create alerting detection script
1457
+
1458
+ **Files:**
1459
+ - Create: `skills/wp-alerting/scripts/alerting_inspect.mjs`
1460
+
1461
+ **Step 1: Write detection script**
1462
+
1463
+ Detects: Slack webhook/bot config, SendGrid config, monitoring setup, wp-cron availability.
1464
+
1465
+ **Step 2: Commit**
1466
+
1467
+ ```bash
1468
+ git add skills/wp-alerting/scripts/alerting_inspect.mjs
1469
+ git commit -m "feat(wp-alerting): add alerting detection script
1470
+
1471
+ Detects Slack webhook/bot, SendGrid, monitoring config in WP_SITES_CONFIG.
1472
+
1473
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
1474
+ ```
1475
+
1476
+ ---
1477
+
1478
+ ### Task 13: Create wp-alerting skill (SKILL.md + 4 references)
1479
+
1480
+ **Files:**
1481
+ - Create: `skills/wp-alerting/SKILL.md`
1482
+ - Create: `skills/wp-alerting/references/slack-integration.md`
1483
+ - Create: `skills/wp-alerting/references/alert-thresholds.md`
1484
+ - Create: `skills/wp-alerting/references/escalation-paths.md`
1485
+ - Create: `skills/wp-alerting/references/report-scheduling.md`
1486
+
1487
+ **Step 1: Write SKILL.md + 4 reference files**
1488
+
1489
+ Alerting is procedure-based (agent-driven, not rule engine). References define thresholds, escalation paths, Slack setup, report schedules.
1490
+
1491
+ **Step 2: Commit**
1492
+
1493
+ ```bash
1494
+ git add skills/wp-alerting/
1495
+ git commit -m "feat(wp-alerting): add skill with 4 reference files
1496
+
1497
+ Cross-cutting alerting: Slack + email (SendGrid), severity-based
1498
+ escalation, configurable thresholds, scheduled reporting.
1499
+
1500
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
1501
+ ```
1502
+
1503
+ ---
1504
+
1505
+ ### Task 14: Update wp-monitoring-agent (Alert Dispatch)
1506
+
1507
+ **Files:**
1508
+ - Modify: `agents/wp-monitoring-agent.md`
1509
+
1510
+ **Step 1: Add Alert Dispatch procedure**
1511
+
1512
+ Add Procedure 10 (Alert Dispatch) with severity classification (info/warning/critical/emergency) and routing rules (Slack-only for warning, Slack+email for critical, repeat for emergency).
1513
+
1514
+ **Step 2: Add Slack tools to Available Tools**
1515
+
1516
+ **Step 3: Commit**
1517
+
1518
+ ```bash
1519
+ git add agents/wp-monitoring-agent.md
1520
+ git commit -m "feat(wp-monitoring-agent): add Alert Dispatch procedure
1521
+
1522
+ - Procedure 10: severity classification + Slack/email routing
1523
+ - Added slack_send_alert, slack_send_message, slack_list_channels tools
1524
+ - Updated Related Skills with wp-alerting
1525
+
1526
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
1527
+ ```
1528
+
1529
+ ---
1530
+
1531
+ ### Task 15: Update router to v15 + cross-references + version bump
1532
+
1533
+ **Files:**
1534
+ - Modify: `skills/wordpress-router/references/decision-tree.md` (v15)
1535
+ - Modify: `skills/wp-monitoring/SKILL.md` (cross-ref)
1536
+ - Modify: `skills/wp-analytics/SKILL.md` (cross-ref)
1537
+ - Modify: `skills/wp-search-console/SKILL.md` (cross-ref)
1538
+ - Modify: `package.json` (2.8.0)
1539
+ - Modify: `CHANGELOG.md` (v2.8.0 entry)
1540
+
1541
+ **Step 1: Update decision-tree to v15**
1542
+
1543
+ Add alerting keywords and route in Step 2b.
1544
+
1545
+ **Step 2: Add cross-references**
1546
+
1547
+ **Step 3: Bump version and CHANGELOG**
1548
+
1549
+ **Step 4: Build and commit**
1550
+
1551
+ ```bash
1552
+ cd servers/wp-rest-bridge && npx tsc
1553
+ git add skills/ agents/ package.json CHANGELOG.md
1554
+ git commit -m "chore: bump version to v2.8.0 — Smart Alerting (Slack + Email)
1555
+
1556
+ WCOP Tier 4b: 3 new Slack MCP tools, wp-alerting skill, router v15.
1557
+ Skills: 38 | MCP tools: 128 | Agents: 12 | Detection scripts: 26
1558
+
1559
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
1560
+ ```
1561
+
1562
+ ---
1563
+
1564
+ ## Release 3: v2.9.0 — Automated Workflows (Templates + Event Triggers)
1565
+
1566
+ ### Task 16: Create workflows tool file (4 tools)
1567
+
1568
+ **Files:**
1569
+ - Create: `servers/wp-rest-bridge/src/tools/workflows.ts`
1570
+
1571
+ **Step 1: Write workflows tool file**
1572
+
1573
+ 4 tools: `wf_list_triggers`, `wf_create_trigger`, `wf_update_trigger`, `wf_delete_trigger`.
1574
+ These use the WP REST API to read/write a mu-plugin configuration (custom REST endpoint registered by the mu-plugin).
1575
+
1576
+ **Step 2: Register in index.ts**
1577
+
1578
+ **Step 3: Build and commit**
1579
+
1580
+ ```bash
1581
+ cd servers/wp-rest-bridge && npx tsc
1582
+ git add servers/wp-rest-bridge/
1583
+ git commit -m "feat(wp-rest-bridge): add workflows tool file (4 tools)
1584
+
1585
+ - wf_list_triggers: list configured event triggers
1586
+ - wf_create_trigger: register new trigger (hook → webhook)
1587
+ - wf_update_trigger: modify existing trigger
1588
+ - wf_delete_trigger: remove trigger (safety gate)
1589
+ - Uses WP REST API custom endpoint from mu-plugin
1590
+ - Total MCP tools: 132
1591
+
1592
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
1593
+ ```
1594
+
1595
+ ---
1596
+
1597
+ ### Task 17: Add safety hook for wf_delete_trigger
1598
+
1599
+ **Files:**
1600
+ - Modify: `hooks/hooks.json`
1601
+
1602
+ **Step 1: Add PreToolUse hook**
1603
+
1604
+ Add new entry in the `PreToolUse` array:
1605
+
1606
+ ```json
1607
+ {
1608
+ "matcher": "mcp__wp-rest-bridge__wf_delete_trigger",
1609
+ "hooks": [
1610
+ {
1611
+ "type": "prompt",
1612
+ "prompt": "The agent is about to DELETE a workflow event trigger. This will stop the automated action associated with this WordPress hook. Verify the user explicitly requested this deletion and understands which automation will stop."
1613
+ }
1614
+ ]
1615
+ }
1616
+ ```
1617
+
1618
+ **Step 2: Commit**
1619
+
1620
+ ```bash
1621
+ git add hooks/hooks.json
1622
+ git commit -m "feat(hooks): add safety hook for wf_delete_trigger
1623
+
1624
+ PreToolUse prompt confirmation before deleting workflow event triggers.
1625
+
1626
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
1627
+ ```
1628
+
1629
+ ---
1630
+
1631
+ ### Task 18: Create workflow detection script
1632
+
1633
+ **Files:**
1634
+ - Create: `skills/wp-content-workflows/scripts/workflow_inspect.mjs`
1635
+
1636
+ **Step 1: Write detection script**
1637
+
1638
+ Detects: mu-plugin `wcop-event-triggers.php`, configured triggers, wp-cron status, existing workflow-related plugins.
1639
+
1640
+ **Step 2: Commit**
1641
+
1642
+ ```bash
1643
+ git add skills/wp-content-workflows/scripts/workflow_inspect.mjs
1644
+ git commit -m "feat(wp-content-workflows): add workflow detection script
1645
+
1646
+ Detects mu-plugin triggers, wp-cron, workflow plugins.
1647
+
1648
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
1649
+ ```
1650
+
1651
+ ---
1652
+
1653
+ ### Task 19: Create wp-content-workflows skill (SKILL.md + 5 references)
1654
+
1655
+ **Files:**
1656
+ - Create: `skills/wp-content-workflows/SKILL.md`
1657
+ - Create: `skills/wp-content-workflows/references/launch-sequence.md`
1658
+ - Create: `skills/wp-content-workflows/references/refresh-cycle.md`
1659
+ - Create: `skills/wp-content-workflows/references/seasonal-campaign.md`
1660
+ - Create: `skills/wp-content-workflows/references/seo-sprint.md`
1661
+ - Create: `skills/wp-content-workflows/references/mu-plugin-triggers.md`
1662
+
1663
+ **Step 1: Write SKILL.md + 5 reference files**
1664
+
1665
+ 4 workflow templates (Launch Sequence, Refresh Cycle, Seasonal Campaign, SEO Sprint) + mu-plugin architecture reference.
1666
+
1667
+ **Step 2: Commit**
1668
+
1669
+ ```bash
1670
+ git add skills/wp-content-workflows/
1671
+ git commit -m "feat(wp-content-workflows): add skill with 5 reference files
1672
+
1673
+ 4 workflow templates: Launch Sequence, Refresh Cycle, Seasonal Campaign,
1674
+ SEO Sprint. mu-plugin trigger architecture reference.
1675
+
1676
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
1677
+ ```
1678
+
1679
+ ---
1680
+
1681
+ ### Task 20: Update wp-site-manager agent (Workflow Automation)
1682
+
1683
+ **Files:**
1684
+ - Modify: `agents/wp-site-manager.md`
1685
+
1686
+ **Step 1: Add Workflow Orchestration section**
1687
+
1688
+ Add delegation procedure: identify workflow → verify prerequisites → execute steps → report.
1689
+
1690
+ **Step 2: Update delegation table**
1691
+
1692
+ Add workflow-related delegation entries.
1693
+
1694
+ **Step 3: Commit**
1695
+
1696
+ ```bash
1697
+ git add agents/wp-site-manager.md
1698
+ git commit -m "feat(wp-site-manager): add Workflow Automation section
1699
+
1700
+ - Workflow Orchestration procedure: identify → verify → execute → report
1701
+ - Updated delegation table with workflow-related entries
1702
+ - Added Related Skills: wp-content-workflows
1703
+
1704
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
1705
+ ```
1706
+
1707
+ ---
1708
+
1709
+ ### Task 21: Update router to v16 + cross-references + version bump + GUIDE.md
1710
+
1711
+ **Files:**
1712
+ - Modify: `skills/wordpress-router/references/decision-tree.md` (v16)
1713
+ - Modify: `skills/wp-content/SKILL.md` (cross-ref)
1714
+ - Modify: `skills/wp-social-email/SKILL.md` (cross-ref)
1715
+ - Modify: `skills/wp-webhooks/SKILL.md` (cross-ref)
1716
+ - Modify: `skills/wp-content-optimization/SKILL.md` (cross-ref)
1717
+ - Modify: `package.json` (2.9.0)
1718
+ - Modify: `CHANGELOG.md` (v2.9.0 entry)
1719
+ - Modify: `docs/GUIDE.md` (update from v2.6.0 to v2.9.0)
1720
+
1721
+ **Step 1: Update decision-tree to v16**
1722
+
1723
+ Add workflow keywords and route in Step 2b.
1724
+
1725
+ **Step 2: Add cross-references in 4 skills**
1726
+
1727
+ **Step 3: Bump version and CHANGELOG**
1728
+
1729
+ **Step 4: Update GUIDE.md**
1730
+
1731
+ Update all counts, add v2.7.0-v2.9.0 features, add new scenarios for analytics, alerting, workflows.
1732
+
1733
+ **Step 5: Build final**
1734
+
1735
+ ```bash
1736
+ cd servers/wp-rest-bridge && npx tsc
1737
+ ```
1738
+
1739
+ **Step 6: Commit**
1740
+
1741
+ ```bash
1742
+ git add skills/ agents/ hooks/ package.json CHANGELOG.md docs/GUIDE.md
1743
+ git commit -m "chore: bump version to v2.9.0 — Automated Workflows
1744
+
1745
+ WCOP Tier 5: 4 workflow MCP tools, wp-content-workflows skill,
1746
+ safety hook, router v16, GUIDE.md v2.9.0.
1747
+ Skills: 39 | MCP tools: 132 | Agents: 12 | Detection scripts: 27
1748
+ WCOP score: 8.8/10 (Observability 9, Automation 9)
1749
+
1750
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
1751
+ ```
1752
+
1753
+ ---
1754
+
1755
+ ### Task 22: Update memory file
1756
+
1757
+ **Files:**
1758
+ - Modify: `/home/vinmor/.claude/projects/-home-vinmor/memory/wordpress-manager.md`
1759
+
1760
+ **Step 1: Update all sections**
1761
+
1762
+ - Version: 2.9.0
1763
+ - Skills: 39, MCP tools: 132, refs: 192, detection scripts: 27, hooks: 10
1764
+ - Add 3 new skill descriptions (wp-analytics, wp-alerting, wp-content-workflows)
1765
+ - Update router to v16
1766
+ - Add version history entries for v2.7.0, v2.8.0, v2.9.0
1767
+
1768
+ **Step 2: Commit memory (no git — memory file is outside repo)**
1769
+
1770
+ ---
1771
+
1772
+ ## Summary
1773
+
1774
+ | Release | Tasks | New Tools | New Skills | Key Files |
1775
+ |---------|-------|-----------|------------|-----------|
1776
+ | v2.7.0 | 1-10 | 14 (GA4+PL+CWV) | wp-analytics | ga4.ts, plausible.ts, cwv.ts |
1777
+ | v2.8.0 | 11-15 | 3 (Slack) | wp-alerting | slack.ts |
1778
+ | v2.9.0 | 16-22 | 4 (wf_*) | wp-content-workflows | workflows.ts |
1779
+ | **Total** | **22** | **21** | **3** | **4 tool files** |
1780
+
1781
+ ---
1782
+
1783
+ *Implementation Plan Tier 4+5 WCOP v1.0 — wordpress-manager v2.6.0 → v2.9.0 — 2026-03-01*