---
name: ms-chat WordPress Agent Tools (msio)
overview: Central registry + ms-chat client/tools for RANK AI WordPress sites. Discover via API key, confirm in WP plugin admin, reads from api.morningscore.io, writes via WP agent REST.
project: msio
related: rank-ai-by-morningscore
todos:
  - id: phase-a-jetforce-wp
    content: "jetforce-wp: WpSite + MsChatLink, register/unregister, discover, confirm/revoke APIs, resolveSiteForAgent"
    status: pending
  - id: phase-b-ms-chat-client
    content: "ms-chat: wordpress client, Settings UI, signed deep-link builder, compatibility map"
    status: pending
  - id: phase-c-read-tools
    content: "ms-chat: wordpress_* read tools, connection status, job polling, unconfirmed tool render"
    status: pending
  - id: phase-d-write-tools
    content: "ms-chat: write tools with requireApproval, version gating, approval UI"
    status: pending
  - id: phase-e-polish
    content: "Agent instructions, status anti-spam, stale handling, tests, deployment env vars"
    status: pending
isProject: false
---

# ms-chat WordPress Agent Tools — msio Implementation Plan

Companion doc for the WP plugin: [`ms-chat-wp-plugin-implementation.md`](./ms-chat-wp-plugin-implementation.md)

## Overview

Connect ms-chat to RANK AI WordPress sites using **discover + confirm**:

| Layer | Mechanism | Grants |
|-------|-----------|--------|
| **Discover** | API key match + plugin registration heartbeat | See which WP sites exist, version, stale status |
| **Confirm** | One click in WP plugin admin (user has `manage_options`) | Agent read + write tools for that `resourceId` + `siteUrl` |

**Reads** (health counts, domains, articles) come from **api.morningscore.io** — not from WP.

**Writes** (health fix jobs, page booster, article upload) go to **WP REST** at the linked `siteUrl`.

One confirm per WP install — not per WPML language or Morningscore domain.

---

## Architecture

```
  WP plugin (rank_api_key)
       |
       | POST /api/wp/register (key save, activation, daily)
       v
  jetforce-wp
       |  WpSite { siteUrl, pluginVersion, lastSeenAt }
       |  MsChatLink { resourceId, siteUrl, confirmedAt }
       v
  ms-chat session (same API key)
       |
       | 1. DISCOVER
       |    GET /api/agent/discover
       |
       | 2. CONFIRM (deep link from ms-chat Settings)
       |    "Connect in WordPress →" opens WP admin with signed ms_chat_state
       |    User clicks Connect → POST /api/auth/ms-chat/confirm
       |    → redirect returnUrl?linked=true
       |
       | 3. READS → api.morningscore.io
       |
       | 4. WRITES → {siteUrl}/wp-json/rank-ai/v1/agent/*
       v
  WP plugin (agent token auth)
```

---

## Security model

| Check | When | Purpose |
|-------|------|---------|
| `MS_CHAT_AGENT_SECRET` | Agent calls (ms-chat → WP) | Service-to-service |
| API key hash matches `WpSite` | Discover + confirm | Tenant scoping |
| `MsChatLink.confirmedAt` + not revoked | Agent writes | Explicit consent |
| Signed `ms_chat_state` | Deep link + confirm | CSRF / replay |
| Per-site agent token in WP only | Agent calls | Token isolation |
| `requireApproval` | Write tools | Per-mutation consent |
| `siteUrl` resolved server-side | All agent calls | No prompt-injection |

### Signed state (deep link)

```
state = HMAC-SHA256({
  resourceId,
  siteUrl,
  returnUrl,
  exp,    // 15 min
  nonce
}, MS_CHAT_LINK_SIGNING_SECRET)
```

Encoded as base64url query param `ms_chat_state`.

Confirm endpoint verifies: signature, not expired, `siteUrl` matches, API key on `WpSite` matches session.

### Env vars

| Var | Service | Purpose |
|-----|---------|---------|
| `MS_CHAT_AGENT_SECRET` | jetforce-wp, ms-chat, WP (verify) | Service auth |
| `MS_CHAT_LINK_SIGNING_SECRET` | jetforce-wp, ms-chat | Deep link state signing |

---

## Data model (jetforce-wp)

```prisma
model WpSite {
  id                     String    @id @default(cuid())
  morningscoreApiKeyHash String    @map("morningscore_api_key_hash")
  siteUrl                String    @map("site_url")
  pluginVersion          String?   @map("plugin_version")
  wpVersion              String?   @map("wp_version")
  phpVersion             String?   @map("php_version")
  lastSeenAt             DateTime  @map("last_seen_at")
  unregisteredAt         DateTime? @map("unregistered_at")

  msChatLinks            MsChatLink[]

  @@unique([morningscoreApiKeyHash, siteUrl])
  @@index([morningscoreApiKeyHash])
  @@map("wp_sites")
}

model MsChatLink {
  id           String    @id @default(cuid())
  resourceId   String    @map("resource_id")
  siteUrl      String    @map("site_url")
  confirmedAt  DateTime  @map("confirmed_at")
  revokedAt    DateTime? @map("revoked_at")

  @@unique([resourceId, siteUrl])
  @@index([resourceId])
  @@index([siteUrl])
  @@map("ms_chat_links")
}
```

Store `sha256(apiKey)` only — never plaintext API keys.

---

## jetforce-wp API

### Registration (called by WP plugin)

#### `POST /api/wp/register`

```
Authorization: Bearer {morningscore_api_key}

{
  "site_url": "https://example.com",
  "plugin_version": "1.0.6",
  "wp_version": "6.5",
  "php_version": "8.2"
}
```

Upsert `WpSite`, set `lastSeenAt`, clear `unregisteredAt`.

#### `POST /api/wp/unregister`

Same auth. Set `unregisteredAt` on matching site.

### Discover (called by ms-chat)

#### `GET /api/agent/discover`

```
Authorization: Bearer {morningscore_api_key}
X-MS-Agent-Secret: {MS_CHAT_AGENT_SECRET}
```

Response:

```json
{
  "sites": [
    {
      "site_url": "https://example.com",
      "plugin_version": "1.0.6",
      "last_seen_at": "2026-07-07T14:00:00Z",
      "confirmed": true,
      "resource_id": "res_abc",
      "stale": false
    }
  ]
}
```

`stale: true` when `last_seen_at` > 7 days ago.

### Confirm / revoke (called by WP plugin on Connect/Disconnect)

#### `POST /api/auth/ms-chat/confirm`

```
Authorization: Bearer {morningscore_api_key}

{
  "resource_id": "res_abc",
  "site_url": "https://example.com",
  "agent_token_hash": "sha256(...)",
  "ms_chat_state": "..."   // optional but required when arrived via deep link
}
```

Creates `MsChatLink`. Returns `{ "return_url": "..." }` when state contained `returnUrl`.

#### `POST /api/auth/ms-chat/revoke`

```
Authorization: Bearer {morningscore_api_key}

{
  "resource_id": "res_abc",
  "site_url": "https://example.com"
}
```

Sets `revokedAt`.

### Resolver

```ts
resolveSiteForAgent(apiKey, siteUrl, resourceId):
  1. WpSites where apiKeyHash matches, unregisteredAt is null
  2. Filter: normalize(siteUrl) matches
  3. Filter: MsChatLink with resourceId + siteUrl + confirmedAt + !revokedAt
  4. 0 → not found / not confirmed
  5. 1 → return site metadata
  6. 2+ → 409 ambiguous
```

---

## ms-chat tool surface

### From api.morningscore.io (direct — no WP call)

| Data | Used for |
|------|----------|
| Health issue counts | Chat context, fix planning |
| Domain list | Domain picker |
| Articles list | Rank Writer |
| Tracked keywords, credits | Booster context |

### Read tools (require confirmed link → call WP)

| Tool ID | Notes |
|---------|-------|
| `wordpress_sites_list` | Wrapper over discover |
| `wordpress_connection_status` | confirmed, version, stale |
| `wordpress_health_job_status` | Action Scheduler batch state |
| `wordpress_page_booster_status` | Active booster job |

### Write tools (confirmed link + `requireApproval`)

| Tool ID | Approval UI | min_plugin_version |
|---------|-------------|-------------------|
| `wordpress_health_fix_start` | Issue type, count, credits | `1.0.5` |
| `wordpress_page_booster_start` | URLs, mode, credits | `1.0.6` |
| `wordpress_page_booster_push_live` | URL, fields | `1.0.6` |
| `wordpress_articles_upload` | Titles, target | `1.0.6` |

Unconfirmed → tool render shows **"Connect in WordPress →"** (deep link), not approve/deny.

Below `min_plugin_version` → block with upgrade message.

---

## Deep-link flow (v1)

### ms-chat builds URL

```ts
// src/mastra/wordpress/build-confirm-url.ts
export function buildWordPressConfirmUrl({
  siteUrl,
  resourceId,
  returnUrl,
}: {
  siteUrl: string;
  resourceId: string;
  returnUrl: string;
}): string {
  const state = signState({ resourceId, siteUrl, returnUrl, exp, nonce });
  return `${siteUrl}/wp-admin/admin.php?page=rank-admin&tab=api_config&ms_chat_state=${state}`;
}
```

### User journey

```
ms-chat Settings
  → "Connect in WordPress →"
  → WP admin (signed state in URL)
  → Connect ms-chat button
  → POST confirm to jetforce-wp
  → redirect to ms-chat returnUrl?linked=true
  → success toast in ms-chat
```

Manual connect (no deep link): same Connect button, no redirect — see WP plugin doc.

---

## ms-chat Settings UX

```
WordPress sites (via RANK AI plugin)
────────────────────────────────────
✓ example.com          RANK AI v1.0.6 · seen today    [Confirmed]
  [Disconnect]

○ staging.example.com  RANK AI v1.0.4 · seen 2d ago   [Connect in WordPress →]

[Install guide →]
```

---

## Version gating

```ts
// src/mastra/wordpress/compatibility.ts
export const WP_TOOL_REQUIREMENTS = {
  wordpress_health_fix_start:       '1.0.5',
  wordpress_page_booster_start:     '1.0.6',
  wordpress_page_booster_push_live: '1.0.6',
  wordpress_articles_upload:        '1.0.6',
} as const;

export function isPluginVersionSupported(
  installed: string | null,
  required: string
): boolean { /* semver compare */ }
```

Agent instructions:

- Don't suggest tools below `min_plugin_version`
- Soft-nudge upgrade once per session if behind but tool still works
- If `stale: true` on discover → prompt user to open WP admin

---

## Implementation phases

### Phase A — jetforce-wp (5 pts)

| Task | Detail |
|------|--------|
| Prisma schema | `WpSite`, `MsChatLink` |
| `POST /api/wp/register` | Upsert + version fields |
| `POST /api/wp/unregister` | Soft-delete site |
| `GET /api/agent/discover` | Sites + confirmed + stale |
| `POST /api/auth/ms-chat/confirm` | Create link, return `return_url` |
| `POST /api/auth/ms-chat/revoke` | Set `revokedAt` |
| `resolveSiteForAgent` | Resolver |
| `agentAuth` middleware | `MS_CHAT_AGENT_SECRET` |
| State signing helpers | HMAC sign/verify |

**Files:**

| File | Change |
|------|--------|
| `services/jetforce/wp/prisma/schema.prisma` | Models |
| `src/routes/wp-register.ts` | **New** |
| `src/routes/ms-chat-link.ts` | **New** — confirm + revoke |
| `src/routes/agent.ts` | **New** — discover |
| `src/services/resolve-site-for-agent.ts` | **New** |
| `src/services/ms-chat-state.ts` | **New** — sign/verify |
| `src/middleware/agentAuth.ts` | **New** |
| `src/env.ts` | Secrets |
| `src/routes/index.ts` | Mount routes |

---

### Phase B — ms-chat client + Settings (5 pts)

| Task | Detail |
|------|--------|
| WordPress HTTP client | jetforce-wp discover + WP agent routes |
| `build-confirm-url.ts` | Signed deep link |
| `compatibility.ts` | Version requirements |
| `WordPressStoresSection.tsx` | Settings UI |
| `wordpress-integration-state.ts` | System notices |
| Deep link handler on `?linked=true` | Success toast |

**Files:**

| File | Change |
|------|--------|
| `services/general/ms-chat/src/mastra/wordpress/client.ts` | **New** |
| `services/general/ms-chat/src/mastra/wordpress/build-confirm-url.ts` | **New** |
| `services/general/ms-chat/src/mastra/wordpress/compatibility.ts` | **New** |
| `services/general/ms-chat/src/features/settings/WordPressStoresSection.tsx` | **New** |
| `services/general/ms-chat/src/lib/integrations/wordpress-integration-state.ts` | **New** |
| `services/general/ms-chat/src/features/settings/SettingsPage.tsx` | Add section |
| `services/general/ms-chat/src/mastra/mcp.ts` | WordPress toolset gate |

---

### Phase C — Read tools (5 pts)

| Task | Detail |
|------|--------|
| `wordpress_sites_list` | Discover wrapper |
| `wordpress_connection_status` | Version + stale + confirmed |
| `wordpress_health_job_status` | WP agent REST |
| `wordpress_page_booster_status` | WP agent REST |
| `wordpress-tool-render.tsx` | Unconfirmed state UI |

**Files:**

| File | Change |
|------|--------|
| `src/mastra/wordpress/tools.ts` | **New** — read tools |
| `src/features/wordpress/wordpress-tool-render.tsx` | **New** |
| `src/mastra/agents/instructions.ts` | Confirm-then-act |

---

### Phase D — Write tools + approval (8 pts)

| Task | Detail |
|------|--------|
| `wordpress_health_fix_start` | `requireApproval` |
| `wordpress_page_booster_start` | `requireApproval` |
| `wordpress_page_booster_push_live` | `requireApproval` |
| `wordpress_articles_upload` | `requireApproval` |
| Version gate before render | Block + upgrade CTA |
| Agent → WP REST calls | HMAC auth headers |

**Files:**

| File | Change |
|------|--------|
| `src/mastra/wordpress/tools.ts` | Write tools |
| `src/features/wordpress/wordpress-tool-render.tsx` | Approval cards |
| `src/mastra/wordpress/client.ts` | Agent call helpers |

---

### Phase E — Polish (3 pts)

| Task | Detail |
|------|--------|
| Status anti-spam | Poll ≤1×/turn, milestone summaries |
| Agent instructions | Version gating, upgrade nudges |
| Stale site messaging | 7-day threshold |
| Tests | State signing, resolver, discover |
| Deployment | Env vars on all services |
| `AI_SUMMARY` updates | Service docs |

---

## Out of scope (v1)

- ms-chat WordPress OAuth
- Per-WPML-domain confirmation
- Health issue counts via WP
- Outbound command queue for firewalled WP (v2)
- Rollback tools
- Confirm page on rank.morningscore.io

---

## Definition of done (msio)

- [ ] `GET /api/agent/discover` returns registered WP sites with version + confirmed flag
- [ ] Confirm/revoke APIs create/delete `MsChatLink`
- [ ] Signed deep link opens WP admin; after connect, redirects to `returnUrl?linked=true`
- [ ] ms-chat Settings shows detected vs confirmed sites with version + stale badge
- [ ] Read tools poll WP job status for confirmed sites
- [ ] Write tools require confirmed link + per-action approval
- [ ] Health counts come from api.morningscore.io only
- [ ] Outdated plugin blocks tools below `min_plugin_version`
- [ ] Tests pass for state signing, resolver, discover

---

## Dependency on WP plugin

WP plugin must ship (can be parallel):

1. Registration on key save / activation / daily cron
2. Connect UI with signed state handler
3. Agent REST routes + token auth
4. Redirect to `returnUrl` after deep-link connect

See [`ms-chat-wp-plugin-implementation.md`](./ms-chat-wp-plugin-implementation.md).
