GEEK HAUS
> geekhaus:~$ man mcp

MCP Service

Model Context Protocol service for AI assistants to access Geek Haus curated tech news.

MCP Service

The Geek Haus MCP server exposes our curated feed to AI assistants over Model Context Protocol. Any client that speaks Streamable HTTP MCP can connect, no install required.

Endpoint

https://geekhaus.club/api/mcp
  • Transport: Streamable HTTP (POST). SSE is not supported (deprecated by the spec).
  • Protocol version: 2025-06-18.
  • Session model: stateless. No Mcp-Session-Id is required between calls — every request is independent.

Authentication

The same Partner API key authenticates MCP requests. Send it as a Bearer token in the Authorization header. To get a key, contact us.

Connecting a client

Most modern MCP clients (Claude Desktop, Cursor, Windsurf, ChatGPT custom connectors) accept a JSON config like this:

{
  "mcpServers": {
    "geekhaus": {
      "url": "https://geekhaus.club/api/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_API_KEY"
      }
    }
  }
}

For stdio-only clients, wrap with mcp-remote:

{
  "mcpServers": {
    "geekhaus": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://geekhaus.club/api/mcp",
        "--header",
        "Authorization: Bearer YOUR_API_KEY"
      ]
    }
  }
}

Where each client expects this config

ClientConfig location
Claude DesktopmacOS: ~/Library/Application Support/Claude/claude_desktop_config.json · Windows: %APPDATA%\Claude\claude_desktop_config.json
Cursor~/.cursor/mcp.json (global) or .cursor/mcp.json (per-project)
Windsurf~/.codeium/windsurf/mcp_config.json
ChatGPT custom connectorsSettings → Connectors → Add MCP server (paste URL + Bearer key in UI)

After editing the config, restart the client. The tools show up in the client's tool picker — try a prompt like "Use geekhaus to show me the top tech stories this week" to confirm the wiring works.

Available tools

ToolPurpose
search_articlesBrowse articles with optional filters (source slug, since, limit, cursor).
get_articleFetch one article by slug.
list_sourcesList every publication tracked, with article counts.
get_weekly_digestTop recent articles within a days window (default 7 days, 7 items).

All tools accept an optional locale argument (en or ko, default en).

Argument schemas

search_articles

ArgTypeRequiredDefaultNotes
locale'en' | 'ko'no'en'
sourcestringnoSource slug (see list_sources).
sinceISO-8601 datetimenoLower bound.
limitinteger 1-100no20
cursorstringnoOpaque cursor from previous response.

get_article

ArgTypeRequiredDefault
slugstringyes
locale'en' | 'ko'no'en'

list_sources

ArgTypeRequiredDefault
locale'en' | 'ko'no'en'

get_weekly_digest

ArgTypeRequiredDefault
locale'en' | 'ko'no'en'
daysinteger 1-90no7
limitinteger 1-50no7

Tool results return as { content: [{ type: "text", text: "<json>" }] }. The text field is the JSON-stringified response from the matching REST endpoint, so all Partner API field semantics apply.

Low-level protocol examples

Every MCP call is a JSON-RPC 2.0 POST. The server responds with Content-Type: text/event-stream containing one data: line per JSON-RPC message. Parse the line after data: as JSON to get the actual response.

Initialize

Request:

curl -X POST https://geekhaus.club/api/mcp \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  --data '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-06-18",
      "capabilities": {},
      "clientInfo": { "name": "my-app", "version": "1.0.0" }
    }
  }'

Response (SSE frame body):

event: message
data: {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{"listChanged":true}},"serverInfo":{"name":"geekhaus-mcp","version":"1.0.0"}}}

tools/list

Request:

curl -X POST https://geekhaus.club/api/mcp \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  --data '{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }'

Response (truncated):

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      { "name": "search_articles", "title": "Search articles", "description": "..." },
      { "name": "get_article", "title": "Get article", "description": "..." },
      { "name": "list_sources", "title": "List sources", "description": "..." },
      { "name": "get_weekly_digest", "title": "Get weekly digest", "description": "..." }
    ]
  }
}

tools/call — search_articles

Request:

curl -X POST https://geekhaus.club/api/mcp \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  --data '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
      "name": "search_articles",
      "arguments": { "source": "anthropic-com", "limit": 2 }
    }
  }'

Response:

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"items\":[{\"id\":\"...\",\"slug\":\"2026/05/22/...\",\"title\":\"...\",\"summary\":\"...\"}],\"nextCursor\":\"2026-05-22T...\"}"
      }
    ]
  }
}

The text field is a JSON-stringified /api/v1/articles response. Parse it with JSON.parse(result.content[0].text).

Client SDK example (TypeScript)

Using @modelcontextprotocol/sdk:

import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
 
const transport = new StreamableHTTPClientTransport(new URL('https://geekhaus.club/api/mcp'), {
  requestInit: {
    headers: { Authorization: 'Bearer YOUR_API_KEY' },
  },
})
 
const client = new Client({ name: 'my-app', version: '1.0.0' })
await client.connect(transport)
 
const tools = await client.listTools()
console.log(tools.tools.map((t) => t.name))
 
const result = await client.callTool({
  name: 'get_weekly_digest',
  arguments: { days: 7, limit: 5 },
})
 
const payload = JSON.parse(result.content[0].text)
console.log(payload.items.length, 'articles in the last 7 days')

Client SDK example (Python)

Using mcp (the official Python SDK):

import asyncio
from mcp.client.session import ClientSession
from mcp.client.streamable_http import streamablehttp_client
 
URL = "https://geekhaus.club/api/mcp"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}
 
async def main():
    async with streamablehttp_client(URL, headers=HEADERS) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            tools = await session.list_tools()
            print([t.name for t in tools.tools])
 
            result = await session.call_tool(
                "search_articles", {"source": "anthropic-com", "limit": 3}
            )
            import json
            payload = json.loads(result.content[0].text)
            print(len(payload["items"]), "articles from anthropic.com")
 
asyncio.run(main())

Errors

Missing or invalid keys return 401 with the standard envelope:

{ "error": { "code": "unauthorized", "message": "Missing API key." } }

Tool-level errors propagate inside the JSON-RPC error field per spec.

Notes

  • The MCP server and Partner API share the same data layer and editorial filtering. Whatever you see through REST, you will see through MCP.
  • Tool results are returned as JSON-stringified text. Most clients pass this straight to the model without further parsing.
  • Reach out via contact for access, feedback, or to request a new tool.