httpx-mcp 1.0.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.
- httpx_mcp-1.0.0/PKG-INFO +164 -0
- httpx_mcp-1.0.0/README.md +154 -0
- httpx_mcp-1.0.0/httpx_mcp/__init__.py +3 -0
- httpx_mcp-1.0.0/httpx_mcp/server.py +362 -0
- httpx_mcp-1.0.0/pyproject.toml +23 -0
httpx_mcp-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: httpx-mcp
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A MCP tool for HTTP request testing using httpx
|
|
5
|
+
Author-email: ZHEFOX <zhefox@outlook.com>
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: httpx>=0.27.0
|
|
8
|
+
Requires-Dist: mcp>=1.0.0
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# HTTPX MCP
|
|
12
|
+
|
|
13
|
+
An MCP tool for HTTP interface testing, built on the httpx library, designed for AI.
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- 🚀 **Full HTTP Method Support**: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
|
|
18
|
+
- 📝 **Multiple Request Formats**: JSON, form data, raw text
|
|
19
|
+
- 🔧 **Custom Headers**: Support for arbitrary HTTP headers
|
|
20
|
+
- 📋 **Raw Request Parsing**: Directly paste Burp Suite captured requests
|
|
21
|
+
- ⏱️ **Response Details**: Status code, response headers, response body, timing statistics
|
|
22
|
+
- 🔒 **SSL Control**: Optional SSL certificate verification
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
cd httpx-mcp
|
|
28
|
+
pip install -e .
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## MCP Configuration
|
|
32
|
+
|
|
33
|
+
Add to your MCP configuration file:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"mcpServers": {
|
|
38
|
+
"httpx-mcp": {
|
|
39
|
+
"command": "python",
|
|
40
|
+
"args": ["-m", "httpx_mcp.server"],
|
|
41
|
+
"cwd": "c:/Users/ZHEFOX/Desktop/mcptools/httpx-mcp"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or use directly after installation:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"mcpServers": {
|
|
52
|
+
"httpx-mcp": {
|
|
53
|
+
"command": "httpx-mcp"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Available Tools
|
|
60
|
+
|
|
61
|
+
### 1. `http_request` - General HTTP Request
|
|
62
|
+
|
|
63
|
+
Send any HTTP request, switch request method via the `method` parameter.
|
|
64
|
+
|
|
65
|
+
**Parameters:**
|
|
66
|
+
| Parameter | Type | Required | Default | Description |
|
|
67
|
+
|-----------|------|----------|---------|-------------|
|
|
68
|
+
| `url` | string | ✅ | - | Full URL |
|
|
69
|
+
| `method` | string | - | GET | HTTP method: GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS |
|
|
70
|
+
| `params` | string | - | - | URL query parameters, JSON or key=value format |
|
|
71
|
+
| `headers` | string | - | - | Request headers, JSON format |
|
|
72
|
+
| `body` | string | - | - | Request body |
|
|
73
|
+
| `content_type` | string | - | application/json | Content-Type |
|
|
74
|
+
| `timeout` | number | - | 30 | Timeout in seconds |
|
|
75
|
+
| `follow_redirects` | boolean | - | true | Whether to follow redirects |
|
|
76
|
+
| `verify_ssl` | boolean | - | true | Whether to verify SSL |
|
|
77
|
+
| `include_headers` | boolean | - | true | Whether response includes headers |
|
|
78
|
+
|
|
79
|
+
**Examples:**
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
# GET request
|
|
83
|
+
method="GET", url="https://httpbin.org/get"
|
|
84
|
+
|
|
85
|
+
# GET with query parameters
|
|
86
|
+
method="GET", url="https://httpbin.org/get", params='{"page": "1", "size": "10"}'
|
|
87
|
+
|
|
88
|
+
# POST JSON
|
|
89
|
+
method="POST", url="https://httpbin.org/post", body='{"name": "test", "value": 123}'
|
|
90
|
+
|
|
91
|
+
# PUT update
|
|
92
|
+
method="PUT", url="https://httpbin.org/put", body='{"id": 1, "name": "updated"}'
|
|
93
|
+
|
|
94
|
+
# DELETE
|
|
95
|
+
method="DELETE", url="https://httpbin.org/delete"
|
|
96
|
+
|
|
97
|
+
# With authentication header
|
|
98
|
+
method="GET", url="https://api.example.com/users", headers='{"Authorization": "Bearer token123"}'
|
|
99
|
+
|
|
100
|
+
# POST form
|
|
101
|
+
method="POST", url="https://httpbin.org/post", body="username=admin&password=123", content_type="application/x-www-form-urlencoded"
|
|
102
|
+
|
|
103
|
+
# Disable SSL verification
|
|
104
|
+
method="GET", url="https://self-signed.example.com", verify_ssl=false
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 2. `http_raw` - Raw HTTP Request
|
|
108
|
+
|
|
109
|
+
Directly parse raw HTTP requests captured by tools like Burp Suite.
|
|
110
|
+
|
|
111
|
+
**Parameters:**
|
|
112
|
+
| Parameter | Type | Required | Default | Description |
|
|
113
|
+
|-----------|------|----------|---------|-------------|
|
|
114
|
+
| `raw_request` | string | ✅ | - | Raw HTTP request text |
|
|
115
|
+
| `base_url` | string | - | - | Base URL (if request doesn't contain full URL) |
|
|
116
|
+
| `verify_ssl` | boolean | - | true | Whether to verify SSL |
|
|
117
|
+
|
|
118
|
+
**Example:**
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
raw_request="""
|
|
122
|
+
POST /api/login HTTP/1.1
|
|
123
|
+
Host: example.com
|
|
124
|
+
Content-Type: application/json
|
|
125
|
+
Cookie: session=abc123
|
|
126
|
+
|
|
127
|
+
{"username":"admin","password":"123456"}
|
|
128
|
+
"""
|
|
129
|
+
base_url="https://example.com"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Response Format
|
|
133
|
+
|
|
134
|
+
The tool returns formatted response information:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
HTTP/1.1 200 OK
|
|
138
|
+
|
|
139
|
+
=== Response Headers ===
|
|
140
|
+
content-type: application/json
|
|
141
|
+
date: Mon, 01 Jan 2024 00:00:00 GMT
|
|
142
|
+
...
|
|
143
|
+
|
|
144
|
+
=== Response Body ===
|
|
145
|
+
{
|
|
146
|
+
"result": "success",
|
|
147
|
+
"data": {...}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
=== Request Info ===
|
|
151
|
+
Time: 0.234s
|
|
152
|
+
Size: 1024 bytes
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Use Cases
|
|
156
|
+
|
|
157
|
+
1. **API Testing**: Quickly test REST API endpoints
|
|
158
|
+
2. **Security Testing**: Send custom payloads for security testing
|
|
159
|
+
3. **Interface Debugging**: View detailed request/response information
|
|
160
|
+
4. **Request Replay**: Directly use Burp captured content to replay requests
|
|
161
|
+
|
|
162
|
+
## License
|
|
163
|
+
|
|
164
|
+
MIT
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# HTTPX MCP
|
|
2
|
+
|
|
3
|
+
An MCP tool for HTTP interface testing, built on the httpx library, designed for AI.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🚀 **Full HTTP Method Support**: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
|
|
8
|
+
- 📝 **Multiple Request Formats**: JSON, form data, raw text
|
|
9
|
+
- 🔧 **Custom Headers**: Support for arbitrary HTTP headers
|
|
10
|
+
- 📋 **Raw Request Parsing**: Directly paste Burp Suite captured requests
|
|
11
|
+
- ⏱️ **Response Details**: Status code, response headers, response body, timing statistics
|
|
12
|
+
- 🔒 **SSL Control**: Optional SSL certificate verification
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
cd httpx-mcp
|
|
18
|
+
pip install -e .
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## MCP Configuration
|
|
22
|
+
|
|
23
|
+
Add to your MCP configuration file:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mcpServers": {
|
|
28
|
+
"httpx-mcp": {
|
|
29
|
+
"command": "python",
|
|
30
|
+
"args": ["-m", "httpx_mcp.server"],
|
|
31
|
+
"cwd": "c:/Users/ZHEFOX/Desktop/mcptools/httpx-mcp"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Or use directly after installation:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"mcpServers": {
|
|
42
|
+
"httpx-mcp": {
|
|
43
|
+
"command": "httpx-mcp"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Available Tools
|
|
50
|
+
|
|
51
|
+
### 1. `http_request` - General HTTP Request
|
|
52
|
+
|
|
53
|
+
Send any HTTP request, switch request method via the `method` parameter.
|
|
54
|
+
|
|
55
|
+
**Parameters:**
|
|
56
|
+
| Parameter | Type | Required | Default | Description |
|
|
57
|
+
|-----------|------|----------|---------|-------------|
|
|
58
|
+
| `url` | string | ✅ | - | Full URL |
|
|
59
|
+
| `method` | string | - | GET | HTTP method: GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS |
|
|
60
|
+
| `params` | string | - | - | URL query parameters, JSON or key=value format |
|
|
61
|
+
| `headers` | string | - | - | Request headers, JSON format |
|
|
62
|
+
| `body` | string | - | - | Request body |
|
|
63
|
+
| `content_type` | string | - | application/json | Content-Type |
|
|
64
|
+
| `timeout` | number | - | 30 | Timeout in seconds |
|
|
65
|
+
| `follow_redirects` | boolean | - | true | Whether to follow redirects |
|
|
66
|
+
| `verify_ssl` | boolean | - | true | Whether to verify SSL |
|
|
67
|
+
| `include_headers` | boolean | - | true | Whether response includes headers |
|
|
68
|
+
|
|
69
|
+
**Examples:**
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
# GET request
|
|
73
|
+
method="GET", url="https://httpbin.org/get"
|
|
74
|
+
|
|
75
|
+
# GET with query parameters
|
|
76
|
+
method="GET", url="https://httpbin.org/get", params='{"page": "1", "size": "10"}'
|
|
77
|
+
|
|
78
|
+
# POST JSON
|
|
79
|
+
method="POST", url="https://httpbin.org/post", body='{"name": "test", "value": 123}'
|
|
80
|
+
|
|
81
|
+
# PUT update
|
|
82
|
+
method="PUT", url="https://httpbin.org/put", body='{"id": 1, "name": "updated"}'
|
|
83
|
+
|
|
84
|
+
# DELETE
|
|
85
|
+
method="DELETE", url="https://httpbin.org/delete"
|
|
86
|
+
|
|
87
|
+
# With authentication header
|
|
88
|
+
method="GET", url="https://api.example.com/users", headers='{"Authorization": "Bearer token123"}'
|
|
89
|
+
|
|
90
|
+
# POST form
|
|
91
|
+
method="POST", url="https://httpbin.org/post", body="username=admin&password=123", content_type="application/x-www-form-urlencoded"
|
|
92
|
+
|
|
93
|
+
# Disable SSL verification
|
|
94
|
+
method="GET", url="https://self-signed.example.com", verify_ssl=false
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 2. `http_raw` - Raw HTTP Request
|
|
98
|
+
|
|
99
|
+
Directly parse raw HTTP requests captured by tools like Burp Suite.
|
|
100
|
+
|
|
101
|
+
**Parameters:**
|
|
102
|
+
| Parameter | Type | Required | Default | Description |
|
|
103
|
+
|-----------|------|----------|---------|-------------|
|
|
104
|
+
| `raw_request` | string | ✅ | - | Raw HTTP request text |
|
|
105
|
+
| `base_url` | string | - | - | Base URL (if request doesn't contain full URL) |
|
|
106
|
+
| `verify_ssl` | boolean | - | true | Whether to verify SSL |
|
|
107
|
+
|
|
108
|
+
**Example:**
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
raw_request="""
|
|
112
|
+
POST /api/login HTTP/1.1
|
|
113
|
+
Host: example.com
|
|
114
|
+
Content-Type: application/json
|
|
115
|
+
Cookie: session=abc123
|
|
116
|
+
|
|
117
|
+
{"username":"admin","password":"123456"}
|
|
118
|
+
"""
|
|
119
|
+
base_url="https://example.com"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Response Format
|
|
123
|
+
|
|
124
|
+
The tool returns formatted response information:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
HTTP/1.1 200 OK
|
|
128
|
+
|
|
129
|
+
=== Response Headers ===
|
|
130
|
+
content-type: application/json
|
|
131
|
+
date: Mon, 01 Jan 2024 00:00:00 GMT
|
|
132
|
+
...
|
|
133
|
+
|
|
134
|
+
=== Response Body ===
|
|
135
|
+
{
|
|
136
|
+
"result": "success",
|
|
137
|
+
"data": {...}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
=== Request Info ===
|
|
141
|
+
Time: 0.234s
|
|
142
|
+
Size: 1024 bytes
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Use Cases
|
|
146
|
+
|
|
147
|
+
1. **API Testing**: Quickly test REST API endpoints
|
|
148
|
+
2. **Security Testing**: Send custom payloads for security testing
|
|
149
|
+
3. **Interface Debugging**: View detailed request/response information
|
|
150
|
+
4. **Request Replay**: Directly use Burp captured content to replay requests
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
MIT
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HTTPX MCP Server - HTTP Request Testing Tool
|
|
3
|
+
|
|
4
|
+
Provides the following MCP tools:
|
|
5
|
+
- http_request: Send HTTP requests (GET, POST, PUT, DELETE, PATCH, etc.)
|
|
6
|
+
- http_raw: Parse raw HTTP requests (supports Burp Suite capture format)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import sys
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
import httpx
|
|
14
|
+
from mcp.server import Server
|
|
15
|
+
from mcp.server.stdio import stdio_server
|
|
16
|
+
from mcp.types import Tool, TextContent
|
|
17
|
+
|
|
18
|
+
# Create MCP server
|
|
19
|
+
server = Server("httpx-mcp")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def format_response(response: httpx.Response, include_headers: bool = True) -> str:
|
|
23
|
+
"""Format HTTP response into a readable string"""
|
|
24
|
+
result_parts = []
|
|
25
|
+
|
|
26
|
+
# Status line
|
|
27
|
+
result_parts.append(f"HTTP/{response.http_version} {response.status_code} {response.reason_phrase}")
|
|
28
|
+
result_parts.append("")
|
|
29
|
+
|
|
30
|
+
# Response headers
|
|
31
|
+
if include_headers:
|
|
32
|
+
result_parts.append("=== Response Headers ===")
|
|
33
|
+
for name, value in response.headers.items():
|
|
34
|
+
result_parts.append(f"{name}: {value}")
|
|
35
|
+
result_parts.append("")
|
|
36
|
+
|
|
37
|
+
# Response body
|
|
38
|
+
result_parts.append("=== Response Body ===")
|
|
39
|
+
|
|
40
|
+
content_type = response.headers.get("content-type", "")
|
|
41
|
+
body = response.text
|
|
42
|
+
|
|
43
|
+
# Try to format JSON
|
|
44
|
+
if "application/json" in content_type:
|
|
45
|
+
try:
|
|
46
|
+
parsed = json.loads(body)
|
|
47
|
+
body = json.dumps(parsed, indent=2, ensure_ascii=False)
|
|
48
|
+
except json.JSONDecodeError:
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
result_parts.append(body)
|
|
52
|
+
|
|
53
|
+
# Add response time
|
|
54
|
+
result_parts.append("")
|
|
55
|
+
result_parts.append(f"=== Request Info ===")
|
|
56
|
+
result_parts.append(f"Time: {response.elapsed.total_seconds():.3f}s")
|
|
57
|
+
result_parts.append(f"Size: {len(response.content)} bytes")
|
|
58
|
+
|
|
59
|
+
return "\n".join(result_parts)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def parse_headers(headers_input: str | dict | list | None) -> dict:
|
|
63
|
+
"""Parse request headers, supports multiple formats"""
|
|
64
|
+
if headers_input is None:
|
|
65
|
+
return {}
|
|
66
|
+
|
|
67
|
+
if isinstance(headers_input, dict):
|
|
68
|
+
return headers_input
|
|
69
|
+
|
|
70
|
+
if isinstance(headers_input, list):
|
|
71
|
+
result = {}
|
|
72
|
+
for item in headers_input:
|
|
73
|
+
if isinstance(item, str) and ":" in item:
|
|
74
|
+
key, value = item.split(":", 1)
|
|
75
|
+
result[key.strip()] = value.strip()
|
|
76
|
+
elif isinstance(item, dict):
|
|
77
|
+
result.update(item)
|
|
78
|
+
return result
|
|
79
|
+
|
|
80
|
+
if isinstance(headers_input, str):
|
|
81
|
+
# Try to parse as JSON
|
|
82
|
+
try:
|
|
83
|
+
parsed = json.loads(headers_input)
|
|
84
|
+
if isinstance(parsed, dict):
|
|
85
|
+
return parsed
|
|
86
|
+
elif isinstance(parsed, list):
|
|
87
|
+
return parse_headers(parsed)
|
|
88
|
+
except json.JSONDecodeError:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
# Parse line by line "Key: Value" format
|
|
92
|
+
result = {}
|
|
93
|
+
for line in headers_input.strip().split("\n"):
|
|
94
|
+
line = line.strip()
|
|
95
|
+
if ":" in line:
|
|
96
|
+
key, value = line.split(":", 1)
|
|
97
|
+
result[key.strip()] = value.strip()
|
|
98
|
+
return result
|
|
99
|
+
|
|
100
|
+
return {}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def parse_params(params_input: str | dict | None) -> dict | None:
|
|
104
|
+
"""Parse URL query parameters"""
|
|
105
|
+
if params_input is None:
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
if isinstance(params_input, dict):
|
|
109
|
+
return params_input
|
|
110
|
+
|
|
111
|
+
if isinstance(params_input, str):
|
|
112
|
+
try:
|
|
113
|
+
return json.loads(params_input)
|
|
114
|
+
except json.JSONDecodeError:
|
|
115
|
+
# Parse key=value&key2=value2 format
|
|
116
|
+
result = {}
|
|
117
|
+
for pair in params_input.split("&"):
|
|
118
|
+
if "=" in pair:
|
|
119
|
+
key, value = pair.split("=", 1)
|
|
120
|
+
result[key.strip()] = value.strip()
|
|
121
|
+
return result if result else None
|
|
122
|
+
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@server.list_tools()
|
|
127
|
+
async def list_tools() -> list[Tool]:
|
|
128
|
+
"""List all available MCP tools"""
|
|
129
|
+
return [
|
|
130
|
+
Tool(
|
|
131
|
+
name="http_request",
|
|
132
|
+
description="""Send HTTP request to specified URL. Supports all HTTP methods (GET/POST/PUT/DELETE/PATCH, etc.).
|
|
133
|
+
|
|
134
|
+
Usage examples:
|
|
135
|
+
- GET request: method="GET", url="https://api.example.com/users"
|
|
136
|
+
- GET with params: method="GET", url="...", params='{"page":"1","size":"10"}'
|
|
137
|
+
- POST JSON: method="POST", url="...", body='{"key":"value"}'
|
|
138
|
+
- PUT update: method="PUT", url="...", body='{"id":1,"name":"test"}'
|
|
139
|
+
- DELETE: method="DELETE", url="https://api.example.com/users/1"
|
|
140
|
+
- Custom headers: headers='{"Authorization":"Bearer xxx"}'
|
|
141
|
+
- Form submit: body="name=test&age=18", content_type="application/x-www-form-urlencoded" """,
|
|
142
|
+
inputSchema={
|
|
143
|
+
"type": "object",
|
|
144
|
+
"properties": {
|
|
145
|
+
"method": {
|
|
146
|
+
"type": "string",
|
|
147
|
+
"description": "HTTP method: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS",
|
|
148
|
+
"enum": ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"],
|
|
149
|
+
"default": "GET"
|
|
150
|
+
},
|
|
151
|
+
"url": {
|
|
152
|
+
"type": "string",
|
|
153
|
+
"description": "Full URL for the request"
|
|
154
|
+
},
|
|
155
|
+
"params": {
|
|
156
|
+
"type": "string",
|
|
157
|
+
"description": "URL query parameters, JSON format or key=value&key2=value2 format"
|
|
158
|
+
},
|
|
159
|
+
"headers": {
|
|
160
|
+
"type": "string",
|
|
161
|
+
"description": "Request headers, JSON format, e.g.: {\"Authorization\": \"Bearer xxx\"}"
|
|
162
|
+
},
|
|
163
|
+
"body": {
|
|
164
|
+
"type": "string",
|
|
165
|
+
"description": "Request body, can be JSON string, form data, or raw text"
|
|
166
|
+
},
|
|
167
|
+
"content_type": {
|
|
168
|
+
"type": "string",
|
|
169
|
+
"description": "Content-Type, e.g.: application/json, application/x-www-form-urlencoded",
|
|
170
|
+
"default": "application/json"
|
|
171
|
+
},
|
|
172
|
+
"timeout": {
|
|
173
|
+
"type": "number",
|
|
174
|
+
"description": "Request timeout in seconds",
|
|
175
|
+
"default": 30
|
|
176
|
+
},
|
|
177
|
+
"follow_redirects": {
|
|
178
|
+
"type": "boolean",
|
|
179
|
+
"description": "Whether to follow redirects",
|
|
180
|
+
"default": True
|
|
181
|
+
},
|
|
182
|
+
"verify_ssl": {
|
|
183
|
+
"type": "boolean",
|
|
184
|
+
"description": "Whether to verify SSL certificate",
|
|
185
|
+
"default": True
|
|
186
|
+
},
|
|
187
|
+
"include_headers": {
|
|
188
|
+
"type": "boolean",
|
|
189
|
+
"description": "Whether to include response headers in the output",
|
|
190
|
+
"default": True
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
"required": ["url"]
|
|
194
|
+
}
|
|
195
|
+
),
|
|
196
|
+
Tool(
|
|
197
|
+
name="http_raw",
|
|
198
|
+
description="""Send raw HTTP request. Supports pasting request format directly from Burp Suite or other capture tools.
|
|
199
|
+
|
|
200
|
+
Raw request format example:
|
|
201
|
+
POST /api/login HTTP/1.1
|
|
202
|
+
Host: example.com
|
|
203
|
+
Content-Type: application/json
|
|
204
|
+
|
|
205
|
+
{"username":"admin","password":"123"}""",
|
|
206
|
+
inputSchema={
|
|
207
|
+
"type": "object",
|
|
208
|
+
"properties": {
|
|
209
|
+
"raw_request": {
|
|
210
|
+
"type": "string",
|
|
211
|
+
"description": "Raw HTTP request text (including request line, headers, blank line, body)"
|
|
212
|
+
},
|
|
213
|
+
"base_url": {
|
|
214
|
+
"type": "string",
|
|
215
|
+
"description": "Base URL (if raw_request doesn't contain full URL), e.g.: https://example.com"
|
|
216
|
+
},
|
|
217
|
+
"verify_ssl": {
|
|
218
|
+
"type": "boolean",
|
|
219
|
+
"description": "Whether to verify SSL certificate",
|
|
220
|
+
"default": True
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
"required": ["raw_request"]
|
|
224
|
+
}
|
|
225
|
+
)
|
|
226
|
+
]
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@server.call_tool()
|
|
230
|
+
async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
231
|
+
"""Execute MCP tool call"""
|
|
232
|
+
try:
|
|
233
|
+
if name == "http_request":
|
|
234
|
+
return await handle_http_request(arguments)
|
|
235
|
+
elif name == "http_raw":
|
|
236
|
+
return await handle_http_raw(arguments)
|
|
237
|
+
else:
|
|
238
|
+
return [TextContent(type="text", text=f"Unknown tool: {name}")]
|
|
239
|
+
except Exception as e:
|
|
240
|
+
return [TextContent(type="text", text=f"Error: {type(e).__name__}: {str(e)}")]
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
async def handle_http_request(args: dict[str, Any]) -> list[TextContent]:
|
|
244
|
+
"""Handle general HTTP request"""
|
|
245
|
+
method = args.get("method", "GET").upper()
|
|
246
|
+
url = args["url"]
|
|
247
|
+
params = parse_params(args.get("params"))
|
|
248
|
+
headers = parse_headers(args.get("headers"))
|
|
249
|
+
body = args.get("body")
|
|
250
|
+
content_type = args.get("content_type", "application/json")
|
|
251
|
+
timeout = args.get("timeout", 30)
|
|
252
|
+
follow_redirects = args.get("follow_redirects", True)
|
|
253
|
+
verify_ssl = args.get("verify_ssl", True)
|
|
254
|
+
include_headers = args.get("include_headers", True)
|
|
255
|
+
|
|
256
|
+
# Set Content-Type
|
|
257
|
+
if body and "content-type" not in {k.lower() for k in headers}:
|
|
258
|
+
headers["Content-Type"] = content_type
|
|
259
|
+
|
|
260
|
+
async with httpx.AsyncClient(
|
|
261
|
+
timeout=timeout,
|
|
262
|
+
follow_redirects=follow_redirects,
|
|
263
|
+
verify=verify_ssl
|
|
264
|
+
) as client:
|
|
265
|
+
response = await client.request(
|
|
266
|
+
method=method,
|
|
267
|
+
url=url,
|
|
268
|
+
params=params,
|
|
269
|
+
headers=headers,
|
|
270
|
+
content=body.encode() if body else None
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
result = format_response(response, include_headers)
|
|
274
|
+
return [TextContent(type="text", text=result)]
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def parse_raw_request(raw: str, base_url: str | None = None) -> tuple[str, str, dict, str | None]:
|
|
278
|
+
"""Parse raw HTTP request text"""
|
|
279
|
+
lines = raw.strip().replace("\r\n", "\n").split("\n")
|
|
280
|
+
|
|
281
|
+
# Parse request line
|
|
282
|
+
request_line = lines[0]
|
|
283
|
+
parts = request_line.split()
|
|
284
|
+
method = parts[0].upper()
|
|
285
|
+
path = parts[1] if len(parts) > 1 else "/"
|
|
286
|
+
|
|
287
|
+
# Parse request headers
|
|
288
|
+
headers = {}
|
|
289
|
+
body_start = len(lines)
|
|
290
|
+
|
|
291
|
+
for i, line in enumerate(lines[1:], 1):
|
|
292
|
+
if line.strip() == "":
|
|
293
|
+
body_start = i + 1
|
|
294
|
+
break
|
|
295
|
+
if ":" in line:
|
|
296
|
+
key, value = line.split(":", 1)
|
|
297
|
+
headers[key.strip()] = value.strip()
|
|
298
|
+
|
|
299
|
+
# Parse request body
|
|
300
|
+
body = None
|
|
301
|
+
if body_start < len(lines):
|
|
302
|
+
body = "\n".join(lines[body_start:])
|
|
303
|
+
|
|
304
|
+
# Build full URL
|
|
305
|
+
if path.startswith("http://") or path.startswith("https://"):
|
|
306
|
+
url = path
|
|
307
|
+
else:
|
|
308
|
+
host = headers.get("Host", "")
|
|
309
|
+
if base_url:
|
|
310
|
+
url = base_url.rstrip("/") + path
|
|
311
|
+
elif host:
|
|
312
|
+
# Determine protocol based on SSL-related headers
|
|
313
|
+
protocol = "https" if "443" in host else "http"
|
|
314
|
+
url = f"{protocol}://{host}{path}"
|
|
315
|
+
else:
|
|
316
|
+
url = path
|
|
317
|
+
|
|
318
|
+
return method, url, headers, body
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
async def handle_http_raw(args: dict[str, Any]) -> list[TextContent]:
|
|
322
|
+
"""Handle raw HTTP request"""
|
|
323
|
+
raw_request = args["raw_request"]
|
|
324
|
+
base_url = args.get("base_url")
|
|
325
|
+
verify_ssl = args.get("verify_ssl", True)
|
|
326
|
+
|
|
327
|
+
method, url, headers, body = parse_raw_request(raw_request, base_url)
|
|
328
|
+
|
|
329
|
+
async with httpx.AsyncClient(
|
|
330
|
+
timeout=30,
|
|
331
|
+
follow_redirects=True,
|
|
332
|
+
verify=verify_ssl
|
|
333
|
+
) as client:
|
|
334
|
+
response = await client.request(
|
|
335
|
+
method=method,
|
|
336
|
+
url=url,
|
|
337
|
+
headers=headers,
|
|
338
|
+
content=body.encode() if body else None
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
result = format_response(response)
|
|
342
|
+
return [TextContent(type="text", text=result)]
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
async def run_server():
|
|
346
|
+
"""Run MCP server"""
|
|
347
|
+
async with stdio_server() as (read_stream, write_stream):
|
|
348
|
+
await server.run(
|
|
349
|
+
read_stream,
|
|
350
|
+
write_stream,
|
|
351
|
+
server.create_initialization_options()
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def main():
|
|
356
|
+
"""Main entry point"""
|
|
357
|
+
import asyncio
|
|
358
|
+
asyncio.run(run_server())
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
if __name__ == "__main__":
|
|
362
|
+
main()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "httpx-mcp"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "A MCP tool for HTTP request testing using httpx"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"mcp>=1.0.0",
|
|
9
|
+
"httpx>=0.27.0",
|
|
10
|
+
]
|
|
11
|
+
authors = [
|
|
12
|
+
{name = "ZHEFOX", email = "zhefox@outlook.com"},
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
httpx-mcp = "httpx_mcp.server:main"
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["hatchling"]
|
|
20
|
+
build-backend = "hatchling.build"
|
|
21
|
+
|
|
22
|
+
[tool.hatch.build.targets.wheel]
|
|
23
|
+
packages = ["httpx_mcp"]
|