Files
MCP-Project-bus/tests/test_mcp_http.py
T
2026-07-30 02:14:43 +02:00

121 lines
4.1 KiB
Python

from __future__ import annotations
import json
import tempfile
import threading
import unittest
import urllib.error
import urllib.request
from pathlib import Path
from project_bus.auth import StaticTokenAuthProvider, TokenIdentity
from project_bus.db import Database
from project_bus.mcp import MCPApplication
from project_bus.server import ProjectBusHTTPServer
from project_bus.service import ProjectBusService
class MCPHTTPTestCase(unittest.TestCase):
def setUp(self) -> None:
self.temporary = tempfile.TemporaryDirectory()
service = ProjectBusService(Database(Path(self.temporary.name) / "bus.db"))
service.initialize()
auth = StaticTokenAuthProvider(
{"bootstrap-secret-at-least-24": TokenIdentity("system", "system", "System")}
)
self.server = ProjectBusHTTPServer(
("127.0.0.1", 0), MCPApplication(service), auth, {"https://chat.example"}
)
self.thread = threading.Thread(target=self.server.serve_forever, daemon=True)
self.thread.start()
self.url = f"http://127.0.0.1:{self.server.server_port}/mcp"
def tearDown(self) -> None:
self.server.shutdown()
self.server.server_close()
self.thread.join()
self.temporary.cleanup()
def request(
self,
payload: dict,
*,
token: str | None = None,
origin: str | None = None,
) -> tuple[int, dict]:
headers = {"Content-Type": "application/json"}
if token:
headers["Authorization"] = f"Bearer {token}"
if origin:
headers["Origin"] = origin
request = urllib.request.Request(
self.url,
data=json.dumps(payload).encode(),
headers=headers,
method="POST",
)
try:
response = urllib.request.urlopen(request, timeout=2)
return response.status, json.loads(response.read())
except urllib.error.HTTPError as error:
return error.code, json.loads(error.read())
def test_initialize_and_list_tools(self) -> None:
status, initialized = self.request(
{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {}}
)
self.assertEqual(status, 200)
self.assertEqual(initialized["result"]["protocolVersion"], "2025-03-26")
status, listed = self.request(
{"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}}
)
self.assertEqual(status, 200)
names = {tool["name"] for tool in listed["result"]["tools"]}
self.assertIn("sync_since", names)
self.assertIn("close_gate", names)
def test_authenticated_tool_call(self) -> None:
status, response = self.request(
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "create_project",
"arguments": {
"project_id": "demo",
"name": "Demo",
"idempotency_key": "create-demo",
},
},
},
token="bootstrap-secret-at-least-24",
)
self.assertEqual(status, 200)
self.assertFalse(response["result"]["isError"])
self.assertEqual(response["result"]["structuredContent"]["project_id"], "demo")
def test_tool_call_requires_authentication(self) -> None:
status, response = self.request(
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {"name": "sync_since", "arguments": {"project_id": "demo"}},
}
)
self.assertEqual(status, 401)
self.assertEqual(response["error"], "unauthorized")
def test_origin_allowlist(self) -> None:
status, response = self.request(
{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {}},
origin="https://evil.example",
)
self.assertEqual(status, 403)
self.assertEqual(response["error"], "origin_not_allowed")
if __name__ == "__main__":
unittest.main()