context7 mcp server is the read half. You also need a write half.
Every writeup of context7 shows it installed alone, as if a model equipped with fresh library docs has everything it needs. It does not. Docs are the reading. Code is the writing. And if the output is a message you actually want sent, a reference server alone cannot send it.
This guide pairs context7 with whatsapp-mcp-macos, a local action MCP server that drives the real WhatsApp app on your Mac. Together they close the loop: context7 tells the model how, whatsapp-mcp proves it worked. Both live in the same ~/.claude.json. Here is the exact shape.
The two shapes of MCP server
Almost every MCP server ever written lands on one side of a simple line. Either it reaches outside the host to pull reference material (docs, web pages, vector stores, GitHub metadata), or it reaches inside the user's machine to do something (edit files, drive an app, send a message, hit a database). context7 is a textbook example of the first. whatsapp-mcp-macos is a textbook example of the second.
Most guides treat all MCP servers as interchangeable plugins. They are not. The reader shape and the writer shape have different transports, different permission models, different failure modes, and different debugging playbooks. The table below is how I hold them apart when I design a pairing.
context7 vs whatsapp-mcp-macos
Same protocol. Two shapes. Different everything else.
| Feature | context7 | whatsapp-mcp |
|---|---|---|
| Transport | stdio to a node process, can also run in HTTP/SSE mode against context7.com | stdio to a compiled Swift binary. Local only. No HTTP mode. |
| Reach | Reads documentation for libraries hosted on context7's index. | Drives whichever WhatsApp app is running on this specific Mac. |
| Tool shape | Reference tools: resolve a library, fetch its docs, return text. | Action tools: search contacts, open chat, send message, verify delivery. |
| State per call | Stateless. Each fetch is a fresh HTTP request. | Stateful UI. The active chat is implicit in WhatsApp's window, not in the server. |
| Auth | CONTEXT7_API_KEY env var, free tier available, rate-limited per key. | Zero env vars. Inherits macOS Accessibility trust from the host process. |
| Failure budget | Network timeouts, rate limits, cold starts on the docs index. | 5.0 second AX messaging timeout (main.swift line 119) on every UI call. |
| What breaks it | Upstash endpoint down, wrong library name, quota exhausted. | WhatsApp window not frontmost, Accessibility not granted to host, app still loading chats. |
Why pair them at all
A model equipped only with a reference server can produce plausible code and plausible message drafts, but it cannot verify that either is correct. A model equipped only with an action server can act but has no fresh ground truth for the code or copy it is about to commit to. The pair is not a nice-to-have, it is the minimum viable read/write loop.
context7 answers how
When the model is about to write code that calls a library (say, a templating engine or a formatter), context7 returns the current API surface so it stops hallucinating signatures from 2023.
whatsapp-mcp answers did it work
Once the model has the code and the content, it actually types into the real WhatsApp app and reads the last-message node back to confirm delivery. No mocks, no dry runs.
The pair closes the loop
A read-only reference server by itself tells you about the world. A write-only action server by itself guesses at it. Together they let a model do what a careful human does: look up the spec, apply it, verify the outcome.
The exact ~/.claude.json entry
Here is the whole thing. One host. Two children. Notice what is the same (both are type: "stdio", both are forked by the host on launch) and what is different (context7 needs an API key in env, whatsapp-mcp needs zero env vars and inherits its permission from the host).
Restart Claude Code once after saving. On next launch the host will fork both binaries, send each an initialize frame, and register the union of their tools in the model's tool catalog.
How the host holds both children together
Both servers are sibling processes of the host. They do not talk to each other. The host is the only thing that reads from one and, on a later turn, writes to the other. That is the whole coordination layer.
one host routes the model's tools/call frames to the right child
The beams are one-at-a-time, not simultaneous. The host serializes tool calls through the model's turn loop.
anchor fact
0s AX timeout, 0 action tools, 0 env vars
The whole error budget of the write half is set by one line in the whatsapp-mcp source. On line 119 of Sources/WhatsAppMCP/main.swift, the call AXUIElementSetMessagingTimeout(appElement, 5.0) is hardcoded. Every tool that touches the WhatsApp UI (search, open, send, read) has a hard 5.0 second ceiling per accessibility call. There is no configuration flag that loosens it.
The binary itself registers exactly 11 tools and takes zero environment variables. That is what lets it sit next to a context7 server that needs a CONTEXT7_API_KEY without any special arrangement: the two halves of the pair each carry their own auth story, and the host does not have to mediate.
Where the timeout lives
If you care about the budget, care about this line. Nothing context7 does, no matter how fast or slow, relaxes the action side's ceiling. Everything whatsapp-mcp does is bounded by it.
What one whatsapp_send_message call actually does
The action half is more than a function call. Every write goes through six steps inside the child process, and step five is the one most AI tools skip: verification. whatsapp-mcp reads back the last rendered message node and compares it to what was sent before returning success.
This is where pairing pays off. context7 can tell the model how a library formats a number, but only whatsapp-mcp can tell it whether the resulting message actually rendered on the recipient's screen.
Set it up in five steps
No step below touches the network except the first, and no step opens a port. The entire pairing is local process plumbing plus one npm registry fetch.
from zero to paired servers
- 1
Install context7
No global install needed. The npx invocation fetches it on first use.
- 2
Install whatsapp-mcp
npm install -g whatsapp-mcp-macos, which runs xcrun swift build in postinstall.
- 3
Add both to ~/.claude.json
One object for context7 (with CONTEXT7_API_KEY), one for whatsapp with no env.
- 4
Grant Accessibility to the host
Not to whatsapp-mcp. The host forks the child, so the child inherits that trust.
- 5
Restart the host
Claude Code or Cursor reads the config once per launch. Two new child processes appear.
Watching both children boot
The quickest way to sanity check the pairing is to watch the process tree before and after you launch the host. Two new children appear with the host as their parent, and both die the moment you quit.
Both ppids match the host pid, because both servers are siblings of each other, not of the host. Their lifetimes are strict sub-intervals of the host's.
When you outgrow the pair
The reader/writer pair is the minimum. Real workflows often add more readers and more writers, but the shape stays the same: each one is an independent stdio child, each one declares its own tools at handshake, and none of them talk to each other.
good third and fourth servers to add
- filesystem MCP, when the model also needs to edit code in your repo
- github MCP, when the work output is a PR, not just a sent message
- slack MCP, when the same AI also posts internal updates
- sqlite or postgres MCP, when the reply is driven by real user state
- puppeteer MCP, only when stdio-based UI automation falls short
The pairing, in chips
Every chip above is a property the pair inherits automatically from the forked-child shape of MCP. Nothing you have to configure.
The permission model is where the pairing actually shows
context7 needs an API key in its env. whatsapp-mcp needs macOS Accessibility trust granted to the host process. Those two feel similar but they are not: the API key flows into context7 by way of the host's environment export, which the child inherits at fork time. The Accessibility grant flows into whatsapp-mcp by way of macOS's TCC database, which attributes the privileged call to the responsible process, and that process is the host, not the server.
The practical consequence is that you grant Accessibility to Claude Code (or Cursor, or whichever host is forking the pair), not to whatsapp-mcp itself. If you drag the binary into System Settings and tick the box, nothing happens. The child inherits its privilege, it never asks for its own.
context7 has no equivalent concern because it does not call any TCC-gated macOS API. It only opens an outbound HTTPS socket, and outbound sockets need no permission beyond network availability. This asymmetry is why the reader half is almost always easier to set up than the writer half.
Wiring a read + write MCP pair into your agent stack?
30 minutes to walk through your config, permissions, and the right failure budget for each server. Bring your ~/.claude.json.
Frequently asked questions
Is context7 an MCP server, and what kind?
Yes. context7 is a reference-shaped MCP server: it exposes tools that fetch up-to-date library documentation, typically via HTTP to context7.com, and returns the text to the host. In almost every installation it is registered as a stdio child process (a node executable launched by npx), which means from the host's point of view it behaves exactly like any other stdio MCP server, even though the node process itself makes outbound HTTP calls.
Why would I pair context7 with whatsapp-mcp-macos?
Because they sit on opposite ends of the MCP tool taxonomy. context7 is a reader: it pulls documentation, it does not change anything. whatsapp-mcp-macos is a writer: it drives the real WhatsApp app on your Mac, sends messages, and verifies delivery. Most real-world workflows need both. A model that only has context7 can explain how to send a WhatsApp message but cannot actually send one. A model that only has whatsapp-mcp can send a message but has no fresh source of truth for the code, copy, or API it is about to use.
Do the two servers talk to each other directly?
No. MCP servers never talk to each other. They are sibling child processes of the same host. The host is the only entity that reads from one and writes to another, and it does so implicitly by feeding tool results from context7 into the model's context, which may later emit a tools/call for a whatsapp-mcp tool. There is no socket, no shared file, no inter-server protocol. If you want to coordinate them, you coordinate them in prompt.
What changes in ~/.claude.json when I add a second server?
You add a second key under mcpServers. Each key is an independent process recipe: its own command, args, env, and optional cwd. The host spawns one child per key at startup and routes tools/list and tools/call requests to whichever child owns the tool. The two children have no awareness of each other. Restarting the host kills both children and respawns them; there is no hot reload for either.
Does context7 need Accessibility permission on macOS?
No. context7 makes outbound HTTP calls and does not touch the UI, so it has nothing to ask the accessibility APIs. whatsapp-mcp-macos does need Accessibility, but the grant is on the host process, not on whatsapp-mcp itself. Because whatsapp-mcp is a child process forked by the host, macOS checks the host's TCC (Transparency, Consent, Control) record when the AX call is made. Adding whatsapp-mcp to System Settings > Privacy & Security > Accessibility on its own does nothing.
What does whatsapp-mcp's 5.0 second timeout have to do with context7?
It sets your error budget whenever the action half of the loop is involved. On line 119 of Sources/WhatsAppMCP/main.swift the call AXUIElementSetMessagingTimeout(appElement, 5.0) is hardcoded, which means every tool that touches the WhatsApp UI (search, open, send, read) can take at most 5 seconds before the AX call returns an error. context7 has its own, longer HTTP timeouts, so a typical turn looks like: context7 resolves docs in ~1 second, whatsapp-mcp performs a search-and-send in ~1 to 3 seconds, with a hard 5-second ceiling per AX call. Plan prompts accordingly.
Can I run context7 in its remote HTTP mode and still pair it with a local whatsapp-mcp?
Yes. MCP lets the same host mix transports. context7 can be registered with an http URL that points at context7.com, in which case the host opens a long-lived streaming connection instead of forking a node child, while whatsapp continues as a stdio child of the host. The reader/writer shape of the pairing is unaffected. The one thing to keep in mind is that http MCP servers cannot inherit the host's OS trust the way stdio servers can, so any server that needs Accessibility (like whatsapp-mcp) must remain stdio, not http.
Which tool does context7 provide that I should expect the model to call first?
In the typical library-docs flow, context7 exposes a resolve-library tool followed by a fetch-docs tool. The model calls the resolver with a human-readable library name, gets back a canonical identifier, then calls the fetcher with that identifier to stream the actual documentation into its context. Only after that does it start calling whatsapp-mcp tools. If you see whatsapp_send_message being called with code the model has clearly guessed at, the issue is that it skipped the context7 lookup, which usually means you did not ask it to check the docs first.
Is whatsapp-mcp usable without context7?
Absolutely. whatsapp-mcp is useful on its own for any workflow where the model already knows what it wants to say and just needs to act: sending a standup summary, routing an incoming question to a teammate, checking unread chats on a schedule. The pairing argument is not that context7 is required, it is that context7 is the natural reader sibling when the model also needs to generate or modify code as part of the message it is about to send.
How do I debug the pairing when it misbehaves?
Start by running pgrep -fl '(context7|whatsapp-mcp)' with the host open. You should see two children, both owned by the host's pid. If one is missing, the host failed to spawn it; check the host's MCP log for the exact stderr line. For context7, run npx -y @upstash/context7-mcp manually in a terminal and send an initialize frame on stdin to confirm it boots. For whatsapp-mcp, launch it the same way and send a tools/list request; the server's own log line (setupAndStartServer: defined 11 tools) should appear on stderr. If both boot manually but do not work through the host, the culprit is almost always the host config, not the servers.