Skip to content

Requests send a bare "node" User-Agent, making this traffic impossible for firewalls/security plugins to identify #111

Description

@alisadiq90

Summary

The client never sets an explicit User-Agent header when calling the WordPress REST API. I ran the published package against a local test listener to see what actually goes out on the wire, and the request arrives with:

user-agent: node

That's Node's own built-in fetch (undici) default when nothing overrides it, not something specific to this package. It carries no identifying information at all, and "node" is common enough that it's effectively indistinguishable from a huge range of unrelated scripts and tools.

Why this matters

I've run into WordPress security/firewall plugins blocking this traffic because their bot-protection systems classify requests partly by User-Agent, and a bare node string looks indistinguishable from bot/scraper traffic. Since MCP clients run on the end user's own machine with no fixed IP, User-Agent is really the only practical signal a security plugin has to work with here, and right now it doesn't carry one.

This would affect any WordPress security plugin that does UA-based classification, and any project built on this bridge, not just a single use case.

Where this happens

src/lib/wordpress-api.ts, in the headers object construction (around lines 391-396):

const headers: Record<string, string> = {
  'Content-Type': 'application/json',
  Accept: 'application/json, text/event-stream',
  'MCP-Protocol-Version': '2025-06-18',
  ...customHeaders,
};

No User-Agent key is set here, which matches what actually goes out on the wire: Node's built-in fetch default.

Suggested fix

Add an explicit, version-stamped User-Agent at the same spot, something like:

const headers: Record<string, string> = {
  'Content-Type': 'application/json',
  Accept: 'application/json, text/event-stream',
  'MCP-Protocol-Version': '2025-06-18',
  'User-Agent': `mcp-wordpress-remote/${VERSION}`,
  ...customHeaders,
};

(keeping it before ...customHeaders so a caller can still override it via CUSTOM_HEADERS if they need to)

This is a small change but it's the difference between security plugins being able to build a real allowlist rule for this traffic versus not being able to identify it at all. Happy to open a PR for this if that's useful, just wanted to raise it first in case there's a reason it's currently left unset that I'm not seeing.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions