-
Notifications
You must be signed in to change notification settings - Fork 0
Add three-layer test structure and embedded skill resource #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # Example MCP Server — Skill Guide | ||
|
|
||
| ## Tools | ||
|
|
||
| | Tool | Use when... | | ||
| |------|-------------| | ||
| | `list_items` | You need to browse or search items | | ||
| | `get_item` | You have an item ID and need full details | | ||
|
|
||
| ## Context Reuse | ||
|
|
||
| - Use the `id` from `list_items` results when calling `get_item` | ||
|
|
||
| ## Workflows | ||
|
|
||
| ### 1. Browse and Inspect | ||
| 1. `list_items` with a limit to get an overview | ||
| 2. For interesting items: `get_item` to get full details |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| """ | ||
| Shared fixtures and configuration for integration tests. | ||
|
|
||
| These tests require a valid EXAMPLE_API_KEY environment variable. | ||
| They make real API calls and should not be run in CI without proper setup. | ||
| """ | ||
|
|
||
| import os | ||
|
|
||
| import pytest | ||
| import pytest_asyncio | ||
|
|
||
| from mcp_example.api_client import ExampleClient | ||
|
|
||
|
|
||
| def pytest_configure(config): | ||
| """Check for required environment variables before running tests.""" | ||
| if not os.environ.get("EXAMPLE_API_KEY"): | ||
| pytest.exit( | ||
| "ERROR: EXAMPLE_API_KEY environment variable is required.\n" | ||
| "Set it before running integration tests:\n" | ||
| " export EXAMPLE_API_KEY=your_key_here\n" | ||
| " make test-integration" | ||
| ) | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def api_key() -> str: | ||
| """Get the API key from environment.""" | ||
| key = os.environ.get("EXAMPLE_API_KEY") | ||
| if not key: | ||
| pytest.skip("EXAMPLE_API_KEY not set") | ||
| return key | ||
|
|
||
|
|
||
| @pytest_asyncio.fixture | ||
| async def client(api_key: str) -> ExampleClient: | ||
| """Create a client for testing.""" | ||
| client = ExampleClient(api_key=api_key) | ||
| yield client | ||
| await client.close() | ||
|
|
||
|
|
||
| # TODO: Add well-known test data constants | ||
| # class TestData: | ||
| # """Well-known test data for integration tests.""" | ||
| # KNOWN_ID = "abc123" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| """ | ||
| Core tools integration tests. | ||
|
|
||
| Tests basic API functionality with real API calls. | ||
| Replace with your actual endpoints and assertions. | ||
| """ | ||
|
|
||
| # import pytest | ||
| # from mcp_example.api_client import ExampleAPIError, ExampleClient | ||
|
|
||
|
|
||
| # TODO: Add integration tests for each tool group. Example: | ||
| # | ||
| # class TestListItems: | ||
| # """Test list items endpoint.""" | ||
| # | ||
| # @pytest.mark.asyncio | ||
| # async def test_list_items(self, client: ExampleClient): | ||
| # """Test listing items.""" | ||
| # result = await client.list_items(limit=5) | ||
| # assert isinstance(result, list) | ||
| # print(f"Found {len(result)} items") | ||
| # | ||
| # | ||
| # For tier-gated endpoints, add a helper: | ||
| # | ||
| # async def has_premium_access(client: ExampleClient) -> bool: | ||
| # """Check if the plan supports premium endpoints.""" | ||
| # try: | ||
| # await client.premium_method() | ||
| # return True | ||
| # except ExampleAPIError as e: | ||
| # if e.status in (400, 401, 403): | ||
| # return False | ||
| # raise |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| """ | ||
| Smoke test: verify the LLM reads the skill resource and selects the correct tool. | ||
|
|
||
| Requires ANTHROPIC_API_KEY and EXAMPLE_API_KEY in environment. | ||
| """ | ||
|
|
||
| import os | ||
|
|
||
| import anthropic | ||
| import pytest | ||
| from fastmcp import Client | ||
|
|
||
| from mcp_example.server import mcp | ||
|
|
||
|
|
||
| def get_anthropic_client() -> anthropic.Anthropic: | ||
| token = os.environ.get("ANTHROPIC_API_KEY") | ||
| if not token: | ||
| pytest.skip("ANTHROPIC_API_KEY not set") | ||
| return anthropic.Anthropic(api_key=token) | ||
|
|
||
|
|
||
| async def get_server_context() -> dict: | ||
| """Extract instructions, skill content, and tool definitions from the MCP server.""" | ||
| async with Client(mcp) as client: | ||
| init = await client.initialize() | ||
| instructions = init.instructions | ||
|
|
||
| resources = await client.list_resources() | ||
| skill_text = "" | ||
| for r in resources: | ||
| if "skill://" in str(r.uri): | ||
| contents = await client.read_resource(str(r.uri)) | ||
| skill_text = contents[0].text if hasattr(contents[0], "text") else str(contents[0]) | ||
|
|
||
| tools_list = await client.list_tools() | ||
| tools = [] | ||
| for t in tools_list: | ||
| tool_def = { | ||
| "name": t.name, | ||
| "description": t.description or "", | ||
| "input_schema": t.inputSchema, | ||
| } | ||
| tools.append(tool_def) | ||
|
|
||
| return { | ||
| "instructions": instructions, | ||
| "skill": skill_text, | ||
| "tools": tools, | ||
| } | ||
|
|
||
|
|
||
| class TestSkillLLMInvocation: | ||
| """Test that an LLM reads the skill and makes correct tool choices. | ||
|
|
||
| TODO: Replace with tests specific to your server's tools and skill. | ||
|
|
||
| Each test should: | ||
| 1. Send a user prompt that maps to a specific tool per the SKILL.md | ||
| 2. Assert the LLM calls the expected tool (not a similar one) | ||
| """ | ||
|
|
||
| # @pytest.mark.asyncio | ||
| # async def test_query_selects_correct_tool(self): | ||
| # """When asked to X, the LLM should call tool_name.""" | ||
| # ctx = await get_server_context() | ||
| # client = get_anthropic_client() | ||
| # | ||
| # system = ( | ||
| # f"You are an assistant.\n\n" | ||
| # f"## Server Instructions\n{ctx['instructions']}\n\n" | ||
| # f"## Skill Resource\n{ctx['skill']}" | ||
| # ) | ||
| # | ||
| # response = client.messages.create( | ||
| # model="claude-haiku-4-5-20251001", | ||
| # max_tokens=1024, | ||
| # system=system, | ||
| # messages=[{"role": "user", "content": "Your test prompt here"}], | ||
| # tools=[{"type": "custom", **t} for t in ctx["tools"]], | ||
| # ) | ||
| # | ||
| # tool_calls = [b for b in response.content if b.type == "tool_use"] | ||
| # assert len(tool_calls) > 0, "LLM did not call any tool" | ||
| # assert tool_calls[0].name == "expected_tool_name" | ||
| pass | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| """Shared fixtures for unit tests.""" | ||
|
|
||
| from unittest.mock import AsyncMock | ||
|
|
||
| import pytest | ||
|
|
||
| from mcp_example.server import mcp | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def mcp_server(): | ||
| """Return the MCP server instance.""" | ||
| return mcp | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def mock_client(): | ||
| """Create a mock API client.""" | ||
| client = AsyncMock() | ||
| client.list_items = AsyncMock( | ||
| return_value=[ | ||
| {"id": "1", "name": "Item 1"}, | ||
| {"id": "2", "name": "Item 2"}, | ||
| ] | ||
| ) | ||
| client.get_item = AsyncMock( | ||
| return_value={ | ||
| "id": "1", | ||
| "name": "Item 1", | ||
| "description": "Test item", | ||
| } | ||
| ) | ||
| return client |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.