feat(fable-mcp): implement FableClient, all tool modules, and server.py
- client.py: async httpx wrapper, FableAPIError, stream_get() SSE generator, singleton init_client()/get_client() and _reset_client() for tests - tools/: notes, tasks, projects, milestones, search, chat — thin async functions that accept FableClient and call Fable REST endpoints - server.py: FastMCP entry point with 20 tools registered via @mcp.tool(), each opening a fresh FableClient context per call; validates env vars at startup - tests: 34 tests covering client HTTP behaviour, error handling, singleton, SSE streaming, and all tool modules Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
"""Tests for notes MCP tools."""
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(mock_client):
|
||||
return mock_client
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_notes_calls_get(client):
|
||||
from fable_mcp.tools.notes import list_notes
|
||||
client.get = AsyncMock(return_value={"notes": [], "total": 0})
|
||||
result = await list_notes(client, limit=10)
|
||||
client.get.assert_called_once()
|
||||
call_args = client.get.call_args
|
||||
assert "/api/notes" in call_args[0][0]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_note_calls_correct_endpoint(client):
|
||||
from fable_mcp.tools.notes import get_note
|
||||
client.get = AsyncMock(return_value={"id": 42, "title": "Hello"})
|
||||
result = await get_note(client, note_id=42)
|
||||
client.get.assert_called_once_with("/api/notes/42")
|
||||
assert result["id"] == 42
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_note_posts_payload(client):
|
||||
from fable_mcp.tools.notes import create_note
|
||||
client.post = AsyncMock(return_value={"id": 1, "title": "My Note"})
|
||||
result = await create_note(client, title="My Note", body="Content")
|
||||
client.post.assert_called_once()
|
||||
call_kwargs = client.post.call_args[1]
|
||||
assert call_kwargs["json"]["title"] == "My Note"
|
||||
assert call_kwargs["json"]["body"] == "Content"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_note_patches_payload(client):
|
||||
from fable_mcp.tools.notes import update_note
|
||||
client.patch = AsyncMock(return_value={"id": 1, "title": "Updated"})
|
||||
result = await update_note(client, note_id=1, title="Updated")
|
||||
client.patch.assert_called_once()
|
||||
path = client.patch.call_args[0][0]
|
||||
assert "/api/notes/1" in path
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_note_calls_delete(client):
|
||||
from fable_mcp.tools.notes import delete_note
|
||||
client.delete = AsyncMock(return_value=None)
|
||||
await delete_note(client, note_id=5)
|
||||
client.delete.assert_called_once_with("/api/notes/5")
|
||||
Reference in New Issue
Block a user