Use your existing Grok Build OAuth session to add X search and Grok as an MCP tool inside Claude Code — no API key required.
- Grok Build CLI installed and authenticated via
grok login - Claude Code installed
- Python 3.10+ with
uvavailable (pip install uvorbrew install uv)
Verify your Grok auth is working:
grok -p "hello" # should respond without prompting for a keymkdir -p ~/.local/share/grok-mcp
uv venv ~/.local/share/grok-mcp/.venv
uv pip install mcp --python ~/.local/share/grok-mcp/.venvThen create the server file at ~/.local/share/grok-mcp/server.py:
#!/usr/bin/env python3
"""Grok CLI MCP Server — wraps the local `grok` binary (OAuth session, no API key)."""
import asyncio
import subprocess
from pathlib import Path
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
GROK_BIN = str(Path.home() / ".grok" / "bin" / "grok")
app = Server("grok-cli")
def _run_grok(prompt: str, max_turns: int = 5) -> str:
result = subprocess.run(
[GROK_BIN, "-p", prompt, "--always-approve", "--max-turns", str(max_turns)],
capture_output=True,
text=True,
timeout=120,
)
output = result.stdout.strip()
if result.returncode != 0 and not output:
output = result.stderr.strip() or f"grok exited with code {result.returncode}"
return output
@app.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="search_x",
description=(
"Search X (formerly Twitter) using Grok's real-time X search. "
"Returns recent posts, discussions, and analysis from X."
),
inputSchema={
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "What to search for on X",
},
"count": {
"type": "integer",
"description": "Approximate number of results to return (default 5)",
"default": 5,
},
},
"required": ["query"],
},
),
Tool(
name="ask_grok",
description=(
"Send an arbitrary prompt to Grok CLI. Use for web search, "
"X search, image generation, or any other Grok capability."
),
inputSchema={
"type": "object",
"properties": {
"prompt": {
"type": "string",
"description": "The prompt to send to Grok",
},
"max_turns": {
"type": "integer",
"description": "Max agent turns (default 5)",
"default": 5,
},
},
"required": ["prompt"],
},
),
]
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
if name == "search_x":
query = arguments["query"]
count = arguments.get("count", 5)
prompt = (
f"Search X/Twitter for: {query}. "
f"Return the top {count} results with post content, author, "
f"engagement stats (likes/reposts), and post URLs."
)
output = await asyncio.to_thread(_run_grok, prompt, 8)
return [TextContent(type="text", text=output)]
elif name == "ask_grok":
prompt = arguments["prompt"]
max_turns = arguments.get("max_turns", 5)
output = await asyncio.to_thread(_run_grok, prompt, max_turns)
return [TextContent(type="text", text=output)]
else:
return [TextContent(type="text", text=f"Unknown tool: {name}")]
async def main():
async with stdio_server() as (read_stream, write_stream):
await app.run(read_stream, write_stream, app.create_initialization_options())
if __name__ == "__main__":
asyncio.run(main())claude mcp add --scope user grok-cli \
-- \
~/.local/share/grok-mcp/.venv/bin/python \
~/.local/share/grok-mcp/server.py--scope user makes it available in every Claude Code session (not just the current project).
Restart Claude Code, then try:
"Use search_x to find recent posts about [topic] on X"
"Use ask_grok to search X for AI news from today"
- The MCP server is a tiny Python process that Claude Code spawns on startup via stdio.
- Each tool call shells out to
~/.grok/bin/grok -p "..."with--always-approve. - Grok reads its OAuth token from
~/.grok/auth.json— the same session fromgrok login. - No API key is needed or used anywhere.
If Grok stops responding, refresh your session:
grok auth logout && grok loginThen restart Claude Code.