name: CI
on:
push:
pull_request:
jobs:
smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install -U pip
pip install -e .
pip install -U anyio mcp
- name: Smoke MCP server (get_info)
env:
PORT_HUNTER_TOKEN: CI_TOKEN
PORT_HUNTER_ALLOWED_DIR: ${{ github.workspace }}
run: |
python - << 'PY'
import os, sys, asyncio, json
from mcp.client.session import ClientSession
from mcp.client.stdio import stdio_client
try:
from mcp.client.stdio import StdioServerParameters
except Exception:
StdioServerParameters = None # compat
def server_params():
env = {
"PORT_HUNTER_TOKEN": os.environ.get("PORT_HUNTER_TOKEN", "CI_TOKEN"),
"PORT_HUNTER_ALLOWED_DIR": os.environ.get("PORT_HUNTER_ALLOWED_DIR", os.getcwd()),
}
py = sys.executable
if StdioServerParameters is not None:
return StdioServerParameters(command=py, args=["-m", "porthunter.server"], env=env)
else:
# compat para versiones antiguas del SDK (si stdio_client lo soporta)
return {"command": py, "args": ["-m", "porthunter.server"], "env": env}
def normalize(res):
# 1) API nueva: objeto con .content => TextContent(text='{...}')
content = getattr(res, "content", None)
if isinstance(content, list):
for item in content:
text = item.get("text") if isinstance(item, dict) else getattr(item, "text", None)
if isinstance(text, str):
try:
return json.loads(text)
except Exception:
pass
# 2) Formato viejo: dict con "result" (str JSON o dict)
if isinstance(res, dict) and "result" in res:
inner = res["result"]
if isinstance(inner, dict):
return inner
if isinstance(inner, str):
try:
return json.loads(inner)
except Exception:
return {"raw": inner}
# 3) Fallback
return {"raw": repr(res)}
async def main():
params = server_params()
async with stdio_client(params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
res = await session.call_tool("get_info", {"auth_token": os.environ["PORT_HUNTER_TOKEN"]})
data = normalize(res)
assert isinstance(data, dict)
assert data.get("ok") is True, f"Unexpected response: {data}"
print(json.dumps(data, indent=2))
asyncio.run(main())
PY
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install test dependencies
run: |
python -m pip install -U pip
pip install -e .
pip install -U pytest pytest-asyncio anyio mcp
- name: Run pytest
env:
PORT_HUNTER_TOKEN: CI_TOKEN
PORT_HUNTER_ALLOWED_DIR: ${{ github.workspace }}
run: |
python -m pytest -q