Serving Your Abilities

Serving Abilities Over STDIO via WP-CLI

⏱ 14 min

HTTP transport is what you’d point a remote or browser-based client at. STDIO is the other option the MCP Adapter ships, and it’s the one most local AI coding tools actually prefer: no network round-trip, no session headers, no HTTPS certificate to worry about. This lesson covers wp mcp-adapter serve, the WP-CLI command that runs your server over standard input and output instead.

What you'll learn in this lesson
What STDIO transport actually is
A single WP-CLI process speaking JSON-RPC 2.0 over stdin/stdout, no HTTP involved.
The wp mcp-adapter serve and list commands, exactly
Every flag they accept.
Choosing --user correctly
How the user you serve as determines what current_user_can() sees inside every permission check.
Configuring a local MCP client to launch it
The JSON config shape tools like Claude Desktop expect.
Prerequisites

The custom server registered in Lesson 6 (my-plugin-content-server), or the default server from Lesson 1. WP-CLI available on your local machine with direct access to the WordPress install (STDIO doesn’t work against a server you can only reach over HTTP).

Step 1: The two commands

The adapter registers exactly two WP-CLI subcommands. wp mcp-adapter list shows every server that’s been registered:

# File: terminal
wp mcp-adapter list --format=table

--format accepts table, csv, json, or yaml, table is the default. wp mcp-adapter serve is the one that actually runs a server, reading JSON-RPC requests from stdin and writing responses to stdout:

# File: terminal
wp mcp-adapter serve --server=my-plugin-content-server --user=admin

--server picks which registered server to run, if you omit it, the adapter uses the first one available, which is fragile once you have more than one, always pass it explicitly. --user accepts a numeric ID, a login, or an email address, and determines which WordPress user’s capabilities every permission_callback on that server sees for this entire session. Omit it and the process runs unauthenticated, with whatever (usually very limited) capabilities that implies.

Step 2: Talk to it directly, one request at a time

Because it’s just JSON-RPC over stdin/stdout, you can pipe a single request in and read the response back without any client at all, useful for quick manual checks:

# File: terminal, list every tool the server exposes
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' \
  | wp mcp-adapter serve --user=admin --server=my-plugin-content-server
# File: terminal, call the create-draft-post tool directly
echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"my-plugin-create-draft-post","arguments":{"title":"From STDIO","body":"Hello."}}}' \
  | wp mcp-adapter serve --user=admin --server=my-plugin-content-server

Unlike HTTP transport, there’s no session handshake here at all, each wp mcp-adapter serve invocation is one process with its own lifecycle. That’s the main practical tradeoff: STDIO is simpler to reason about locally, but it only exists for as long as the client keeps that one process alive, it isn’t something a remote client can connect to from across a network.

Step 3: Wire it into a real MCP client

Local AI clients that launch subprocess servers, Claude Desktop among them, expect a config entry naming the command and arguments to run:

{
  "mcpServers": {
    "wordpress-content": {
      "command": "wp",
      "args": [
        "--path=/path/to/your/wordpress/site",
        "mcp-adapter",
        "serve",
        "--server=my-plugin-content-server",
        "--user=admin"
      ]
    }
  }
}

The --path flag tells WP-CLI which WordPress install to operate on, required here since the client is launching wp from wherever it runs, not from inside your site’s directory.

Before wiring a server into a client config
Confirm the server ID with wp mcp-adapter list
A typo in --server silently falls back to the first available server, not an error.
Pick --user deliberately, not admin by default
Use the least-privileged user that can still do what this server needs to do.
Test the exact command manually first
Run it in your terminal with a piped JSON-RPC request before trusting a GUI client to launch it correctly.

Test it

Confirm the server responds correctly as a specific, non-admin user, exactly the way a real client launch would behave:

# File: terminal
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' \
  | wp mcp-adapter serve --user=editor-test-user --server=my-plugin-content-server

You should get back the same tool list either way (tool visibility isn’t user-dependent here), but calling tools/call against my-plugin-create-draft-post as a user without edit_posts should now return a permission error rather than creating a post, confirming --user genuinely changes what current_user_can() sees inside the ability’s permission check.

Running --user=admin out of convenience and forgetting to change it

It’s easy to develop and test everything with --user=admin because it always works, and then leave that exact command in a shared client config. Anyone who can edit that config, or any tooling that reads it, effectively gets administrator-level access to your site through the MCP server. Treat --user the same way you’d treat a hardcoded password: pick the least privilege that gets the job done, and revisit it before sharing any config file with a wider audience.

Recap

wp mcp-adapter serve --server=<id> --user=<id|login|email> runs a registered MCP server over STDIO, a single process speaking JSON-RPC 2.0 with no HTTP session to manage. wp mcp-adapter list --format=<table|json|csv|yaml> shows what’s registered. The --user you choose directly determines what every ability’s permission check sees, so pick it as deliberately as you would any other access grant. Next, Lesson 8 covers why you’d register more than one server on the same site in the first place.

Resources & further reading

← Serving Abilities Over HTTP Transport Running Multiple MCP Servers on One Site →