Skip to content

MCP Protocol Implementation

This document describes how the Natural Language Agent implements the Model Context Protocol (MCP).

Protocol Version

This server is dual-era: it implements both 2024-11-05 (a connection-scoped initialize handshake) and the current revision, 2026-07-28 (stateless, per-request negotiation), on both transports at the same time.

A request whose params._meta carries io.modelcontextprotocol/protocolVersion is served under the modern, 2026-07-28 model: see MCP Specification Compliance for the full negotiation rules, required Streamable HTTP headers, and what was deliberately not adopted from that revision.

Every other request, including every initialize handshake, is served under this server's original, legacy behavior, unchanged: the server negotiates the version during the handshake and never simply echoes the version a client requests. A client that asks for a revision the server does not implement receives 2024-11-05 in the initialize response, and it should then decide whether to continue or disconnect. A client that omits protocolVersion also receives 2024-11-05. An initialize request whose parameters are malformed, such as a protocolVersion that is not a string, fails with a -32602 Invalid params error on both transports.

Transport Modes

The server supports two transport modes:

1. stdio Mode (Default)

Used by: Claude Desktop and other MCP clients

Communication: JSON-RPC 2.0 over standard input/output

How it works:

  • Server reads JSON-RPC messages from stdin
  • Server writes JSON-RPC responses to stdout
  • Errors and diagnostics written to stderr

Starting in stdio mode:

./bin/pgedge-postgres-mcp

2. HTTP/HTTPS Mode

Used by: Web applications, direct API access, custom integrations

Communication: JSON-RPC 2.0 over HTTP/HTTPS with streaming support

Endpoints:

  • POST /mcp/v1 - JSON-RPC endpoint
  • GET /health - Health check endpoint

How it works:

  • Client sends HTTP POST request with JSON-RPC payload
  • Server processes request and returns JSON-RPC response
  • Supports HTTP/1.1 with keep-alive
  • Streaming responses for real-time data

Starting in HTTP mode:

./bin/pgedge-postgres-mcp -http -addr ":8080"

MCP Capabilities

The server advertises these capabilities during initialization:

{
  "capabilities": {
    "tools": {},
    "resources": {},
    "prompts": {}
  }
}

Tools

Five callable functions for database interaction and management:

  1. query_database - Execute SQL queries against PostgreSQL
  2. get_schema_info - Get detailed database schema information
  3. similarity_search - Advanced hybrid search combining vector similarity with BM25 and MMR
  4. generate_embedding - Generate vector embeddings from text using AI models
  5. read_resource - Read MCP resources by URI

Note: Database connection is configured at server startup via environment variables, config file, or command-line flags. There are no runtime connection management tools.

For detailed tool documentation, see Tools Documentation.

Resources

Read-only resources for system information:

  • pg://system_info - PostgreSQL version and system information

For detailed resource documentation, see Resources Documentation.

Prompts

Custom prompts for common database tasks (extensible).

JSON-RPC Messages

Request Format

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "method_name",
  "params": {
    "param1": "value1",
    "param2": "value2"
  }
}

Response Format

Success:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "data": "..."
  }
}

Error:

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32600,
    "message": "Invalid Request"
  }
}

MCP Methods

Initialize

Establish connection and negotiate capabilities.

Request:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "roots": {
        "listChanged": true
      }
    },
    "clientInfo": {
      "name": "my-client",
      "version": "1.0.0"
    }
  }
}

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "tools": {},
      "resources": {},
      "prompts": {}
    },
    "serverInfo": {
      "name": "pgedge-postgres-mcp",
      "version": "1.0.0-alpha2"
    }
  }
}

List Tools

Get available tools.

Request:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list",
  "params": {}
}

Response:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "query_database",
        "description": "Execute a natural language query...",
        "inputSchema": {
          "type": "object",
          "properties": {
            "query": {
              "type": "string",
              "description": "Natural language question..."
            }
          },
          "required": ["query"]
        }
      }
      // ... more tools
    ]
  }
}

Call Tool

Execute a tool.

Request:

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "query_database",
    "arguments": {
      "query": "Show me all active users"
    }
  }
}

Response:

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Natural Language Query: Show me all active users\n\nGenerated SQL:\nSELECT * FROM users WHERE status = 'active';\n\nResults:\n..."
      }
    ]
  }
}

List Resources

Get available resources.

Request:

{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "resources/list",
  "params": {}
}

Response:

{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "resources": [
      {
        "uri": "pg://system_info",
        "name": "postgresql_system_info",
        "description": "Returns PostgreSQL version...",
        "mimeType": "application/json"
      }
      // ... more resources
    ]
  }
}

Read Resource

Read a specific resource.

Request:

{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "resources/read",
  "params": {
    "uri": "pg://system_info"
  }
}

Response:

{
  "jsonrpc": "2.0",
  "id": 5,
  "result": {
    "contents": [
      {
        "uri": "pg://system_info",
        "mimeType": "application/json",
        "text": "{\"version\":\"PostgreSQL 16.0\",\"os\":\"Linux\",\"arch\":\"x86_64\"}"
      }
    ]
  }
}

Error Codes

Standard JSON-RPC error codes:

Code Message Meaning
-32700 Parse error Invalid JSON
-32601 Method not found Method doesn't exist
-32602 Invalid params Invalid method parameters, or (modern requests only) an unknown resource URI in resources/read
-32603 Internal error Server error

Codes specific to a resource lookup:

Code Message Meaning
-32002 Resource not found Unknown resource URI in resources/read, on a legacy (pre-2026-07-28) request

Codes specific to the modern (2026-07-28) protocol, defined by the spec's reserved range for protocol-level errors; see MCP Specification Compliance:

Code Message Meaning
-32020 HeaderMismatch A Streamable HTTP header does not match the request body, or a required one is missing
-32021 MissingRequiredClientCapability The request needs a client capability not declared in _meta.clientCapabilities (not currently reachable: this server requires no client capability for anything it implements)
-32022 UnsupportedProtocolVersion The requested _meta.protocolVersion is not one this server implements

Streaming (HTTP Mode)

In HTTP mode, the server supports streaming responses for tools that generate large outputs.

How it works:

  1. Client sends request to /mcp/v1
  2. Server processes request
  3. Server streams response chunks
  4. Client receives progressive updates

Example - Streaming Query Results:

The query_database tool can stream results as they're generated:

const response = await fetch('https://localhost:8080/mcp/v1', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_TOKEN',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    jsonrpc: '2.0',
    id: 1,
    method: 'tools/call',
    params: {
      name: 'query_database',
      arguments: { query: 'Show all users' }
    }
  })
});

// Stream response
const reader = response.body.getReader();
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  // Process chunk
  console.log(new TextDecoder().decode(value));
}

Authentication (HTTP Mode)

HTTP mode uses Bearer token authentication.

Request with authentication:

curl -X POST https://localhost:8080/mcp/v1 \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{...}'

Unauthenticated request returns 401:

{
  "error": "Unauthorized"
}

For token management, see Authentication Guide.

Protocol Extensions

Multi-Database Support

The query_database tool supports querying multiple databases:

Temporary connection:

{
  "method": "tools/call",
  "params": {
    "name": "query_database",
    "arguments": {
      "query": "Show users at postgres://otherhost/otherdb"
    }
  }
}

Change default connection:

{
  "method": "tools/call",
  "params": {
    "name": "query_database",
    "arguments": {
      "query": "set default database to postgres://newhost/newdb"
    }
  }
}

Read-Only Transaction Enforcement

All queries via query_database are executed in read-only transactions:

BEGIN;
SET TRANSACTION READ ONLY;
-- User's query here
COMMIT;

This prevents data modification while allowing full read access.

Implementation Details

Server Lifecycle

  1. Startup:

    • Parse configuration
    • Connect to PostgreSQL
    • Load database metadata (tables, columns, types)
    • Initialize tool and resource registries
    • Start transport (stdio or HTTP)
  2. Request Handling:

    • Receive JSON-RPC message
    • Validate request format
    • Route to appropriate handler
    • Execute tool/resource
    • Return JSON-RPC response
  3. Shutdown:

    • Close database connections
    • Clean up resources
    • Exit gracefully

Metadata Loading

On startup, the server loads:

  • All tables and views (excluding system schemas)
  • Column names and data types
  • Nullability constraints
  • Table and column comments from pg_description

This metadata is used for:

  • Schema information tools
  • Natural language to SQL conversion
  • Query validation

Error Handling

The server provides detailed error messages in development:

  • Database connection errors
  • SQL syntax errors
  • Permission errors
  • API key errors

In production, errors are logged but sanitized in responses.

Testing MCP Protocol

With MCP Inspector

npx @modelcontextprotocol/inspector /path/to/bin/pgedge-postgres-mcp

The MCP Inspector provides a web UI for testing:

  • Initialize connection
  • List tools and resources
  • Call tools interactively
  • View request/response JSON

With curl (HTTP mode)

# Start server
./bin/pgedge-postgres-mcp -http -no-auth

# Initialize
curl -X POST http://localhost:8080/mcp/v1 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2024-11-05",
      "capabilities": {},
      "clientInfo": {"name": "curl", "version": "1.0"}
    }
  }'

# List tools
curl -X POST http://localhost:8080/mcp/v1 \
  -H "Content-Type": "application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/list",
    "params": {}
  }'

Integration Tests

The project includes comprehensive integration tests for MCP protocol compliance:

go test ./test/... -v -run TestMCPCompliance

Tests verify:

  • Protocol version negotiation
  • Tool and resource registration
  • Request/response format
  • Error handling
  • Streaming support (HTTP mode)

For more testing information, see Testing Guide.