mcp-server-llmling
by phil65
Verified
- mcp-server-llmling
- tests
"""Test server lifecycle using different approaches."""
from __future__ import annotations
import asyncio
import contextlib
import json
from typing import TYPE_CHECKING, Any
from mcp.types import JSONRPCMessage, JSONRPCNotification, JSONRPCRequest
import pytest
from mcp_server_llmling import constants
if TYPE_CHECKING:
from llmling.config.runtime import RuntimeConfig
from mcp_server_llmling import LLMLingServer
from mcp_server_llmling.mcp_inproc_session import MCPInProcSession
INFO = {"name": "test-client", "version": "1.0"}
PARAMS = {"protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": INFO}
@pytest.mark.asyncio
async def test_server_lifecycle_handshake_client(client: MCPInProcSession) -> None:
"""Test server lifecycle using MCPInProcSession."""
try:
await client.start()
await asyncio.sleep(0.5)
# Initialize connection
init_response = await client.send_request("initialize", PARAMS)
assert isinstance(init_response, dict)
assert "serverInfo" in init_response
server_info = init_response["serverInfo"]
assert server_info["name"] == constants.SERVER_NAME
assert "version" in server_info
assert "capabilities" in init_response
# Send initialized notification
await client.send_notification("notifications/initialized", {})
# Test functionality
tools = await client.list_tools()
assert isinstance(tools, list)
resources = await client.list_resources()
assert isinstance(resources, list)
prompts = await client.list_prompts()
assert isinstance(prompts, list)
finally:
await client.close()
@pytest.mark.asyncio
async def test_server_lifecycle_test_session(
running_server: tuple[LLMLingServer, tuple[Any, Any]],
) -> None:
"""Test server lifecycle using test session."""
req = JSONRPCRequest(jsonrpc="2.0", id=1, method="initialize", params=PARAMS)
server, (client_read, client_write) = running_server
init_request = JSONRPCMessage(req)
await client_write.send(init_request)
response = await client_read.receive()
assert "result" in response.root.model_dump()
result = response.root.result
assert "serverInfo" in result
assert result["serverInfo"]["name"] == constants.SERVER_NAME
# Send initialized notification
noti = JSONRPCNotification(jsonrpc="2.0", method="notifications/initialized")
notification = JSONRPCMessage(noti)
await client_write.send(notification)
req = JSONRPCRequest(jsonrpc="2.0", id=2, method="tools/list")
# Test functionality
tools_request = JSONRPCMessage(req)
await client_write.send(tools_request)
tools_response = await client_read.receive()
assert "tools" in tools_response.root.result
@pytest.mark.asyncio
async def test_server_lifecycle_direct(
runtime_config: RuntimeConfig,
server: LLMLingServer,
) -> None:
"""Test server lifecycle using direct method calls."""
try:
# Start runtime components
await runtime_config.startup()
# Test direct access
tools = runtime_config.get_tools()
assert isinstance(tools, list)
assert len(tools) > 0 # Should have our test tools
resources = runtime_config.list_resource_names()
assert isinstance(resources, list)
prompts = runtime_config.list_prompt_names()
assert isinstance(prompts, list)
finally:
await runtime_config.shutdown()
await server.shutdown()
@pytest.mark.asyncio
async def test_server_lifecycle_subprocess() -> None:
"""Test server lifecycle using raw subprocess."""
process = await asyncio.create_subprocess_exec(
*constants.SERVER_CMD,
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
# Start stderr reader task
async def read_stderr() -> None:
assert process.stderr
while True:
line = await process.stderr.readline()
if not line:
break
print(f"Server stderr: {line.decode().strip()}")
stderr_task = asyncio.create_task(read_stderr())
try:
assert process.stdin
assert process.stdout
await asyncio.sleep(0.5) # Give server time to start
# Send initialize request
request = {"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": PARAMS}
process.stdin.write(json.dumps(request).encode() + b"\n")
await process.stdin.drain()
# Read until we get a valid JSON response
while True:
response = await process.stdout.readline()
if not response:
msg = "No response from server"
raise RuntimeError(msg)
try:
result = json.loads(response.decode())
assert "result" in result
assert "serverInfo" in result["result"]
assert result["result"]["serverInfo"]["name"] == constants.SERVER_NAME
break # Valid response found
except json.JSONDecodeError:
continue # Skip non-JSON lines
finally:
# Cleanup
stderr_task.cancel()
with contextlib.suppress(asyncio.CancelledError):
await stderr_task
process.terminate()
try:
await asyncio.wait_for(process.wait(), timeout=1.0)
except TimeoutError:
process.kill()
if __name__ == "__main__":
pytest.main([__file__, "-v"])