Skip to content
This repository was archived by the owner on May 4, 2026. It is now read-only.
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 45 additions & 8 deletions tests/test_mcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os
import subprocess
import sys
import threading
import time

import pytest
Expand Down Expand Up @@ -42,20 +43,56 @@ def test_stdio_initialize_and_tools_list(monkeypatch: pytest.MonkeyPatch) -> Non
}
initialized = {"jsonrpc": "2.0", "method": "notifications/initialized"}
tools_list = {"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}}
input_text = "\n".join(json.dumps(m) for m in (init, initialized, tools_list)) + "\n"

proc = subprocess.run(
proc = subprocess.Popen(
[sys.executable, "-m", "tiny_ntfy_mcp"],
input=input_text,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
capture_output=True,
env=os.environ.copy(),
timeout=5,
check=False,
)
assert proc.returncode == 0, proc.stderr
responses: list[dict] = []
# Signalled by the reader once the tools/list response (id=2) is observed,
# so the main thread waits on a real event rather than racing a join timeout.
saw_tools_list = threading.Event()

# Stdin is intentionally kept open until the tools/list response arrives
# so that MCP's transport-close handler (mcp>=1.27.0) does not cancel the
# in-flight request before it can respond.
def _collect() -> None:
for raw in proc.stdout:
raw = raw.strip()
if not raw:
continue
msg = json.loads(raw)
responses.append(msg)
if msg.get("id") == 2:
saw_tools_list.set()
return

t = threading.Thread(target=_collect, daemon=True)
t.start()
try:
for msg in (init, initialized, tools_list):
proc.stdin.write(json.dumps(msg) + "\n")
proc.stdin.flush()

responses = [json.loads(line) for line in proc.stdout.splitlines()]
got_response = saw_tools_list.wait(timeout=5)
finally:
proc.stdin.close()
try:
proc.wait(timeout=5)
except subprocess.TimeoutExpired:
proc.kill()
proc.wait()
# Reader exits naturally once stdout closes; join to ensure no further
# writes to `responses` race the assertions below.
t.join(timeout=5)

stderr_output = proc.stderr.read()
assert got_response, f"timed out waiting for tools/list response. Got ids: {[r.get('id') for r in responses]}\nstderr: {stderr_output}"
assert proc.returncode == 0, stderr_output
init_resp = next(r for r in responses if r.get("id") == 1)
tools_resp = next(r for r in responses if r.get("id") == 2)

Expand Down