mcp-testkit 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. mcp_testkit-0.1.0/.github/workflows/ci.yml +111 -0
  2. mcp_testkit-0.1.0/.gitignore +6 -0
  3. mcp_testkit-0.1.0/AGENTS.md +148 -0
  4. mcp_testkit-0.1.0/LICENSE +21 -0
  5. mcp_testkit-0.1.0/PKG-INFO +259 -0
  6. mcp_testkit-0.1.0/README.md +237 -0
  7. mcp_testkit-0.1.0/docs/superpowers/plans/2026-04-05-mcp-test-server.md +1730 -0
  8. mcp_testkit-0.1.0/docs/superpowers/specs/2026-04-05-mcp-test-server-design.md +170 -0
  9. mcp_testkit-0.1.0/mcp_test_server/__init__.py +3 -0
  10. mcp_testkit-0.1.0/mcp_test_server/api.py +659 -0
  11. mcp_testkit-0.1.0/mcp_test_server/server.py +83 -0
  12. mcp_testkit-0.1.0/mcp_test_server/tools/__init__.py +29 -0
  13. mcp_testkit-0.1.0/mcp_test_server/tools/collection_tools.py +144 -0
  14. mcp_testkit-0.1.0/mcp_test_server/tools/conversion_tools.py +75 -0
  15. mcp_testkit-0.1.0/mcp_test_server/tools/datetime_tools.py +101 -0
  16. mcp_testkit-0.1.0/mcp_test_server/tools/echo_tools.py +95 -0
  17. mcp_testkit-0.1.0/mcp_test_server/tools/encoding_tools.py +63 -0
  18. mcp_testkit-0.1.0/mcp_test_server/tools/math_tools.py +61 -0
  19. mcp_testkit-0.1.0/mcp_test_server/tools/string_tools.py +45 -0
  20. mcp_testkit-0.1.0/mcp_test_server/tools/validation_tools.py +80 -0
  21. mcp_testkit-0.1.0/pyproject.toml +45 -0
  22. mcp_testkit-0.1.0/tests/__init__.py +0 -0
  23. mcp_testkit-0.1.0/tests/test_api.py +244 -0
  24. mcp_testkit-0.1.0/tests/test_collection_tools.py +365 -0
  25. mcp_testkit-0.1.0/tests/test_conversion_tools.py +255 -0
  26. mcp_testkit-0.1.0/tests/test_datetime_tools.py +301 -0
  27. mcp_testkit-0.1.0/tests/test_echo_tools.py +313 -0
  28. mcp_testkit-0.1.0/tests/test_encoding_tools.py +228 -0
  29. mcp_testkit-0.1.0/tests/test_integration.py +86 -0
  30. mcp_testkit-0.1.0/tests/test_math_tools.py +233 -0
  31. mcp_testkit-0.1.0/tests/test_string_tools.py +247 -0
  32. mcp_testkit-0.1.0/tests/test_validation_tools.py +283 -0
@@ -0,0 +1,111 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.11", "3.12", "3.13"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Python ${{ matrix.python-version }}
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install package
25
+ run: pip install -e ".[dev]" 2>/dev/null || pip install -e . && pip install pytest
26
+
27
+ - name: Run tests
28
+ run: python -m pytest tests/ -v --tb=short
29
+
30
+ lint:
31
+ runs-on: ubuntu-latest
32
+ steps:
33
+ - uses: actions/checkout@v4
34
+
35
+ - name: Set up Python
36
+ uses: actions/setup-python@v5
37
+ with:
38
+ python-version: "3.12"
39
+
40
+ - name: Install
41
+ run: pip install -e . ruff
42
+
43
+ - name: Check formatting
44
+ run: ruff format --check .
45
+
46
+ - name: Check linting
47
+ run: ruff check .
48
+
49
+ integration:
50
+ runs-on: ubuntu-latest
51
+ needs: test
52
+ steps:
53
+ - uses: actions/checkout@v4
54
+
55
+ - name: Set up Python
56
+ uses: actions/setup-python@v5
57
+ with:
58
+ python-version: "3.12"
59
+
60
+ - name: Install package
61
+ run: pip install -e . pytest
62
+
63
+ - name: Verify CLI entry point
64
+ run: mcp-test-server --help
65
+
66
+ - name: Verify SSE server starts and responds
67
+ run: |
68
+ mcp-test-server --transport sse --port 3001 &
69
+ sleep 3
70
+ curl -sf http://127.0.0.1:3001/api-docs | python -c "import sys,json; d=json.load(sys.stdin); assert d['openapi']=='3.0.3'; assert len(d['paths'])==65"
71
+ curl -sf "http://127.0.0.1:3001/api/math/add?a=2&b=3" | python -c "import sys,json; d=json.load(sys.stdin); assert d['result']==5.0"
72
+ curl -sf -X POST http://127.0.0.1:3001/api/string/reverse -H 'Content-Type: application/json' -d '{"text":"hello"}' | python -c "import sys,json; d=json.load(sys.stdin); assert d['result']=='olleh'"
73
+ kill %1
74
+
75
+ - name: Verify auth mode
76
+ run: |
77
+ mcp-test-server --transport sse --port 3002 --auth testkey &
78
+ sleep 3
79
+ STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:3002/api/math/add?a=1\&b=2)
80
+ [ "$STATUS" = "401" ] || (echo "Expected 401, got $STATUS" && exit 1)
81
+ curl -sf -H "Authorization: Bearer testkey" "http://127.0.0.1:3002/api/math/add?a=1&b=2" | python -c "import sys,json; d=json.load(sys.stdin); assert d['result']==3.0"
82
+ kill %1
83
+
84
+ build:
85
+ runs-on: ubuntu-latest
86
+ needs: [test, lint]
87
+ steps:
88
+ - uses: actions/checkout@v4
89
+
90
+ - name: Set up Python
91
+ uses: actions/setup-python@v5
92
+ with:
93
+ python-version: "3.12"
94
+
95
+ - name: Install build tools
96
+ run: pip install build
97
+
98
+ - name: Build sdist and wheel
99
+ run: python -m build
100
+
101
+ - name: Verify wheel contents
102
+ run: |
103
+ pip install dist/*.whl
104
+ mcp-test-server --help
105
+ python -c "from mcp_test_server import __version__; print(f'Version: {__version__}')"
106
+
107
+ - name: Upload artifacts
108
+ uses: actions/upload-artifact@v4
109
+ with:
110
+ name: dist
111
+ path: dist/
@@ -0,0 +1,6 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .venv/
@@ -0,0 +1,148 @@
1
+ # Agent Contributing Guide
2
+
3
+ Instructions for AI agents contributing to this project.
4
+
5
+ ## Project Overview
6
+
7
+ This is an MCP test server with 65 deterministic tools across 8 groups, exposed via MCP (stdio/SSE) and REST API. Every tool returns identical output for identical input — no randomness, no `datetime.now()`, no external state.
8
+
9
+ ## Architecture
10
+
11
+ ```
12
+ mcp_test_server/
13
+ server.py → Entry point. Creates FastMCP instance, registers tools, starts transport.
14
+ api.py → REST API. Data-driven endpoint registry generates Starlette routes + OpenAPI spec.
15
+ tools/*.py → Tool modules. Each exports register(mcp) that decorates tools with @mcp.tool().
16
+ tests/*.py → Unit + integration tests using unittest + FastMCP's call_tool().
17
+ ```
18
+
19
+ **Key pattern:** Tools are registered as closures inside `register(mcp)` via `@mcp.tool()` decorators. Helper functions live at module level.
20
+
21
+ ## How to Add a New Tool
22
+
23
+ ### 1. Choose the right module
24
+
25
+ | Group | File | When to use |
26
+ |-------|------|-------------|
27
+ | math | `mcp_test_server/tools/math_tools.py` | Numeric computation |
28
+ | string | `mcp_test_server/tools/string_tools.py` | String manipulation |
29
+ | collection | `mcp_test_server/tools/collection_tools.py` | Array/dict operations |
30
+ | encoding | `mcp_test_server/tools/encoding_tools.py` | Encode/decode/hash |
31
+ | datetime | `mcp_test_server/tools/datetime_tools.py` | Date computations (no current time!) |
32
+ | validation | `mcp_test_server/tools/validation_tools.py` | Input checking (returns `{valid, reason}`) |
33
+ | conversion | `mcp_test_server/tools/conversion_tools.py` | Unit/format conversions |
34
+ | echo | `mcp_test_server/tools/echo_tools.py` | Protocol testing, fixtures, misc |
35
+
36
+ If a tool doesn't fit any group, add it to `mcp_test_server/tools/echo_tools.py` (the catch-all for test fixtures).
37
+
38
+ ### 2. Implement the tool
39
+
40
+ Add it inside the `register(mcp)` function in the chosen module:
41
+
42
+ ```python
43
+ @mcp.tool()
44
+ def my_new_tool(param: str, count: int) -> str:
45
+ """Clear one-line description of what the tool does."""
46
+ result = param * count
47
+ return json.dumps({"result": result})
48
+ ```
49
+
50
+ Rules:
51
+ - **Return JSON strings** via `json.dumps({"result": ...})` for success
52
+ - **Return error objects** via `json.dumps({"error": "message"})` for expected errors
53
+ - **Raise `ToolError`** only for tools that intentionally test error handling
54
+ - **Be deterministic** — no randomness, no current time, no network calls, no file I/O
55
+ - **Use stdlib only** — no imports beyond Python stdlib + `mcp` + `requests`
56
+ - **Type-annotate all parameters** — FastMCP uses these to generate JSON schemas
57
+ - Validation tools return `json.dumps({"valid": bool, "reason": str})`
58
+
59
+ ### 3. Add the REST API endpoint
60
+
61
+ In `mcp_test_server/api.py`, add an entry to the `ENDPOINTS` list:
62
+
63
+ ```python
64
+ # For GET (simple scalar params):
65
+ ("/api/group/my-tool", "GET", "my_new_tool",
66
+ "Description for OpenAPI",
67
+ [("param", "string", True, "Param description"),
68
+ ("count", "integer", True, "Count description")]),
69
+
70
+ # For POST (complex or text-heavy params):
71
+ ("/api/group/my-tool", "POST", "my_new_tool",
72
+ "Description for OpenAPI",
73
+ [("param", "string", True, "Param description"),
74
+ ("count", "integer", True, "Count description")]),
75
+ ```
76
+
77
+ The tuple format is: `(path, method, tool_name, summary, params)` where params is `[(name, json_schema_type, required, description), ...]`.
78
+
79
+ Use GET for simple scalar inputs. Use POST for text bodies, arrays, objects, or anything complex.
80
+
81
+ The route handler and OpenAPI spec are generated automatically from this entry.
82
+
83
+ ### 4. Write tests
84
+
85
+ Add tests in the corresponding `tests/test_<group>_tools.py`:
86
+
87
+ ```python
88
+ def test_my_new_tool(self):
89
+ result = _call(self.mcp, "my_new_tool", {"param": "abc", "count": 3})
90
+ self.assertEqual(result, {"result": "abcabcabc"})
91
+ ```
92
+
93
+ Also add an API test in `tests/test_api.py`:
94
+
95
+ ```python
96
+ def test_my_new_tool(self):
97
+ r = self.client.post("/api/group/my-tool", json={"param": "abc", "count": 3})
98
+ self.assertEqual(r.json(), {"result": "abcabcabc"})
99
+ ```
100
+
101
+ ### 5. Update integration test
102
+
103
+ In `tests/test_integration.py`, update the expected tool count in `test_all_65_tools_registered` and adjust group counts if applicable.
104
+
105
+ ### 6. Verify
106
+
107
+ ```bash
108
+ python3 -m pytest tests/ -v
109
+ ```
110
+
111
+ All tests must pass. Current count: 389 tests.
112
+
113
+ ## How to Add a New Tool Group
114
+
115
+ 1. Create `mcp_test_server/tools/new_group_tools.py` with a `register(mcp)` function
116
+ 2. Import it in `mcp_test_server/tools/__init__.py` and add to `ALL_GROUPS`
117
+ 3. Add REST endpoints in `mcp_test_server/api.py` `ENDPOINTS` list
118
+ 4. Create `tests/test_new_group_tools.py`
119
+ 5. Update `tests/test_integration.py` tool counts and prefix checks
120
+
121
+ ## Conventions
122
+
123
+ - **Tool names**: `group_action` (e.g., `math_add`, `string_reverse`)
124
+ - **API paths**: `/api/group/action-name` with kebab-case (e.g., `/api/math/add`, `/api/string/char-count`)
125
+ - **Responses**: Always JSON. Success: `{"result": ...}`. Error: `{"error": "message"}`. Validation: `{"valid": bool, "reason": str}`.
126
+ - **No external state**: Tools must be pure functions of their inputs
127
+ - **Test helpers**: Use `_make_server()` and `_call(mcp, name, args)` pattern (see existing tests)
128
+
129
+ ## Running the Server
130
+
131
+ ```bash
132
+ # stdio (for MCP client testing)
133
+ mcp-test-server
134
+
135
+ # SSE + REST API
136
+ mcp-test-server --transport sse
137
+
138
+ # With auth
139
+ mcp-test-server --transport sse --auth <key>
140
+ ```
141
+
142
+ Default SSE port is 3001. Override with `--port`.
143
+
144
+ ## Dependencies
145
+
146
+ Runtime: `mcp[cli]`, `requests`, Python stdlib.
147
+
148
+ The server must not add dependencies beyond these. Use stdlib for all tool implementations.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AgentSpan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,259 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcp-testkit
3
+ Version: 0.1.0
4
+ Summary: MCP test server with 65 deterministic tools for protocol testing
5
+ Project-URL: Repository, https://github.com/agentspan-dev/mcp-test-server
6
+ Author: AgentSpan
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: mcp,model-context-protocol,testing,tools
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Testing
18
+ Requires-Python: >=3.11
19
+ Requires-Dist: mcp[cli]>=1.0.0
20
+ Requires-Dist: requests
21
+ Description-Content-Type: text/markdown
22
+
23
+ # MCP Test Server
24
+
25
+ A Python MCP server with **65 deterministic tools** across 8 groups, supporting **stdio**, **SSE**, and **REST API** transports. Built for consistent, repeatable MCP protocol testing.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ pip install mcp-testkit
31
+ ```
32
+
33
+ Or from source:
34
+
35
+ ```bash
36
+ git clone https://github.com/agentspan-dev/mcp-test-server.git
37
+ cd mcp-test-server
38
+ pip install -e .
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ ```bash
44
+ # stdio (default)
45
+ mcp-test-server
46
+
47
+ # SSE + REST API on port 3001
48
+ mcp-test-server --transport sse
49
+
50
+ # With bearer token authentication
51
+ mcp-test-server --transport sse --auth super_secret_key
52
+
53
+ # Custom host/port
54
+ mcp-test-server --transport sse --host 0.0.0.0 --port 8080
55
+ ```
56
+
57
+ ## Transports
58
+
59
+ | Transport | Command | Endpoints |
60
+ |-----------|---------|-----------|
61
+ | **stdio** | `python3 server.py` | MCP JSON-RPC over stdin/stdout |
62
+ | **SSE** | `python3 server.py --transport sse` | MCP at `/sse` + REST at `/api/*` + OpenAPI at `/api-docs` |
63
+
64
+ ## Authentication
65
+
66
+ Pass `--auth <key>` to require a Bearer token on all requests (both MCP SSE and REST):
67
+
68
+ ```bash
69
+ python3 server.py --transport sse --auth my_secret
70
+
71
+ # Clients must include:
72
+ # Authorization: Bearer my_secret
73
+ ```
74
+
75
+ ## Tools (65 total)
76
+
77
+ All tools are **deterministic** — same input always produces same output. No randomness, no current time, no external state.
78
+
79
+ ### Math (8 tools)
80
+
81
+ | Tool | Params | Description |
82
+ |------|--------|-------------|
83
+ | `math_add` | `a`, `b` | Add two numbers |
84
+ | `math_subtract` | `a`, `b` | Subtract b from a |
85
+ | `math_multiply` | `a`, `b` | Multiply two numbers |
86
+ | `math_divide` | `a`, `b` | Divide (errors on zero) |
87
+ | `math_modulo` | `a`, `b` | Remainder (errors on zero) |
88
+ | `math_power` | `base`, `exponent` | Exponentiation |
89
+ | `math_factorial` | `n` | n! (errors on negative) |
90
+ | `math_fibonacci` | `n` | Nth Fibonacci number (0-indexed) |
91
+
92
+ ### String (8 tools)
93
+
94
+ | Tool | Params | Description |
95
+ |------|--------|-------------|
96
+ | `string_reverse` | `text` | Reverse a string |
97
+ | `string_uppercase` | `text` | Convert to uppercase |
98
+ | `string_lowercase` | `text` | Convert to lowercase |
99
+ | `string_length` | `text` | Character count |
100
+ | `string_char_count` | `text`, `char` | Count occurrences |
101
+ | `string_replace` | `text`, `old`, `new` | Replace all occurrences |
102
+ | `string_split` | `text`, `delimiter` | Split by delimiter |
103
+ | `string_join` | `items`, `delimiter` | Join list with delimiter |
104
+
105
+ ### Collection (8 tools)
106
+
107
+ | Tool | Params | Description |
108
+ |------|--------|-------------|
109
+ | `collection_sort` | `items`, `reverse?` | Sort a list |
110
+ | `collection_flatten` | `items` | Recursively flatten nested lists |
111
+ | `collection_merge` | `dict_a`, `dict_b` | Merge dicts (b wins on conflict) |
112
+ | `collection_filter_gt` | `items`, `threshold` | Filter numbers > threshold |
113
+ | `collection_unique` | `items` | Remove duplicates (order preserved) |
114
+ | `collection_group_by` | `items`, `key` | Group objects by key |
115
+ | `collection_zip` | `list_a`, `list_b` | Zip into list of pairs |
116
+ | `collection_chunk` | `items`, `size` | Split into chunks |
117
+
118
+ ### Encoding (8 tools)
119
+
120
+ | Tool | Params | Description |
121
+ |------|--------|-------------|
122
+ | `encoding_base64_encode` | `text` | Base64 encode |
123
+ | `encoding_base64_decode` | `data` | Base64 decode |
124
+ | `encoding_url_encode` | `text` | URL-encode |
125
+ | `encoding_url_decode` | `text` | URL-decode |
126
+ | `encoding_hex_encode` | `text` | Hex encode |
127
+ | `encoding_hex_decode` | `data` | Hex decode |
128
+ | `encoding_md5` | `text` | MD5 hash |
129
+ | `encoding_sha256` | `text` | SHA-256 hash |
130
+
131
+ ### DateTime (8 tools)
132
+
133
+ All operate on provided dates — never use current time.
134
+
135
+ | Tool | Params | Description |
136
+ |------|--------|-------------|
137
+ | `datetime_parse` | `date_string` | Parse ISO date to components |
138
+ | `datetime_format` | `year`, `month`, `day`, `format` | Format date to string |
139
+ | `datetime_add_days` | `date_string`, `days` | Add/subtract days |
140
+ | `datetime_diff` | `date_a`, `date_b` | Days between two dates |
141
+ | `datetime_day_of_week` | `date_string` | Weekday name |
142
+ | `datetime_is_leap_year` | `year` | Leap year check |
143
+ | `datetime_days_in_month` | `year`, `month` | Days in month |
144
+ | `datetime_week_number` | `date_string` | ISO week number |
145
+
146
+ ### Validation (8 tools)
147
+
148
+ All return `{valid: bool, reason: string}`.
149
+
150
+ | Tool | Params | Description |
151
+ |------|--------|-------------|
152
+ | `validation_is_email` | `text` | Email format check |
153
+ | `validation_is_url` | `text` | URL format check |
154
+ | `validation_is_ipv4` | `text` | IPv4 address check |
155
+ | `validation_is_ipv6` | `text` | IPv6 address check |
156
+ | `validation_is_uuid` | `text` | UUID format check |
157
+ | `validation_is_json` | `text` | Valid JSON check |
158
+ | `validation_is_palindrome` | `text` | Palindrome check |
159
+ | `validation_matches_regex` | `text`, `pattern` | Regex match check |
160
+
161
+ ### Conversion (8 tools)
162
+
163
+ | Tool | Params | Description |
164
+ |------|--------|-------------|
165
+ | `conversion_celsius_to_fahrenheit` | `value` | C to F |
166
+ | `conversion_fahrenheit_to_celsius` | `value` | F to C |
167
+ | `conversion_km_to_miles` | `value` | km to miles |
168
+ | `conversion_miles_to_km` | `value` | miles to km |
169
+ | `conversion_bytes_to_human` | `bytes` | Bytes to human string |
170
+ | `conversion_rgb_to_hex` | `r`, `g`, `b` | RGB to hex color |
171
+ | `conversion_hex_to_rgb` | `hex_color` | Hex to RGB |
172
+ | `conversion_decimal_to_binary` | `value` | Decimal to binary string |
173
+
174
+ ### Echo / Protocol Testing (8 tools)
175
+
176
+ | Tool | Params | Description |
177
+ |------|--------|-------------|
178
+ | `echo` | `message` | Echo input unchanged |
179
+ | `echo_error` | `message` | Always raises ToolError |
180
+ | `echo_large` | `size_kb` | Deterministic ~N KB text |
181
+ | `echo_nested` | `depth` | Nested JSON to depth N |
182
+ | `echo_types` | — | All JSON types |
183
+ | `echo_empty` | — | Empty string |
184
+ | `echo_multiple` | `messages` | Multiple TextContent blocks |
185
+ | `echo_schema` | `str_param`, `int_param`, `float_param`, `bool_param`, `list_param`, `obj_param` | Complex schema test |
186
+
187
+ ### Standalone
188
+
189
+ | Tool | Params | Description |
190
+ |------|--------|-------------|
191
+ | `get_weather` | `city` | Fixed response: 77°F, sunny, always |
192
+
193
+ ## REST API
194
+
195
+ When running with `--transport sse`, all tools are also available as HTTP endpoints:
196
+
197
+ ```bash
198
+ # GET endpoints (math, conversion, some echo, weather)
199
+ curl "http://localhost:3001/api/math/add?a=3&b=5"
200
+ # {"result": 8.0}
201
+
202
+ curl "http://localhost:3001/api/weather?city=NYC"
203
+ # {"city":"NYC","temperature_f":77,...}
204
+
205
+ # POST endpoints (string, collection, encoding, datetime, validation, some echo)
206
+ curl -X POST "http://localhost:3001/api/string/reverse" \
207
+ -H "Content-Type: application/json" \
208
+ -d '{"text":"hello"}'
209
+ # {"result": "olleh"}
210
+
211
+ curl -X POST "http://localhost:3001/api/encoding/sha256" \
212
+ -H "Content-Type: application/json" \
213
+ -d '{"text":"hello"}'
214
+ # {"result": "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"}
215
+ ```
216
+
217
+ **OpenAPI spec** at `GET /api-docs` (OpenAPI 3.0.3).
218
+
219
+ With auth enabled, include `Authorization: Bearer <key>` on all requests.
220
+
221
+ ## Testing
222
+
223
+ ```bash
224
+ python3 -m pytest tests/ -v
225
+ ```
226
+
227
+ 389 tests covering all tools, REST API endpoints, auth, OpenAPI spec, and integration.
228
+
229
+ ## Project Structure
230
+
231
+ ```
232
+ mcp-test-server/
233
+ ├── pyproject.toml # Package config, deps, CLI entry point
234
+ ├── mcp_test_server/
235
+ │ ├── __init__.py # Package version
236
+ │ ├── server.py # Entry point — MCP server + REST API + auth
237
+ │ ├── api.py # REST API routes + OpenAPI spec generation
238
+ │ └── tools/
239
+ │ ├── __init__.py # Tool group registry
240
+ │ ├── math_tools.py
241
+ │ ├── string_tools.py
242
+ │ ├── collection_tools.py
243
+ │ ├── encoding_tools.py
244
+ │ ├── datetime_tools.py
245
+ │ ├── validation_tools.py
246
+ │ ├── conversion_tools.py
247
+ │ └── echo_tools.py # Includes get_weather
248
+ └── tests/
249
+ ├── test_math_tools.py
250
+ ├── test_string_tools.py
251
+ ├── test_collection_tools.py
252
+ ├── test_encoding_tools.py
253
+ ├── test_datetime_tools.py
254
+ ├── test_validation_tools.py
255
+ ├── test_conversion_tools.py
256
+ ├── test_echo_tools.py
257
+ ├── test_api.py
258
+ └── test_integration.py
259
+ ```