Skip to content

Instantly share code, notes, and snippets.

@Evan-Kim2028
Created June 5, 2026 17:03
Show Gist options
  • Select an option

  • Save Evan-Kim2028/44b5ccb04cb3ed4fcb1e6cfb000a6564 to your computer and use it in GitHub Desktop.

Select an option

Save Evan-Kim2028/44b5ccb04cb3ed4fcb1e6cfb000a6564 to your computer and use it in GitHub Desktop.
Grok CLI as MCP Server for Claude Code (X search, no API key)

Grok CLI as MCP Server for Claude Code (No API Key)

Use your existing Grok Build OAuth session to add X search and Grok as an MCP tool inside Claude Code — no API key required.

Prerequisites

  • Grok Build CLI installed and authenticated via grok login
  • Claude Code installed
  • Python 3.10+ with uv available (pip install uv or brew install uv)

Verify your Grok auth is working:

grok -p "hello"   # should respond without prompting for a key

1. Create the MCP server

mkdir -p ~/.local/share/grok-mcp
uv venv ~/.local/share/grok-mcp/.venv
uv pip install mcp --python ~/.local/share/grok-mcp/.venv

Then 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())

2. Register with Claude Code

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).


3. Restart Claude Code and test

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"


How it works

  • 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 from grok login.
  • No API key is needed or used anywhere.

Token expiry

If Grok stops responding, refresh your session:

grok auth logout && grok login

Then restart Claude Code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment