Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Reverse Engineering — MCP Servers

How to discover, evaluate, and map Model Context Protocol (MCP) servers for skills that need to connect as MCP clients. Unlike web reverse-engineering, MCPs are self-describingtools/list hands you the full tool catalog. What you’re reverse-engineering is auth, actual response shapes, coverage gaps, and behavioral quirks.

This is Layer 7 of the reverse-engineering docs:

  • Layer 1: Transport1-transport
  • Layer 2: Discovery2-discovery
  • Layer 3: Auth & Runtime3-auth
  • Layer 4: Content4-content
  • Layer 5: Social Networks5-social
  • Layer 6: Desktop Apps6-desktop-apps
  • Layer 7: MCP Servers (this file) — discovering and evaluating MCPs for skill integration

Tool: The MCP test harness in agentos/scripts/mcp-test.mjs is the primary probe. Use it to discover tools, test calls, and inspect responses. Smithery registry (mcp-test.mjs smithery search) finds third-party MCPs.

Transport — use httpx, not urllib

HTTP MCPs (Granola, Linear, etc.) often sit behind CloudFront or Cloudflare. Python urllib and requests advertise http/1.1 and get flagged by JA4 fingerprinting. Follow 1-transport: use httpx with http2=True for Python probes. Node fetch is fine (negotiates HTTP/2). Skill-local probe scripts (e.g. skills/granola/mcp-probe.py) should use httpx.


Layer 0: Existence — Does the service have an MCP?

Before anything else, determine if an MCP exists for the service. Three discovery paths:

Convention probing

Most services follow predictable URL patterns. Probe these for every skill you have:

PatternExample
https://mcp.{domain}/mcpGranola: mcp.granola.ai/mcp, Linear: mcp.linear.app/mcp
https://{domain}/mcphttps://example.com/mcp
https://api.{domain}/mcphttps://api.example.com/mcp

Probe: Send a bare POST with an initialize JSON-RPC request. A 404 or connection refused means nothing there. A JSON-RPC response or auth challenge means you found one.

# Using mcp-test.mjs with a raw URL (no auth)
node scripts/mcp-test.mjs http https://mcp.granola.ai/mcp

Smithery registry

The Smithery registry indexes MCPs published by third parties. Use this for services that might have community MCPs but no official one:

node scripts/mcp-test.mjs smithery search "granola"
node scripts/mcp-test.mjs smithery search "linear"

Services are publicly announcing MCP support. Search for "{service name}" MCP or "{service name}" Model Context Protocol in changelogs, blog posts, or docs.

Output: Existence table

SkillMCP URLTransportStatus
granolamcp.granola.ai/mcpHTTPfound
linearmcp.linear.app/mcpHTTPfound
todoistnpx @abhiz123/todoist-mcp-serverstdiofound (3rd party)
goodreadsnone found

Layer 1: Transport — How does the session work?

MCPs run over two transports. The harness handles both; you need to log what you observe.

Streamable HTTP

  • POST JSON-RPC to the URL
  • Response may be plain JSON or SSE (event: message\ndata: {...})
  • mcp-session-id in response headers — session-stateful vs stateless
  • Used by: Granola, Linear, other hosted MCPs

Stdio

  • Spawn subprocess: npx -y @package/mcp-server
  • JSON-RPC over stdin/stdout, newline-delimited
  • Used by: Todoist, Notion, Slack (npm packages)

What to log

FieldHow to check
mcp-session-idResponse headers on first request
Response formatSSE vs plain JSON body
protocolVersionFrom initialize response result.serverInfo
capabilitiesTools, resources, prompts, logging
Server-initiated requestsAny during handshake?

Layer 2: Auth — What does it need and how do you get in?

MCP auth discovery is a waterfall. The protocol is designed for this.

Step 1: Naked probe

Send initialize with no auth headers.

OutcomeMeaning
SuccessPublic MCP, no auth (rare for user data)
401 with WWW-AuthenticateAuth required; header describes scheme
Connection accepted, tools/call failsAuth is per-call, not per-session

Step 2: OAuth discovery

Two discovery paths. The 401 response may include resource_metadata pointing at one of these:

Protected resource discovery (RFC 9729):

GET {origin}/.well-known/oauth-protected-resource

Returns authorization_servers, resource, bearer_methods_supported. Example: Granola’s 401 pointed to this; response: {"authorization_servers":["https://mcp-auth.granola.ai"], ...}.

OAuth authorization server discovery (RFC 8414):

GET {origin}/.well-known/oauth-authorization-server

Returns authorization_endpoint, token_endpoint, scopes_supported for the full OAuth flow.

Step 3: Token reuse hypothesis

For services where you already have a skill: can you reuse the existing token? Granola’s supabase token, Linear’s API key — do they work as Authorization: Bearer {token} against the MCP endpoint? This is a single-line test.

Step 4: Scope mapping

Once authenticated: does the MCP give full access or a restricted view? Some MCPs expose read-only tools even if the underlying API supports writes.


Layer 3: Tool catalog — What’s exposed?

This is where MCPs are radically easier than web reverse-engineering. tools/list returns the full catalog:

{
  "tools": [{
    "name": "list_meetings",
    "description": "List recent meetings",
    "inputSchema": { "type": "object", "properties": { "limit": { "type": "integer" } } }
  }]
}

Cross-reference with existing skill

For each MCP tool, find the corresponding operation in your existing skill. Build a coverage matrix:

Your OperationMCP ToolMatch?Notes
list_meetingslist_meetingsexactSame params?
get_meetingget_documentname differsCheck if transcript included
list_conversationsno matchMCP doesn’t expose Q&A
create_noteno matchMCP has write we don’t

This matrix is the key deliverable — it tells you whether the MCP is superset, subset, or lateral complement.

Annotation analysis

Check tool.annotations:

  • readOnlyHint — safe to probe, no mutating
  • destructiveHint — mutates state, be careful in testing

Layer 4: Response analysis — What does the data look like?

MCP input schemas are declared; output is usually opaque content: [{type: "text", text: "..."}]. You must call each tool and inspect.

For each read-safe tool

  1. Call with minimal params
  2. Unwrap content[0].text and parse as JSON
  3. Document the actual response shape — field names, nesting, types
  4. Compare field-by-field to your existing skill’s normalized output

This answers: Is the MCP richer, thinner, or different? Does Granola’s MCP return raw utterances like the internal API, or only a pre-formatted summary?


Layer 5: Gap analysis — Is it worth connecting?

For each service, combine layers 0–4 into a verdict:

SignalImplication
MCP covers all your operations with equal or richer dataMCP as primary, existing skill as fallback
MCP covers some, misses othersMulti-connection: MCP for what it covers, API for the rest
MCP is thinner than your skillKeep existing skill; MCP not worth it
MCP exposes tools you don’t have (especially writes)MCP as additive connection
Auth is significantly easier via MCPMCP worth it for auth stability alone

Running the analysis

For each service, work through layers 0–4 using mcp-test.mjs:

# Generic harness (agentos repo) — pass URL; MCP_BEARER_TOKEN for auth
node scripts/mcp-test.mjs http https://mcp.granola.ai/mcp
node scripts/mcp-test.mjs http https://mcp.granola.ai/mcp call list_meetings '{"limit": 3}'

# Skill-local exploration (agentos-community) — reads token from Granola app
python3 skills/granola/mcp-probe.py
python3 skills/granola/mcp-probe.py tools

Start with services where you already have skills (Granola, Linear). You have ground truth — your existing skill tells you exactly what data to expect. The output is a completed coverage matrix and a clear verdict.


Real-World Examples

SkillMCP URLTransportAuthCoverage
granolamcp.granola.ai/mcpHTTPDifferent auth — supabase token invalid. 401 returns WWW-Authenticate: Bearer error="invalid_token", resource_metadata="https://mcp.granola.ai/.well-known/oauth-protected-resource". MCP uses OAuth; internal API token does not work. Probe with httpx succeeds (no TLS block).
linearmcp.linear.app/mcpHTTPOAuth / API keyTBD — run analysis

Fill this table as you run the analysis. See skills/granola/ for the existing Python skill’s operations and adapter schema.

Probe commands

Use the generic harness (no service-specific code) or skill-local scripts:

# Generic MCP harness (agentos repo) — pass URL; set MCP_BEARER_TOKEN for auth
node scripts/mcp-test.mjs http https://mcp.granola.ai/mcp
MCP_BEARER_TOKEN=$(python3 -c "
import json
from pathlib import Path
p = Path.home() / 'Library/Application Support/Granola/supabase.json'
t = json.loads(json.load(p.open())['workos_tokens'])
print(t['access_token'])
") node scripts/mcp-test.mjs http https://mcp.granola.ai/mcp

# Skill-local exploration (agentos-community)
python3 skills/granola/mcp-probe.py