Skip to content

Instantly share code, notes, and snippets.

@nerdalert
Created May 4, 2026 05:19
Show Gist options
  • Select an option

  • Save nerdalert/1c5dc842a2ad5cb0e4397232406d8922 to your computer and use it in GitHub Desktop.

Select an option

Save nerdalert/1c5dc842a2ad5cb0e4397232406d8922 to your computer and use it in GitHub Desktop.

Validation of experimental e2e of agentic Praxis support

Full code for the demo here

./target/debug/praxis --config /tmp/mcp-gateway-two-backends.yaml &
cd <directory with the script at the bottom>
source .venv/bin/activate
MCP_SERVER_NAME=weather uvicorn dev_mcp:app --host 127.0.0.1 --port 8001 &
MCP_SERVER_NAME=calendar uvicorn dev_mcp:app --host 127.0.0.1 --port 8002 &


# Start Praxis gateway (mcp-gateway-two-backends.yaml config pasted below)
./target/debug/praxis --config /tmp/mcp-gateway-two-backends.yaml &

# =================================================================

# Initialize
curl -s http://127.0.0.1:18090/mcp \
-H 'content-type: application/json' -H 'accept: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1"}}}'

# Initialize Output

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-03-26",
    "capabilities": {
      "tools": {
        "listChanged": false
      }
    },
    "serverInfo": {
      "name": "praxis-mcp-gateway",
      "version": "0.1.0"
    }
  }
}

# =================================================================

# List tools (aggregated, prefixed)
curl -s http://127.0.0.1:18090/mcp \
-H 'content-type: application/json' -H 'accept: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' 

# List tools output
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "description": "Search",
        "name": "weather_search"
      },
      {
        "description": "Get weather",
        "name": "weather_get_weather"
      },
      {
        "description": "Search",
        "name": "cal_search"
      },
      {
        "description": "Create event",
        "name": "cal_create_event"
      }
    ]
  }
}

# =================================================================

# Call weather tool (routes to port 8001, prefix stripped)
curl -s http://127.0.0.1:18090/mcp \
-H 'content-type: application/json' -H 'accept: application/json' \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"weather_get_weather","arguments":{"city":"London"}}}'


# Call weather tool output

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\n  \"server\": \"weather\",\n  \"tool\": \"get_weather\",\n  \"city\": \"London\",\n  \"temp\": 72\n}"
      }
    ],
    "structuredContent": {
      "server": "weather",
      "tool": "get_weather",
      "city": "London",
      "temp": 72
    },
    "isError": false
  }
}

# =================================================================

# Call calendar tool (routes to port 8002, prefix stripped)
curl -s http://127.0.0.1:18090/mcp \
-H 'content-type: application/json' -H 'accept: application/json' \
-d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"cal_create_event","arguments":{"title":"standup"}}}'

# Call calendar tool output
{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\n  \"server\": \"calendar\",\n  \"tool\": \"create_event\",\n  \"title\": \"standup\",\n  \"when\": \"now\"\n}"
      }
    ],
    "structuredContent": {
      "server": "calendar",
      "tool": "create_event",
      "title": "standup",
      "when": "now"
    },
    "isError": false
  }
}

# =================================================================

# Ping
curl -s http://127.0.0.1:18090/mcp \
-H 'content-type: application/json' -H 'accept: application/json' \
-d '{"jsonrpc":"2.0","id":5,"method":"ping"}'

# Ping output
{
  "jsonrpc": "2.0",
  "id": 5,
  "result": {}
}

Praxis config used above

listeners:
  - name: mcp-gateway
    address: "0.0.0.0:18090"
    filter_chains: [main]
filter_chains:
  - name: main
    filters:
      - filter: mcp_gateway
        path: /mcp
        max_body_bytes: 65536
        servers:
          - name: weather
            cluster: weather-mcp
            path: /mcp
            tool_prefix: "weather_"
            tools:
              - name: search
                description: "Search"
              - name: get_weather
                description: "Get weather"
          - name: calendar
            cluster: calendar-mcp
            path: /mcp
            tool_prefix: "cal_"
            tools:
              - name: search
                description: "Search"
              - name: create_event
                description: "Create event"
      - filter: load_balancer
        clusters:
          - name: weather-mcp
            endpoints:
              - "127.0.0.1:8001"
          - name: calendar-mcp
            endpoints:
              - "127.0.0.1:8002"

Streamable HTTP MCP dev server used for the above validation

"""Quick Streamable HTTP MCP dev server for Praxis validation.

Location:
    ~/praxxis/agentic/dev_mcp.py

Install:
    cd ~/praxxis/agentic
    python -m venv .venv
    source .venv/bin/activate
    pip install "mcp[cli]" uvicorn starlette

Start one stateless JSON-response backend:
    MCP_SERVER_NAME=weather uvicorn dev_mcp:app --host 127.0.0.1 --port 8001

Start two stateless backends for mcp_gateway multi-backend tests:
    MCP_SERVER_NAME=weather uvicorn dev_mcp:app --host 127.0.0.1 --port 8001
    MCP_SERVER_NAME=calendar uvicorn dev_mcp:app --host 127.0.0.1 --port 8002

Start a stateful backend for PR 5 lazy-backend-session testing:
    MCP_STATEFUL=1 MCP_SERVER_NAME=weather uvicorn dev_mcp:app --host 127.0.0.1 --port 8001

Endpoint:
    http://127.0.0.1:<port>/mcp

Direct smoke test:
    curl -s http://127.0.0.1:8001/mcp \
      -H 'content-type: application/json' \
      -H 'accept: application/json, text/event-stream' \
      -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Praxis gateway test shape:
    - Configure weather backend as 127.0.0.1:8001, path /mcp, prefix weather_.
    - Configure calendar backend as 127.0.0.1:8002, path /mcp, prefix cal_.
    - tools/list should expose weather_search, weather_get_weather, cal_search,
      and cal_create_event.
    - tools/call weather_search should reach the weather process and the backend
      should see unprefixed params.name == "search".
    - tools/call cal_create_event should reach the calendar process and the
      backend should see unprefixed params.name == "create_event".
"""

from __future__ import annotations

import contextlib
import os
from typing import Any

from mcp.server.fastmcp import FastMCP
from starlette.applications import Starlette
from starlette.routing import Mount


SERVER_NAME = os.getenv("MCP_SERVER_NAME", "weather")
STATEFUL = os.getenv("MCP_STATEFUL", "0") == "1"

mcp = FastMCP(
    f"Praxis Dev MCP - {SERVER_NAME}",
    stateless_http=not STATEFUL,
    json_response=True,
)


@mcp.tool()
def search(query: str) -> dict[str, Any]:
    """Search test tool."""
    return {
        "server": SERVER_NAME,
        "tool": "search",
        "query": query,
    }


@mcp.tool()
def get_weather(city: str = "Raleigh") -> dict[str, Any]:
    """Weather test tool."""
    return {
        "server": SERVER_NAME,
        "tool": "get_weather",
        "city": city,
        "temp": 72,
    }


@mcp.tool()
def create_event(title: str, when: str = "now") -> dict[str, Any]:
    """Calendar test tool."""
    return {
        "server": SERVER_NAME,
        "tool": "create_event",
        "title": title,
        "when": when,
    }


@contextlib.asynccontextmanager
async def lifespan(app: Starlette):
    async with mcp.session_manager.run():
        yield


app = Starlette(
    routes=[Mount("/", app=mcp.streamable_http_app())],
    lifespan=lifespan,
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment