mcp-api-test 0.1.2__tar.gz → 0.1.4__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.
- mcp_api_test-0.1.4/PKG-INFO +112 -0
- mcp_api_test-0.1.4/README.md +81 -0
- {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/pyproject.toml +5 -4
- mcp_api_test-0.1.4/src/flags.py +61 -0
- {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/src/logger.py +8 -0
- mcp_api_test-0.1.4/src/mcp_api_test.egg-info/PKG-INFO +112 -0
- {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/src/mcp_api_test.egg-info/SOURCES.txt +4 -1
- {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/src/mcp_api_test.egg-info/requires.txt +4 -1
- {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/src/mcp_api_test.egg-info/top_level.txt +1 -0
- {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/src/server.py +27 -9
- {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/src/utils.py +14 -11
- mcp_api_test-0.1.4/tests/test_packaging.py +46 -0
- mcp_api_test-0.1.4/tests/test_parse_curl_command.py +70 -0
- mcp_api_test-0.1.4/tests/test_utils.py +23 -0
- mcp_api_test-0.1.2/PKG-INFO +0 -334
- mcp_api_test-0.1.2/README.md +0 -305
- mcp_api_test-0.1.2/src/mcp_api_test.egg-info/PKG-INFO +0 -334
- mcp_api_test-0.1.2/tests/test_parse_curl_command.py +0 -23
- {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/LICENSE +0 -0
- {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/MANIFEST.in +0 -0
- {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/setup.cfg +0 -0
- {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/src/__main__.py +0 -0
- {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/src/mcp_api_test.egg-info/dependency_links.txt +0 -0
- {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/src/mcp_api_test.egg-info/entry_points.txt +0 -0
- {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/src/models.py +0 -0
- {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/tests/test_mcp_api_tools.py +0 -0
- {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/tests/test_parse_status_code.py +0 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mcp-api-test
|
|
3
|
+
Version: 0.1.4
|
|
4
|
+
Summary: MCP server for testing REST API with built-in assertions
|
|
5
|
+
Author: Ryan Febriansyah
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: testing,mcp,llm
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Natural Language :: English
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
17
|
+
Classifier: Topic :: Software Development :: Testing
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: mcp<2.0.0,>=1.0.0
|
|
22
|
+
Requires-Dist: httpx
|
|
23
|
+
Requires-Dist: python-dotenv
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
27
|
+
Requires-Dist: respx; extra == "dev"
|
|
28
|
+
Provides-Extra: docs
|
|
29
|
+
Requires-Dist: mkdocs-material; extra == "docs"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# mcp-api-test
|
|
33
|
+
|
|
34
|
+
  
|
|
35
|
+
|
|
36
|
+
An AI-native API testing engine that enables LLM agents to execute, validate, and reason about REST APIs using natural language or cURL inputs.
|
|
37
|
+
|
|
38
|
+
Unlike traditional testing frameworks, `mcp-api-test` exposes API testing as a **first-class capability for AI agents** rather than a developer-facing tool. LLM-powered clients invoke the `run_test_api` tool directly, making API validation a building block for autonomous coding agents, not a separate human-driven workflow.
|
|
39
|
+
|
|
40
|
+
It complements (rather than replaces) tools like pytest, Postman, and Playwright.
|
|
41
|
+
|
|
42
|
+
## Use cases
|
|
43
|
+
|
|
44
|
+
- **QA & test engineering**: prompt an agent with natural language instead of crafting Postman requests
|
|
45
|
+
- **Post-deployment regression**: have an agent smoke-test critical paths after a deploy
|
|
46
|
+
- **Quick validation**: test an endpoint inline during a coding session
|
|
47
|
+
- **CI/CD automation**: agents validate deployments autonomously in pipelines
|
|
48
|
+
|
|
49
|
+
> **Token efficiency.** Agents get deterministic, structured results in a single tool call instead of parsing raw `curl` output. Fewer tokens, fewer hallucinations.
|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install mcp-api-test
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Or from source:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git clone git@gitlab.com:ryaneatfood/mcp-api-testing.git
|
|
61
|
+
cd mcp-api-testing
|
|
62
|
+
pip install -e .
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Requires Python >= 3.10.
|
|
66
|
+
|
|
67
|
+
## Quick start
|
|
68
|
+
|
|
69
|
+
Configure your MCP client to use the server. For **OpenCode**, add to `opencode.json`:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"mcp": {
|
|
74
|
+
"mcp-api-test": {
|
|
75
|
+
"type": "local",
|
|
76
|
+
"command": ["mcp-api-test"],
|
|
77
|
+
"enabled": true
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
For **Claude Desktop**, add to `claude_desktop_config.json`:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"mcpServers": {
|
|
88
|
+
"mcp-api-test": {
|
|
89
|
+
"command": "mcp-api-test"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Then prompt your agent:
|
|
96
|
+
|
|
97
|
+
> "Test this API: `curl -X POST https://api.example.com/v1/users -H 'Authorization: Bearer xxx' -d '{"name":"test"}'`, ensure status code is 201 and response contains 'id'"
|
|
98
|
+
|
|
99
|
+
The agent invokes `run_test_api`, validates against the expected contract, and returns structured pass/fail results.
|
|
100
|
+
|
|
101
|
+
## Documentation
|
|
102
|
+
|
|
103
|
+
Full documentation is hosted on this [page](https://ryaneatfood.gitlab.io/mcp-api-testing/), and another way to navigate the docs:
|
|
104
|
+
|
|
105
|
+
- **[Getting Started](docs/getting-started/installation.md)**: installation and client setup
|
|
106
|
+
- **[Usage](docs/usage/request-modes.md)**: cURL mode, structured mode, assertions, response format
|
|
107
|
+
- **[Guides](docs/guides/troubleshooting.md)**: troubleshooting, development, configuration
|
|
108
|
+
- **[Reference](docs/reference/tool-parameters.md)**: full parameter reference
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# mcp-api-test
|
|
2
|
+
|
|
3
|
+
  
|
|
4
|
+
|
|
5
|
+
An AI-native API testing engine that enables LLM agents to execute, validate, and reason about REST APIs using natural language or cURL inputs.
|
|
6
|
+
|
|
7
|
+
Unlike traditional testing frameworks, `mcp-api-test` exposes API testing as a **first-class capability for AI agents** rather than a developer-facing tool. LLM-powered clients invoke the `run_test_api` tool directly, making API validation a building block for autonomous coding agents, not a separate human-driven workflow.
|
|
8
|
+
|
|
9
|
+
It complements (rather than replaces) tools like pytest, Postman, and Playwright.
|
|
10
|
+
|
|
11
|
+
## Use cases
|
|
12
|
+
|
|
13
|
+
- **QA & test engineering**: prompt an agent with natural language instead of crafting Postman requests
|
|
14
|
+
- **Post-deployment regression**: have an agent smoke-test critical paths after a deploy
|
|
15
|
+
- **Quick validation**: test an endpoint inline during a coding session
|
|
16
|
+
- **CI/CD automation**: agents validate deployments autonomously in pipelines
|
|
17
|
+
|
|
18
|
+
> **Token efficiency.** Agents get deterministic, structured results in a single tool call instead of parsing raw `curl` output. Fewer tokens, fewer hallucinations.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install mcp-api-test
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or from source:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
git clone git@gitlab.com:ryaneatfood/mcp-api-testing.git
|
|
30
|
+
cd mcp-api-testing
|
|
31
|
+
pip install -e .
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Requires Python >= 3.10.
|
|
35
|
+
|
|
36
|
+
## Quick start
|
|
37
|
+
|
|
38
|
+
Configure your MCP client to use the server. For **OpenCode**, add to `opencode.json`:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"mcp": {
|
|
43
|
+
"mcp-api-test": {
|
|
44
|
+
"type": "local",
|
|
45
|
+
"command": ["mcp-api-test"],
|
|
46
|
+
"enabled": true
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
For **Claude Desktop**, add to `claude_desktop_config.json`:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"mcpServers": {
|
|
57
|
+
"mcp-api-test": {
|
|
58
|
+
"command": "mcp-api-test"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Then prompt your agent:
|
|
65
|
+
|
|
66
|
+
> "Test this API: `curl -X POST https://api.example.com/v1/users -H 'Authorization: Bearer xxx' -d '{"name":"test"}'`, ensure status code is 201 and response contains 'id'"
|
|
67
|
+
|
|
68
|
+
The agent invokes `run_test_api`, validates against the expected contract, and returns structured pass/fail results.
|
|
69
|
+
|
|
70
|
+
## Documentation
|
|
71
|
+
|
|
72
|
+
Full documentation is hosted on this [page](https://ryaneatfood.gitlab.io/mcp-api-testing/), and another way to navigate the docs:
|
|
73
|
+
|
|
74
|
+
- **[Getting Started](docs/getting-started/installation.md)**: installation and client setup
|
|
75
|
+
- **[Usage](docs/usage/request-modes.md)**: cURL mode, structured mode, assertions, response format
|
|
76
|
+
- **[Guides](docs/guides/troubleshooting.md)**: troubleshooting, development, configuration
|
|
77
|
+
- **[Reference](docs/reference/tool-parameters.md)**: full parameter reference
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
MIT
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mcp-api-test"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.4"
|
|
4
4
|
description = "MCP server for testing REST API with built-in assertions"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -10,7 +10,7 @@ authors = [
|
|
|
10
10
|
|
|
11
11
|
requires-python = ">=3.10"
|
|
12
12
|
dependencies = [
|
|
13
|
-
"mcp",
|
|
13
|
+
"mcp>=1.0.0,<2.0.0",
|
|
14
14
|
"httpx",
|
|
15
15
|
"python-dotenv"
|
|
16
16
|
]
|
|
@@ -39,7 +39,8 @@ build-backend = "setuptools.build_meta"
|
|
|
39
39
|
|
|
40
40
|
[tool.setuptools]
|
|
41
41
|
package-dir = {"" = "src"}
|
|
42
|
-
py-modules = ["server", "__main__", "logger", "models", "utils"]
|
|
42
|
+
py-modules = ["server", "__main__", "logger", "models", "utils", "flags"]
|
|
43
43
|
|
|
44
44
|
[project.optional-dependencies]
|
|
45
|
-
dev = ["pytest", "pytest-asyncio", "respx"]
|
|
45
|
+
dev = ["pytest", "pytest-asyncio", "respx"]
|
|
46
|
+
docs = ["mkdocs-material"]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""A collection of curl flag which is being used during HTTP request/inspection"""
|
|
2
|
+
|
|
3
|
+
# flags that consume the next token as their value,
|
|
4
|
+
# maps every recognize consuming flag to its semantic roles
|
|
5
|
+
CURL_VALUE_FLAGS: dict[str, str] = {
|
|
6
|
+
"-X": "method",
|
|
7
|
+
"--request": "method",
|
|
8
|
+
"-H": "header",
|
|
9
|
+
"--header": "header",
|
|
10
|
+
"-d": "data",
|
|
11
|
+
"--data": "data",
|
|
12
|
+
"--data-raw": "data", # browser DevTools "Copy as cURL" uses this
|
|
13
|
+
"--data-binary": "data", # common in API clients
|
|
14
|
+
"--data-urlencode": "data", # URL-encoded payloads
|
|
15
|
+
"-u": "user",
|
|
16
|
+
"--user": "user",
|
|
17
|
+
"--url": "url_flag", # explicit --url form
|
|
18
|
+
"-A": "user_agent",
|
|
19
|
+
"--user-agent": "user_agent",
|
|
20
|
+
"--max-time": "ignored",
|
|
21
|
+
"--connect-timeout": "ignored",
|
|
22
|
+
"-o": "ignored",
|
|
23
|
+
"--output": "ignored",
|
|
24
|
+
"--proxy": "ignored",
|
|
25
|
+
"-x": "ignored",
|
|
26
|
+
"-e": "ignored",
|
|
27
|
+
"--referer": "ignored",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# standalone flags with no following value
|
|
31
|
+
CURL_BOOLEAN_FLAGS: dict[str, str] = {
|
|
32
|
+
"-L",
|
|
33
|
+
"--location", # follow redirects, very common in browser exports
|
|
34
|
+
"-k",
|
|
35
|
+
"--insecure", # skip TLS verification
|
|
36
|
+
"-s",
|
|
37
|
+
"--silent",
|
|
38
|
+
"-v",
|
|
39
|
+
"--verbose",
|
|
40
|
+
"-i",
|
|
41
|
+
"--include",
|
|
42
|
+
"-I",
|
|
43
|
+
"--head",
|
|
44
|
+
"--compressed", # added by Chrome DevTools
|
|
45
|
+
"--http1.1",
|
|
46
|
+
"--http2",
|
|
47
|
+
"-G",
|
|
48
|
+
"--get",
|
|
49
|
+
"--no-keepalive",
|
|
50
|
+
"--fail",
|
|
51
|
+
"-f",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# flags that we recognize but want to surface with a warning for,
|
|
55
|
+
# emit a logger.warning with a human-readable explanation
|
|
56
|
+
CURL_WARN_FLAGS: dict[str, str] = {
|
|
57
|
+
"-k": "--insecure is not supported: TLS verification will still be enforced",
|
|
58
|
+
"--insecure": "--insecure is not supported: TLS verification will still be enforced",
|
|
59
|
+
"-u": "-u/--user (basic auth) is not supported: add an Authorization header explicitly",
|
|
60
|
+
"--user": "-u/--user (basic auth) is not supported: add an Authorization header explicitly",
|
|
61
|
+
}
|
|
@@ -39,6 +39,14 @@ class Logger:
|
|
|
39
39
|
message = message % args
|
|
40
40
|
self.logger.info(message, **kwargs)
|
|
41
41
|
|
|
42
|
+
def warning(self, message: str, *args, **kwargs) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Log a warning message with particular arguments
|
|
45
|
+
"""
|
|
46
|
+
if args:
|
|
47
|
+
message = message % args
|
|
48
|
+
self.logger.warning(message, **kwargs)
|
|
49
|
+
|
|
42
50
|
def error(self, message: str, *args, **kwargs) -> None:
|
|
43
51
|
"""
|
|
44
52
|
Log an error message with particular arguments
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mcp-api-test
|
|
3
|
+
Version: 0.1.4
|
|
4
|
+
Summary: MCP server for testing REST API with built-in assertions
|
|
5
|
+
Author: Ryan Febriansyah
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: testing,mcp,llm
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Natural Language :: English
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
17
|
+
Classifier: Topic :: Software Development :: Testing
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: mcp<2.0.0,>=1.0.0
|
|
22
|
+
Requires-Dist: httpx
|
|
23
|
+
Requires-Dist: python-dotenv
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
27
|
+
Requires-Dist: respx; extra == "dev"
|
|
28
|
+
Provides-Extra: docs
|
|
29
|
+
Requires-Dist: mkdocs-material; extra == "docs"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# mcp-api-test
|
|
33
|
+
|
|
34
|
+
  
|
|
35
|
+
|
|
36
|
+
An AI-native API testing engine that enables LLM agents to execute, validate, and reason about REST APIs using natural language or cURL inputs.
|
|
37
|
+
|
|
38
|
+
Unlike traditional testing frameworks, `mcp-api-test` exposes API testing as a **first-class capability for AI agents** rather than a developer-facing tool. LLM-powered clients invoke the `run_test_api` tool directly, making API validation a building block for autonomous coding agents, not a separate human-driven workflow.
|
|
39
|
+
|
|
40
|
+
It complements (rather than replaces) tools like pytest, Postman, and Playwright.
|
|
41
|
+
|
|
42
|
+
## Use cases
|
|
43
|
+
|
|
44
|
+
- **QA & test engineering**: prompt an agent with natural language instead of crafting Postman requests
|
|
45
|
+
- **Post-deployment regression**: have an agent smoke-test critical paths after a deploy
|
|
46
|
+
- **Quick validation**: test an endpoint inline during a coding session
|
|
47
|
+
- **CI/CD automation**: agents validate deployments autonomously in pipelines
|
|
48
|
+
|
|
49
|
+
> **Token efficiency.** Agents get deterministic, structured results in a single tool call instead of parsing raw `curl` output. Fewer tokens, fewer hallucinations.
|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install mcp-api-test
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Or from source:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git clone git@gitlab.com:ryaneatfood/mcp-api-testing.git
|
|
61
|
+
cd mcp-api-testing
|
|
62
|
+
pip install -e .
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Requires Python >= 3.10.
|
|
66
|
+
|
|
67
|
+
## Quick start
|
|
68
|
+
|
|
69
|
+
Configure your MCP client to use the server. For **OpenCode**, add to `opencode.json`:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"mcp": {
|
|
74
|
+
"mcp-api-test": {
|
|
75
|
+
"type": "local",
|
|
76
|
+
"command": ["mcp-api-test"],
|
|
77
|
+
"enabled": true
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
For **Claude Desktop**, add to `claude_desktop_config.json`:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"mcpServers": {
|
|
88
|
+
"mcp-api-test": {
|
|
89
|
+
"command": "mcp-api-test"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Then prompt your agent:
|
|
96
|
+
|
|
97
|
+
> "Test this API: `curl -X POST https://api.example.com/v1/users -H 'Authorization: Bearer xxx' -d '{"name":"test"}'`, ensure status code is 201 and response contains 'id'"
|
|
98
|
+
|
|
99
|
+
The agent invokes `run_test_api`, validates against the expected contract, and returns structured pass/fail results.
|
|
100
|
+
|
|
101
|
+
## Documentation
|
|
102
|
+
|
|
103
|
+
Full documentation is hosted on this [page](https://ryaneatfood.gitlab.io/mcp-api-testing/), and another way to navigate the docs:
|
|
104
|
+
|
|
105
|
+
- **[Getting Started](docs/getting-started/installation.md)**: installation and client setup
|
|
106
|
+
- **[Usage](docs/usage/request-modes.md)**: cURL mode, structured mode, assertions, response format
|
|
107
|
+
- **[Guides](docs/guides/troubleshooting.md)**: troubleshooting, development, configuration
|
|
108
|
+
- **[Reference](docs/reference/tool-parameters.md)**: full parameter reference
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT
|
|
@@ -3,6 +3,7 @@ MANIFEST.in
|
|
|
3
3
|
README.md
|
|
4
4
|
pyproject.toml
|
|
5
5
|
src/__main__.py
|
|
6
|
+
src/flags.py
|
|
6
7
|
src/logger.py
|
|
7
8
|
src/models.py
|
|
8
9
|
src/server.py
|
|
@@ -14,5 +15,7 @@ src/mcp_api_test.egg-info/entry_points.txt
|
|
|
14
15
|
src/mcp_api_test.egg-info/requires.txt
|
|
15
16
|
src/mcp_api_test.egg-info/top_level.txt
|
|
16
17
|
tests/test_mcp_api_tools.py
|
|
18
|
+
tests/test_packaging.py
|
|
17
19
|
tests/test_parse_curl_command.py
|
|
18
|
-
tests/test_parse_status_code.py
|
|
20
|
+
tests/test_parse_status_code.py
|
|
21
|
+
tests/test_utils.py
|
|
@@ -80,7 +80,7 @@ def parse_curl_command(command: str) -> Dict[str, Any]:
|
|
|
80
80
|
|
|
81
81
|
# emit warning for supported but warnin changing flags
|
|
82
82
|
if token in CURL_WARN_FLAGS:
|
|
83
|
-
logger.warning(message=f"[curl parser] {CURL_WARN_FLAGS[token]}
|
|
83
|
+
logger.warning(message=f"[curl parser] {CURL_WARN_FLAGS[token]}")
|
|
84
84
|
|
|
85
85
|
if role == "method":
|
|
86
86
|
method = value
|
|
@@ -96,11 +96,13 @@ def parse_curl_command(command: str) -> Dict[str, Any]:
|
|
|
96
96
|
payload = json.loads(raw_data)
|
|
97
97
|
except json.JSONDecodeError:
|
|
98
98
|
logger.error(
|
|
99
|
-
f"[curl parser] payload for '{token} is not valid JSON"
|
|
99
|
+
f"[curl parser] payload for '{token}' is not valid JSON"
|
|
100
100
|
)
|
|
101
101
|
payload = raw_data
|
|
102
102
|
elif role == "url_flag":
|
|
103
103
|
url = value
|
|
104
|
+
elif role == "user_agent":
|
|
105
|
+
headers["User-Agent"] = value
|
|
104
106
|
|
|
105
107
|
# everything else is consumed silently
|
|
106
108
|
index += 2
|
|
@@ -109,7 +111,7 @@ def parse_curl_command(command: str) -> Dict[str, Any]:
|
|
|
109
111
|
# boolean standalone flags
|
|
110
112
|
if token in CURL_BOOLEAN_FLAGS:
|
|
111
113
|
if token in CURL_WARN_FLAGS:
|
|
112
|
-
logger.
|
|
114
|
+
logger.warning(message=f"[curl parser] {CURL_WARN_FLAGS[token]}")
|
|
113
115
|
index += 1
|
|
114
116
|
continue
|
|
115
117
|
|
|
@@ -193,10 +195,15 @@ async def run_test_api(
|
|
|
193
195
|
start_time = time.perf_counter()
|
|
194
196
|
try:
|
|
195
197
|
async with httpx.AsyncClient(timeout=30) as client:
|
|
196
|
-
# prevent requests payload with None-type object or empty body
|
|
197
198
|
kwargs = {"method": method, "url": full_path_url, "headers": headers}
|
|
198
199
|
if payload is not None:
|
|
199
|
-
|
|
200
|
+
# cURL parser may produce a raw string for non-JSON -d payloads
|
|
201
|
+
# (example: form-encoded data). Use content= to send as-is rather
|
|
202
|
+
# than json= which would double-encode the string.
|
|
203
|
+
if isinstance(payload, str):
|
|
204
|
+
kwargs["content"] = payload
|
|
205
|
+
else:
|
|
206
|
+
kwargs["json"] = payload
|
|
200
207
|
response = await client.request(**kwargs)
|
|
201
208
|
end_time = time.perf_counter()
|
|
202
209
|
response_time_ms = (end_time - start_time) * 1000
|
|
@@ -245,9 +252,20 @@ async def run_test_api(
|
|
|
245
252
|
if required_response_fields:
|
|
246
253
|
for field in required_response_fields:
|
|
247
254
|
if isinstance(resp_body, dict):
|
|
248
|
-
# resolve conflict resolution about dot-nested structure
|
|
249
255
|
is_exist = get_nested_fields(body=resp_body, field_path=field)
|
|
256
|
+
elif isinstance(resp_body, list):
|
|
257
|
+
# For JSON arrays, check whether at least one item
|
|
258
|
+
# (if dict) contains the expected field.
|
|
259
|
+
is_exist = any(
|
|
260
|
+
(
|
|
261
|
+
get_nested_fields(body=item, field_path=field)
|
|
262
|
+
if isinstance(item, dict)
|
|
263
|
+
else field in str(item)
|
|
264
|
+
)
|
|
265
|
+
for item in resp_body
|
|
266
|
+
)
|
|
250
267
|
else:
|
|
268
|
+
# Scalar or plain-text response like substring match.
|
|
251
269
|
is_exist = field in str(resp_body)
|
|
252
270
|
assertions.append(
|
|
253
271
|
{
|
|
@@ -283,9 +301,9 @@ async def run_test_api(
|
|
|
283
301
|
)
|
|
284
302
|
)
|
|
285
303
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
)
|
|
304
|
+
# When no assertions are provided there is nothing to check,
|
|
305
|
+
# set default to False so the user can see the raw response and decide.
|
|
306
|
+
overall_result = all(a["passed"] for a in assertions) if assertions else False
|
|
289
307
|
|
|
290
308
|
result = APITestResult(
|
|
291
309
|
api_name=api_name,
|
|
@@ -31,16 +31,19 @@ def render_console_template_result(result: APITestResult) -> str:
|
|
|
31
31
|
lines.append("Assertions")
|
|
32
32
|
lines.append(divider)
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
34
|
+
if result.assertions:
|
|
35
|
+
for assertion in result.assertions:
|
|
36
|
+
symbol = "✓" if assertion.passed else "x"
|
|
37
|
+
lines.append(f"{symbol} {assertion.assertion}")
|
|
38
|
+
|
|
39
|
+
lines.append("")
|
|
40
|
+
lines.append("Summary")
|
|
41
|
+
lines.append(divider)
|
|
42
|
+
lines.append(f"Total Assertions : {len(result.assertions)}")
|
|
43
|
+
lines.append(f"Passed : {passed_assertions}")
|
|
44
|
+
lines.append(f"Failed : {failed_assertions}")
|
|
45
|
+
else:
|
|
46
|
+
lines.append(" (no assertions provided)")
|
|
44
47
|
|
|
45
48
|
return "\n".join(lines)
|
|
46
49
|
|
|
@@ -105,7 +108,7 @@ def build_fullpath_url(base_url: str, endpoint: str) -> str:
|
|
|
105
108
|
_endpoint = endpoint.strip()
|
|
106
109
|
|
|
107
110
|
if not _endpoint:
|
|
108
|
-
|
|
111
|
+
_endpoint = "/"
|
|
109
112
|
|
|
110
113
|
if not _endpoint.startswith("/"):
|
|
111
114
|
# automaticaly prepend the '/' before raise an assertion
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Static checks that the source layout matches the wheel packaging config"""
|
|
2
|
+
|
|
3
|
+
import tomllib
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
|
7
|
+
SRC_DIR = PROJECT_ROOT / "src"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _packaged_modules() -> list[str]:
|
|
11
|
+
with open(PROJECT_ROOT / "pyproject.toml", "rb") as f:
|
|
12
|
+
config = tomllib.load(f)
|
|
13
|
+
return config["tool"]["setuptools"]["py-modules"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _project_version() -> str:
|
|
17
|
+
with open(PROJECT_ROOT / "pyproject.toml", "rb") as f:
|
|
18
|
+
config = tomllib.load(f)
|
|
19
|
+
return config["project"]["version"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_all_src_modules_are_packaged():
|
|
23
|
+
src_modules = {
|
|
24
|
+
p.stem
|
|
25
|
+
for p in SRC_DIR.glob("*.py")
|
|
26
|
+
if p.name != "__init__.py" and not p.name.startswith("__")
|
|
27
|
+
}
|
|
28
|
+
missing = src_modules - set(_packaged_modules())
|
|
29
|
+
assert not missing, (
|
|
30
|
+
f"Modules in src/ that are missing from pyproject.toml "
|
|
31
|
+
f"[tool.setuptools] py-modules: {sorted(missing)}"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_packaged_modules_exist_in_src():
|
|
36
|
+
for module in _packaged_modules():
|
|
37
|
+
assert (
|
|
38
|
+
SRC_DIR / f"{module}.py"
|
|
39
|
+
).exists(), f"'{module}' is listed in py-modules but has no src/{module}.py"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_version_matches_pyproject():
|
|
43
|
+
# regression: src/__init__.py __version__ drifted from pyproject.toml
|
|
44
|
+
import src
|
|
45
|
+
|
|
46
|
+
assert src.__version__ == _project_version()
|