Skip to content

[Docs]: Add Developer Guide for using MCP via the CLI (curl commands, JSON-RPC) #19

@crivetimihai

Description

@crivetimihai

Overview

This guide demonstrates how to interact with MCP (Model Context Protocol) servers using raw JSON-RPC commands via curl or through STDIO. This is useful for developers who want to understand the protocol at a low level or integrate MCP into custom applications.

This also serves as a sanity check to test / debug the MCP implementation provided by the gateway.

Prerequisites

  • MCP Gateway server running (typically on http://localhost:4444)
  • Authentication token generated (if required)
  • curl command-line tool
  • jq for JSON formatting (optional but recommended)

Authentication Setup

Before making any requests, generate a bearer token:

# Generate authentication token
export MCPGATEWAY_BEARER_TOKEN=$(python3 -m mcpgateway.utils.create_jwt_token -u admin --secret my-test-key)
echo ${MCPGATEWAY_BEARER_TOKEN}

# Test connectivity
curl -s -k -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/health

MCP Protocol Flow

1. Initialize the Protocol

Every MCP session must start with an initialization handshake. Note: There's currently a bug with the initialize method via /rpc endpoint, so use the dedicated protocol endpoint:

# Initialize the protocol (RECOMMENDED - works around /rpc bug)
curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
           "protocol_version":"2025-03-26",
           "capabilities":{},
           "client_info":{"name":"demo","version":"0.0.1"}
         }' \
     http://localhost:4444/protocol/initialize

# Alternative: Via /rpc (currently has a bug - see troubleshooting)
# curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \
#      -H "Content-Type: application/json" \
#      -d '{
#            "jsonrpc":"2.0",
#            "id":1,
#            "method":"initialize",
#            "params":{
#              "protocolVersion":"2025-03-26",
#              "capabilities":{},
#              "clientInfo":{"name":"demo","version":"0.0.1"}
#            }
#          }' \
#      http://localhost:4444/rpc

Expected Response:

{
  "jsonrpc":"2.0",
  "id":1,
  "result":{
    "protocolVersion":"2025-03-26",
    "capabilities":{
      "experimental":{},
      "prompts":{"listChanged":false},
      "resources":{"subscribe":false,"listChanged":false},
      "tools":{"listChanged":false}
    },
    "serverInfo":{"name":"mcpgateway-wrapper","version":"0.2.0"}
  }
}

2. Send Initialization Notification

After successful initialization, send the initialized notification:

# Send initialized notification
curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
           "jsonrpc":"2.0",
           "method":"notifications/initialized",
           "params":{}
         }' \
     http://localhost:4444/rpc

Alternative: Server-Sent Events (SSE) Approach

For more robust communication, especially when dealing with multiple requests or when the /rpc endpoint has issues, you can use the SSE-based approach:

Establishing SSE Connection

# Terminal 1: Start SSE connection (keeps connection open)
curl -N -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \
     http://localhost:4444/sse

This will establish a persistent connection and you'll see responses in real-time.

Sending Messages via SSE

In a separate terminal, send JSON-RPC messages:

# Initialize
curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
           "jsonrpc":"2.0",
           "id":1,
           "method":"initialize",
           "params":{
             "protocolVersion":"2025-03-26",
             "capabilities":{},
             "clientInfo":{"name":"demo","version":"0.0.1"}
           }
         }' \
     http://localhost:4444/message

# List tools
curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
           "jsonrpc":"2.0",
           "id":2,
           "method":"tools/list"
         }' \
     http://localhost:4444/message

# Call a tool
curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
           "jsonrpc":"2.0",
           "id":3,
           "method":"tools/call",
           "params":{
             "name":"get_current_time",
             "arguments":{"timezone":"Europe/Dublin"}
           }
         }' \
     http://localhost:4444/message

The SSE approach provides real-time responses and avoids the async/await issues present in the /rpc endpoint.

Working with Tools

List Available Tools

# List all tools
curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
           "jsonrpc":"2.0",
           "id":2,
           "method":"tools/list"
         }' \
     http://localhost:4444/rpc

Expected Response (no tools):

{
  "jsonrpc":"2.0",
  "id":2,
  "result":{"tools":[]}
}

Expected Response (with tools):

{
  "jsonrpc":"2.0",
  "id":2,
  "result":{
    "tools":[
      {
        "name":"get_current_time",
        "description":"Get current time in a specific timezone",
        "inputSchema":{
          "type":"object",
          "properties":{
            "timezone":{
              "type":"string",
              "description":"IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Use 'America/New_York' as local timezone if no timezone provided by the user."
            }
          },
          "required":["timezone"]
        }
      }
    ]
  }
}

Call a Tool

# Call the get_current_time tool
curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
           "jsonrpc":"2.0",
           "id":3,
           "method":"tools/call",
           "params":{
             "name":"get_current_time",
             "arguments":{"timezone":"Europe/Dublin"}
           }
         }' \
     http://localhost:4444/rpc

Expected Response:

{
  "jsonrpc":"2.0",
  "id":3,
  "result":{
    "content":[
      {
        "type":"text",
        "text":"{\n  \"timezone\": \"Europe/Dublin\",\n  \"datetime\": \"2025-06-08T21:47:07+01:00\",\n  \"is_dst\": true\n}"
      }
    ],
    "isError":false
  }
}

Working with Prompts

List Available Prompts

# List all prompts
curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
           "jsonrpc":"2.0",
           "id":4,
           "method":"prompts/list"
         }' \
     http://localhost:4444/rpc

Get a Specific Prompt

# Get a prompt with arguments
curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
           "jsonrpc":"2.0",
           "id":5,
           "method":"prompts/get",
           "params":{
             "name":"greeting",
             "arguments":{"user":"Bob"}
           }
         }' \
     http://localhost:4444/rpc

Working with Resources

List Available Resources

# List all resources
curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
           "jsonrpc":"2.0",
           "id":6,
           "method":"resources/list"
         }' \
     http://localhost:4444/rpc

Read a Resource

# Read a specific resource
curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
           "jsonrpc":"2.0",
           "id":7,
           "method":"resources/read",
           "params":{
             "uri":"https://example.com/some.txt"
           }
         }' \
     http://localhost:4444/rpc

Utility Methods

Ping the Server

# Ping to test connectivity
curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
           "jsonrpc":"2.0",
           "id":8,
           "method":"ping"
         }' \
     http://localhost:4444/rpc

Expected Response:

{
  "jsonrpc":"2.0",
  "id":8,
  "result":{}
}

Using with STDIO

For STDIO usage, you can use the MCP Gateway Wrapper:

# Set environment variables
export MCP_AUTH_TOKEN=${MCPGATEWAY_BEARER_TOKEN}
export MCP_SERVER_CATALOG_URLS=http://localhost:4444/servers/1
export MCP_TOOL_CALL_TIMEOUT=120

# Run the wrapper
python3 -m mcpgateway.wrapper

Then send JSON-RPC commands directly to stdin:

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"demo","version":"0.0.1"}}}
{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}
{"jsonrpc":"2.0","id":2,"method":"tools/list"}

Error Handling

MCP follows JSON-RPC 2.0 error handling. Common error responses:

{
  "jsonrpc":"2.0",
  "id":1,
  "error":{
    "code":-32602,
    "message":"Invalid params",
    "data":"Missing required parameter: name"
  }
}

Common Error Codes

  • -32700: Parse error
  • -32600: Invalid Request
  • -32601: Method not found
  • -32602: Invalid params
  • -32603: Internal error

Best Practices

  1. Always initialize first: Start every session with the initialize method
  2. Use appropriate timeouts: Set reasonable timeouts for tool calls
  3. Handle errors gracefully: Check for error objects in responses
  4. Use unique IDs: Ensure each request has a unique ID for correlation
  5. Follow the protocol: Send the initialized notification after successful initialization

Example Complete Session

Here's a complete example session with proper formatting using the working endpoints:

#!/bin/bash

# Set up authentication
export MCPGATEWAY_BEARER_TOKEN=$(python3 -m mcpgateway.utils.create_jwt_token -u admin --secret my-test-key)

# Function to make JSON-RPC calls via /rpc (for methods other than initialize)
make_rpc_call() {
    curl -s -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \
         -H "Content-Type: application/json" \
         -d "$1" \
         http://localhost:4444/rpc | jq
}

# Function to make protocol calls (for initialize)
make_protocol_call() {
    curl -s -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \
         -H "Content-Type: application/json" \
         -d "$1" \
         http://localhost:4444/protocol/initialize | jq
}

# 1. Initialize (using protocol endpoint to avoid bug)
echo "=== Initializing ==="
make_protocol_call '{
  "protocol_version":"2025-03-26",
  "capabilities":{},
  "client_info":{"name":"demo","version":"0.0.1"}
}'

# 2. Send initialized notification (optional for HTTP-based calls)
echo "=== Sending initialized notification ==="
make_rpc_call '{
  "jsonrpc":"2.0",
  "method":"notifications/initialized",
  "params":{}
}'

# 3. List tools
echo "=== Listing tools ==="
make_rpc_call '{
  "jsonrpc":"2.0",
  "id":2,
  "method":"tools/list"
}'

# 4. Call a tool (if available)
echo "=== Calling tool ==="
make_rpc_call '{
  "jsonrpc":"2.0",
  "id":3,
  "method":"tools/call",
  "params":{
    "name":"get_current_time",
    "arguments":{"timezone":"Europe/Dublin"}
  }
}'

# 5. Ping test
echo "=== Ping test ==="
make_rpc_call '{
  "jsonrpc":"2.0",
  "id":4,
  "method":"ping"
}'

Alternative: Complete SSE Session

For a more robust approach using SSE:

#!/bin/bash

# Set up authentication
export MCPGATEWAY_BEARER_TOKEN=$(python3 -m mcpgateway.utils.create_jwt_token -u admin --secret my-test-key)

echo "Starting SSE connection in background..."
# Start SSE connection in background
curl -N -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \
     http://localhost:4444/sse &
SSE_PID=$!

# Give SSE time to connect
sleep 2

# Function to send messages via SSE
send_sse_message() {
    curl -s -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \
         -H "Content-Type: application/json" \
         -d "$1" \
         http://localhost:4444/message
}

echo "=== Sending initialize message ==="
send_sse_message '{
  "jsonrpc":"2.0",
  "id":1,
  "method":"initialize",
  "params":{
    "protocolVersion":"2025-03-26",
    "capabilities":{},
    "clientInfo":{"name":"demo","version":"0.0.1"}
  }
}'

sleep 1

echo "=== Sending tools/list message ==="
send_sse_message '{
  "jsonrpc":"2.0",
  "id":2,
  "method":"tools/list"
}'

sleep 1

echo "=== Sending tool call message ==="
send_sse_message '{
  "jsonrpc":"2.0",
  "id":3,
  "method":"tools/call",
  "params":{
    "name":"get_current_time",
    "arguments":{"timezone":"Europe/Dublin"}
  }
}'

# Clean up
sleep 5
kill $SSE_PID

Integration with Claude Desktop

To use this with Claude Desktop, create a configuration that points to the STDIO wrapper:

{
  "mcpServers": {
    "mcpgateway-wrapper": {
      "command": "python3",
      "args": ["-m", "mcpgateway.wrapper"],
      "env": {
        "MCP_AUTH_TOKEN": "<your-token>",
        "MCP_SERVER_CATALOG_URLS": "http://localhost:4444/servers/1",
        "MCP_TOOL_CALL_TIMEOUT": "120"
      }
    }
  }
}

Troubleshooting

Common Issues

  1. Authentication errors: Ensure your bearer token is valid and not expired
  2. Connection refused: Check that the MCP Gateway server is running
  3. Method not found: Verify the method name and that required tools/resources are registered
  4. Timeout errors: Increase the timeout value for long-running tools
  5. Protocol version mismatch: Ensure you're using a supported protocol version

Known Issues

Initialize Method Error: 'coroutine' object has no attribute 'model_dump'

If you encounter this error when calling the initialize method via /rpc:

{"jsonrpc":"2.0","error":{"code":-32000,"message":"Internal error","data":"'coroutine' object has no attribute 'model_dump'"},"id":1}

This is a bug in the /rpc endpoint's initialize handler where an async function is not being properly awaited.

Workaround 1: Use the dedicated protocol endpoint

Instead of using /rpc, use the /protocol/initialize endpoint:

curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
           "protocol_version":"2025-03-26",
           "capabilities":{},
           "client_info":{"name":"demo","version":"0.0.1"}
         }' \
     http://localhost:4444/protocol/initialize

Workaround 2: Use Server-Sent Events (SSE)

Use the SSE-based approach which has proper async handling:

# Terminal 1: Establish SSE connection
curl -N -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \
     http://localhost:4444/sse

# Terminal 2: Send initialization message
curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
           "jsonrpc":"2.0",
           "id":1,
           "method":"initialize",
           "params":{
             "protocolVersion":"2025-03-26",
             "capabilities":{},
             "clientInfo":{"name":"demo","version":"0.0.1"}
           }
         }' \
     http://localhost:4444/message

The SSE approach allows you to maintain a persistent connection and receive responses in real-time, which is closer to how MCP is intended to work.

Further Reading

Metadata

Metadata

Labels

documentationImprovements or additions to documentationenhancementNew feature or requestgood first issueGood for newcomershelp wantedExtra attention is neededmarkdownDocumentation (mkdocs / markdown)triageIssues / Features awaiting triage

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions