| Key | Name | Labels | Description | Attachments | Status | Resolution | Current Environment | Dates | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| PRESSWEB-111 | WordPress MCP setup complete — 85 abilities exposed via mcp-adapter + WAE + pressblk-mcp-fix v1.0.2 |
|
h2. Goal
Expose WordPress (pressblk.com) to AI agents via M...
h2. Goal
Expose WordPress (pressblk.com) to AI agents via Model Context Protocol (MCP), so Claude Code and other MCP clients can manage posts, pages, media, plugins, settings, etc. directly from chat.
h2. Result
* MCP endpoint live at {{https://pressblk.com/wp-json/mcp/mcp-adapter-default-server}}
* Protocol: MCP 2025-06-18
* Auth: Basic with WordPress Application Password (user {{mcp-bot}}, Administrator role)
* *85 native MCP tools exposed* (one per WordPress operation)
* Verified end-to-end: real {{mcp-wp-list-posts}} call returned actual posts from pressblk.com
h2. Plugins installed (in this order)
|| # || Plugin || Version || Source || Role ||
| 1 | MCP Adapter | 0.5.0 | WordPress/mcp-adapter | Official MCP engine. Bridges Abilities API to MCP protocol. |
| 2 | MCP WordPress Capabilities (WAE) | 1.0.0 | kradyy/wordpress-wae | Registers 85 abilities ({{mcp-wp/*}}) across 12 categories. |
| 3 | PressBlk MCP Fix | 1.0.2 | Custom (Verixity LLC) | Disables mcp-adapter's default server so WAE's separate server can take the route. |
h2. Problem found & fixed
Both mcp-adapter and WAE register a server on the same REST route ({{mcp/mcp-adapter-default-server}}). mcp-adapter creates its server first (priority 10) with only 3 service tools and 0 abilities. WAE tries to register at priority 20 with all 85 abilities, but route is already taken — registration silently shadowed. {{tools/list}} returned 0 useful tools.
Fix in {{pressblk-mcp-fix v1.0.2}} (4 KB plugin):
* *Strategy A:* filter {{mcp_adapter_create_default_server}} → false at priority 1 (skips default server creation entirely)
* *Strategy B (fallback):* on {{mcp_adapter_init}} priority 999, use reflection to scan {{$adapter->servers}}, detect route conflict, remove the default server post-hoc
* Both strategies log diagnostics to {{debug.log}}
After fix:
* Server name in {{initialize}} response: {{MCP WordPress Capabilities}} v1.0.0 (was: {{MCP Adapter Default Server}})
* {{tools/list}}: *85 tools* (was: 0–3)
h2. Tools exposed (85, by category)
|| Category || Count || Examples ||
| Posts/Pages | 10 | {{mcp-wp-list-posts}}, {{mcp-wp-create-page}}, {{mcp-wp-edit-post}} |
| Comments | 5 | {{mcp-wp-list-comments}}, {{mcp-wp-create-comment}} |
| Custom Post Types | 5 | {{mcp-wp-create-content}}, {{mcp-wp-list-content}} |
| Menus | 10 | {{mcp-wp-create-menu}}, {{mcp-wp-assign-menu-location}} |
| FSE/Block Entities | 5 | {{mcp-wp-create-block-entity}} |
| Patterns | 7 | {{mcp-wp-create-pattern}}, {{mcp-wp-import-pattern}} |
| Media | 6 | {{mcp-wp-list-media}}, {{mcp-wp-upload-media}}, {{mcp-wp-replace-media-file}} |
| Users | 5 | {{mcp-wp-list-users}}, {{mcp-wp-edit-user}} |
| Taxonomy | 6 | {{mcp-wp-create-category}}, {{mcp-wp-list-tags}} |
| Settings | 4 | {{mcp-wp-get-settings}}, {{mcp-wp-update-settings}}, {{mcp-wp-get-site-stats}} |
| Plugins & Themes | 14 | {{mcp-wp-install-plugin}}, {{mcp-wp-activate-plugin}}, {{mcp-wp-switch-theme}} |
| Advanced | 8 | {{mcp-wp-batch-update}}, {{mcp-wp-custom-rest-call}}, {{mcp-wp-validate-blocks}} |
h2. Security posture
* Dedicated WP user {{mcp-bot}} with Application Password (not the owner account)
* Currently Administrator role (full WP capabilities) — appropriate while we still need plugin/theme/settings management
* Can be downgraded to Editor if MCP usage is limited to content operations only
* Application Password used in initial setup was leaked in chat and *MUST be rotated* (see follow-ups)
* {{DISALLOW_FILE_MODS}} status: TBD — site currently allows plugin uploads via wp-admin, fine for our use
h2. Follow-ups
* (P0) Rotate Application Password — revoke old, generate new, store securely
* (P1) Wire up {{.mcp.json}} in {{/home/pressblk-back}} so Claude Code can call these tools
* (P2) Consider IP allowlist on nginx for {{/wp-json/mcp/*}} route (only allow agent-server IPs)
* (P3) Document common workflows: "publish blog post via MCP", "bulk update featured images", etc.
* (P4) Investigate why WAE 1.0.0 conflicts with mcp-adapter 0.5.0 default — open upstream issue or PR
h2. Files / artifacts
* Plugin source: {{/tmp/wp-mcp-plugins/pressblk-mcp-fix/pressblk-mcp-fix.php}} on host
* Distributable zip: {{/tmp/wp-mcp-plugins/pressblk-mcp-fix.zip}} (1.8 KB)
h2. Test verification
{code:bash}
curl -X POST -u 'mcp-bot:APP_PWD' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"x","version":"1"}}}' \
https://pressblk.com/wp-json/mcp/mcp-adapter-default-server
# → Server: MCP WordPress Capabilities v1.0.0
curl -X POST -u 'mcp-bot:APP_PWD' \
-H 'Content-Type: application/json' \
-H 'Mcp-Session-Id: SESSION_ID' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
https://pressblk.com/wp-json/mcp/mcp-adapter-default-server
# → 85 tools
{code}
|
Done | Done | -- |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PRESSWEB-110 | Beta system fixes, email campaigns, SMTP/DKIM, malware analysis, search backend |
Session 2026-05-06 — backend and infrastructure work.
*Ligh...
Session 2026-05-06 — backend and infrastructure work.
*Light Theme:*
- Recolored all 4 beta pages to match pressblk.com (white bg, blue #0693e3)
- Fixed footer links: /about-us/ -> /about/, /contact-us/ -> /contact/
- terms-of-use.html and privacy-policy.html mounted in Docker (fixed 500)
*Email Verification:*
- Added /beta-verify/{token} endpoint — proper gate before media library access
- Media library blocks unverified users (403)
- Tickets require email_verified_at
- Email button: Verify My Email -> /beta-verify/{token}
*Email Campaign System (Filament Admin):*
- New EmailCampaignResource with RichEditor for email body
- 3 recipient fields: Users dropdown, Beta Testers dropdown, manual email input
- Delivery types: manual (send now), scheduled, event triggers
- SendCampaignJob with per-recipient logging
- campaigns:send-scheduled command (every minute)
- Fixed recipient_type default value (500 on create)
*SMTP/DKIM/SSL:*
- SSL cert was expired (Plesk self-signed from 2022) — admin installed Let Encrypt
- DKIM configured and passing
- SPF/DKIM/DMARC all PASS — spam is reputation-based, not auth
*Search Backend:*
- SearchController now uses ST_Distance_Sphere with lat/lng/distance
- Auto-fallback: if 0 results in radius, shows 5 nearest
- Hidden binary POINT location from Business JSON (was crashing mobile)
- Added JSON_INVALID_UTF8_SUBSTITUTE to prevent 500 on bad data
- Deleted test businesses with Cyrillic names
*Chat Push Notifications:*
- PushNotificationService: filter is_active tokens, parse Expo ticket errors
- Auto-deactivate DeviceNotRegistered tokens
- Added diagnostic endpoint GET /messages/test-push/{userId}
*Security — Malware:*
- pressblk.com crypto-drainer root cause: WakeX API port 3847 + phpMyAdmin port 8081 open to internet
- MySQL root password hardcoded in server.js
- Recommended: close ports, change all passwords
*Docker:*
- Mounted entrypoint.sh for Filament assets auto-publish
- Filament ticket view shows attached screenshots
- Ticket list shows beta tester name correctly
*Commits:* d0b5d91, 3d75e2f, 0c83437, b58ba2f, 3841907, 5e87720, 4010d42, 0709944, d46a43e + more
|
Backlog | Unresolved | -- |
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PRESSWEB-109 | Beta Testing System — Registration, Media Library, Tickets, FAQ, Email Verification |
Implemented a complete beta tester management system with 4 ...
Implemented a complete beta tester management system with 4 public pages and full workflow automation.
*Pages created:*
- /beta-testers — Registration form with NDA, signature canvas, iOS/Android platform selection
- /my-media/{token} — Personal media library for each tester (upload screenshots, grid view, lightbox, iOS Shortcut support)
- /beta-tickets — Bug report / feature request form (4-step wizard with media library integration)
- /beta-faq — 39 Q&A items across 7 sections
*Backend:*
- BetaTester model + migration (name, email, phone, platform, NDA signature, media_token, email_verified_at)
- BetaMedia model + migration (file uploads, per-tester storage)
- MediaLibraryController — upload, list, delete, list-by-email endpoints
- BetaTicketController — public ticket creation, library_ids attachment, email verification check
- AppStoreConnectService — JWT auth, auto-add iOS testers to TestFlight, skip invite for existing ASC testers
- BetaTesterWelcome email — verification link, TestFlight instructions, media library link, ticket guide
- Docker: persistent volume for media uploads, mounted HTML/blade/ASC key files
*TestFlight integration:*
- Auto-invite new iOS testers to External Beta Testers group via ASC API
- Skip invite for existing internal/external testers (isTesterExists check)
- Added build 2 to Beta Testers group
- Fixed Codemagic build numbering — now queries ASC API for max build + 1
- Fixed beta_groups name: Beta Testers (was AppBuilder Testers)
*IAP fix:*
- Fixed "Invalid request for iOS. The sku property is required" error
- react-native-iap v14.7.20 requires request.apple.sku format (not request.sku)
*Security — Malware analysis on pressblk.com:*
- Crypto-drainer returned (same attacker wallet 0x08207B...)
- Full scan: infection via wp_footer hook, /cart/ and /checkout/ clean (WooCommerce simplified template)
- Root cause found: WakeX chat-api on port 3847 exposed MySQL root password to internet
- phpMyAdmin on port 8081 also open externally
- Same password (PressBlk2025Secure!) used across all services
- SQL queries prepared for WordPress DB cleanup
- Recommended: close ports 3847/8081, change all passwords, install Wordfence
*SMTP issue:*
- mail.pressblk.net SSL port 465 down after Plesk cleanup
- Port 25 + STARTTLS works but SPF mismatch (our IP 148.72.133.193 not in SPF record)
- Fix: admin needs to add ip4:148.72.133.193 to pressblk.app SPF DNS record
*Commits:* 8c23313, 5c19878, 1da6226, 3bea5c9, 195138f, ab47ddd, cf3d881, ff6e96f, 7a5ec34, 0950eb4, 74459d0, 0e3bff0, 2973ea5 (mobile)
|
Backlog | Unresolved | -- |
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PRESSWEB-108 | Security: Malware cleanup on pressblk.com + website header fix |
pressblk.com WordPress site was compromised with a crypto-dr...
pressblk.com WordPress site was compromised with a crypto-drainer malware injected via a fake MU-plugin. The malware displayed a fake Cloudflare verification page to trick visitors into executing malicious code. Additionally, the site header/navigation menu was missing due to Hello Elementor theme not supporting standard WordPress menus.
Scope:
1. Security audit and malware removal
2. Website header/navigation restoration
3. Post-incident security recommendations
|
Done | Done | -- |
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PRESSWEB-107 | Multi-source BO directory scrapers — 15-site backlog |
h2. Sources to scrape
15 Black-owned-business directories r...
h2. Sources to scrape
15 Black-owned-business directories referred by Michael Battle (Oct/Jun/Jul 2025 emails) plus Max's earlier scraping targets. ByBlack is already covered (PRESSWEB-105 + the new staging:import-from-byblack-api command).
Each line below is a checkbox — update status as scrapers ship.
h3. Source list (status: ☐ pending · ⚙ recon · 🛠 built · ✅ live · 🚫 blocked)
*From Michael Battle — Oct 17, 2025 email:*
* ☐ www.buyblack.org
* ☐ www.ourgreenweb.com
* ☐ www.blackbusinesslist.com
* ☐ www.blackdirectory.com
* ☐ www.blacksheet.net
* ✅ www.byblack.us — covered in PRESSWEB-105 + new /api/v3/search/ harvester
* ☐ www.blackownedworld.org
* ☐ www.yourgreenbook.com
*From Michael Battle — June 19, 2025 email (Green Book):*
* ☐ greenbooktb.com
* ☐ www.floridablackchamber.com
*From Michael Battle — June 20, 2025 email (URL):*
* ☐ usblackchambers.org
*From Michael Battle — July 26, 2025 email (MS Black Pages):*
* ☐ MS Black Pages — need to find canonical URL
*Max's earlier scraping targets:*
* ☐ funtimesmagazine.com/business-directory/
* ☐ championblackbusinesses.com
* ☐ abc.iamblackbusiness.com
* ☐ blackwomenempowereddirectory.com
h2. Plan
*Phase 1 — Recon (in progress):* For each site, check robots.txt, look for sitemap.xml, sample one listing page + one detail page, identify CMS/SPA (WordPress, Wix, Joomla, custom SPA, etc.). Classify each as:
* *easy* — has sitemap, server-renders detail pages with clear structured data → reuse StagingImportFromDirectories framework, ~30 min per site to wire up
* *medium* — needs HTML regex/JSON-LD parsing, but data is server-rendered → ~1 hr per site
* *spa* — JS-rendered like ByBlack was → either find their backend API (best) or use Crawlee/Playwright (Phase 2)
* *blocked* — Cloudflare WAF / login-walled / hostile → defer or use paid scraping API
*Phase 2 — Build the easy wins:* For each easy/medium site, add a new entry to the $directories array in StagingImportFromDirectories.php and a new parser method. Reuse the WebShare proxy rotation, UA rotation, jitter, daily cron pattern that's already working for EatOkra and ByBlack.
*Phase 3 — SPA / blocked sites:* For sites that need JS rendering, either reverse-engineer their backend API (worked for ByBlack — found api.byblack.us/api/v3/search/) or use the Playwright pipeline at /home/pressblk-crawlee/.
h2. Expected yield
ByBlack alone has 17,979 listings hidden behind their SPA. If even 5 of these other 15 sites have similar volume, that's potentially 50K-100K new BO-curated rows added to business_staging.
h2. Tracking
Description gets updated as each site moves through the workflow. Each scraper gets its own commit + completes its checkbox above. Final summary added when the full list is done.
h2. Related
* PRESSWEB-105 — Scraper 2.0 base + EatOkra + ByBlack
* PRESSWEB-104 — fast-cycle cron + lock fixes (foundation infrastructure)
* /home/pressblk-crawlee/ — Playwright infrastructure for the SPA-only sites
|
Backlog | Unresolved | -- |
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PRESSWEB-106 | gmaps-scraper queue stuck — partition SQL bug + 16 stuck-queued rows |
h2. Symptom
User checking /scraper-queries observed 0 progr...
h2. Symptom
User checking /scraper-queries observed 0 progress drainage despite the cron showing every 10 min as scheduled. Investigation showed the gmaps-scraper had been *running* but only producing duplicates (every batch returned 13 businesses, all merged into existing rows).
h2. Three root causes
h3. Bug 1 — *%%* in whereRaw caused MySQL syntax error
In scripts/run-scraper-and-import.sh the partition picker was:
{noformat}
->whereRaw('id %% \${INSTANCE_TOTAL} = \${INSTANCE_NUM}')
{noformat}
PHP whereRaw passes the string verbatim. MySQL saw {{id %% 2 = 0}} which is invalid (MySQL modulo is single {{%}}). Stack trace from storage/logs/scraper.log:
{noformat}
SQLSTATE42000: Syntax error or access violation: 1064
... near '% 2 = 0 limit 10 for update'
{noformat}
The SQL exception fell through to a fallback that picked only 1 random pending query per tick instead of the requested 10. With ~12 ticks/hour × 1 query each = ~12 queries/hour throughput, where intended was 120/hour.
h3. Bug 2 — 16 rows stuck in *queued* state
The pickup logic sets {{status='queued'}} when reserving rows but only flips to {{completed}} after a successful run. When the script crashes / OOMs / network-fails between those steps, rows remain {{queued}} forever.
Query showed 16 rows had been {{queued}} since 2026-04-14 (12 days). Permanently lost from rotation until manually reset.
h3. Bug 3 — backlog vastly bigger than visible 'pending' suggested
Of the 8,284 pending queries, *5,760 had never been run* (last_run_at IS NULL) — that's the genuine backlog. The remaining 2,524 had been retried at least once.
h2. Fixes applied
# *Script SQL fix*: scripts/run-scraper-and-import.sh — single-character change {{%%}} → {{%}}. Verified via tinker that the partition picker now returns 5 partition-1 odd-id rows correctly (#293 Orlando real-estate, #1381 Phoenix barbershop, etc.).
# *Stuck-queued reset*: SQL {{UPDATE scraper_queries SET status='pending', last_run_at=NULL WHERE status='queued' AND updated_at < NOW() - INTERVAL 1 HOUR}} → 16 rows back into rotation. Pending count: 8,284 → 8,300.
# *Throughput bump*: host crontab — both A and B instances bumped from {{run-scraper-and-import.sh 10 60 120}} to {{... 30 60 120}} (10 → 30 queries per tick).
# *Zombie cleanup*: killed run-scraper-and-import.sh PIDs 681921+681924 (started 09:30 UTC — running 14h with no completions in the last 18h).
h2. Expected throughput
||Config||Queries/hour||Time to drain 5,760||
|Before fix (broken)|~12|~480 hours / 20 days|
|After Bug 1 fix only|~120|~48 hours / 2 days|
|After Bug 1 + batch 30|*~360*|*~16 hours*|
|+ 4 partitions later (option)|~720|~8 hours|
h2. Files changed
* scripts/run-scraper-and-import.sh — 1-char fix in line 52 (whereRaw)
* host crontab (root) — both A and B instances run-scraper-and-import.sh max-queries 10 → 30
h2. Commit
94054de on dev — "fix(gmaps-scraper): %% in whereRaw causing partition picker to fail"
h2. Verification
Waiting for next cron tick to confirm 30-query batch picks up at scale. Will append result as a comment.
h2. Related
* PRESSWEB-105 — Scraper 2.0 (separate system, directory imports). Surfaced this bug because user asked how /scraper-queries relates to Scraper 2.0; investigation revealed the gmaps-scraper queue had been quietly broken for ~2 weeks.
|
Backlog | Unresolved | -- |
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PRESSWEB-105 | Scraper 2.0 — bulk-import from curated Black-owned business directories |
h2. Why
LinkedIn DW enrichment was producing low-yield matc...
h2. Why
LinkedIn DW enrichment was producing low-yield matches because the populations don't overlap: the DW skews to white-collar corporate professionals while pressblk's target is small Black-owned local businesses. We pivoted away from DW-based enrichment and toward harvesting *curated Black-owned-business directories* — the right population for the platform.
h2. What was built
New artisan command *staging:import-from-directories*. Unlike the existing staging:verify-directories (which only flips black_owned_verified=true on existing rows), this command IMPORTS new rows from the directory: walks the sitemap, parses each detail page, inserts to business_staging with black_owned_verified=true and data_sources='directory_ Tasks Assign to Client
Tasks Assigned to Team
|