mirror of
https://github.com/chaitin/SafeLine.git
synced 2026-01-31 13:53:33 +08:00
feat: mcp go
This commit is contained in:
@@ -1,28 +0,0 @@
|
||||
FROM golang:1.24-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache git
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN CGO_ENABLED=0 go build -o mcp-server .
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache ca-certificates tzdata
|
||||
|
||||
COPY --from=builder /app/mcp-server .
|
||||
COPY --from=builder /app/config.yaml .
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
EXPOSE 5678
|
||||
|
||||
CMD ["./mcp-server"]
|
||||
253
mcp_go/README.md
253
mcp_go/README.md
@@ -1,253 +0,0 @@
|
||||
# SafeLine MCP Server
|
||||
|
||||
SafeLine MCP Server is an implementation of the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) that provides complete management and control capabilities for SafeLine WAF.
|
||||
|
||||
[](docker-compose.yml)
|
||||
[](go.mod)
|
||||
|
||||
## Use Cases
|
||||
|
||||
- Automated management and control of SafeLine WAF instances
|
||||
- WAF configuration and policy management through API
|
||||
- Building AI-based security protection tools and applications
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Install [Docker](https://www.docker.com/) (if running in container)
|
||||
2. Configure SafeLine API Token (obtained from SafeLine console)
|
||||
|
||||
## Features
|
||||
|
||||
- Complete MCP (Management Control Protocol) server implementation
|
||||
- Support for SafeLine WAF instance management and control
|
||||
- Flexible configuration system supporting file configuration and environment variables
|
||||
- Docker containerization support
|
||||
- Secure API communication
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Environment Variable | Description | Default Value | Required |
|
||||
|---------|------|--------|-----|
|
||||
| LISTEN_PORT | Service listening port | 5678 | No |
|
||||
| LISTEN_ADDRESS | Service listening address | 0.0.0.0 | No |
|
||||
| SAFELINE_SECRET | SSE server secret | - | No |
|
||||
| SAFELINE_ADDRESS | SafeLine API address | - | Yes |
|
||||
| SAFELINE_API_TOKEN | SafeLine API authentication token | - | Yes |
|
||||
|
||||
### Using Docker
|
||||
|
||||
#### Method 1: Using docker run
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name safeline-mcp \
|
||||
-p 5678:5678 \
|
||||
-e SAFELINE_API_TOKEN="your_api_token" \
|
||||
-e SAFELINE_ADDRESS="https://your.safeline.com" \
|
||||
-e LISTEN_PORT=5678 \
|
||||
-e LISTEN_ADDRESS="0.0.0.0" \
|
||||
chaitin/safeline-mcp:latest
|
||||
```
|
||||
|
||||
#### Method 2: Using docker-compose
|
||||
|
||||
```bash
|
||||
# 1. Clone repository
|
||||
git clone https://github.com/chaitin/safeline-mcp.git
|
||||
cd safeline-mcp
|
||||
|
||||
# 2. Edit docker-compose.yml to configure environment variables
|
||||
# Example docker-compose.yml:
|
||||
# version: '3'
|
||||
# services:
|
||||
# mcp:
|
||||
# image: chaitin/safeline-mcp:latest
|
||||
# container_name: safeline-mcp
|
||||
# ports:
|
||||
# - "5678:5678"
|
||||
# environment:
|
||||
# - SAFELINE_API_TOKEN=your_api_token
|
||||
# - SAFELINE_ADDRESS=https://your.safeline.com
|
||||
# - LISTEN_PORT=5678
|
||||
# - LISTEN_ADDRESS=0.0.0.0
|
||||
|
||||
# 3. Start service
|
||||
docker compose -f docker-compose.yml up -d
|
||||
```
|
||||
|
||||
#### Method 3: Using Go
|
||||
|
||||
```bash
|
||||
# 1. Clone repository
|
||||
git clone https://github.com/chaitin/SafeLine.git
|
||||
cd safeline-mcp
|
||||
|
||||
# 2. Install dependencies
|
||||
go mod download
|
||||
|
||||
# 3. Configure config.yaml
|
||||
cp config.yaml.example config.yaml
|
||||
# Edit config.yaml with necessary configurations
|
||||
|
||||
# 4. Run service
|
||||
go run main.go
|
||||
```
|
||||
|
||||
## Tools
|
||||
|
||||
### Application Management
|
||||
|
||||
- **create_application**
|
||||
|
||||
### Rule Management
|
||||
- **create_blacklist_rule**
|
||||
- **create_whitelist_rule**
|
||||
|
||||
For more API details, please refer to the [API Documentation](https://master.safeline-ce.staging.dev.in.chaitin.net:9443/swagger/index.html#).
|
||||
|
||||
## Development Guide
|
||||
|
||||
The Go API in this project is currently under development, and APIs may change. If you have specific requirements, please submit an Issue for discussion.
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
internal/
|
||||
├── api/ # API implementation
|
||||
│ ├── app/ # Application-related APIs
|
||||
│ │ └── create_application.go
|
||||
│ └── rule/ # Rule-related APIs
|
||||
│ └── create_rule.go
|
||||
└── tools/ # MCP tool implementation
|
||||
├── app/ # Application-related tools
|
||||
│ └── create_application.go
|
||||
└── rule/ # Rule-related tools
|
||||
└── create_rule.go
|
||||
```
|
||||
|
||||
### Adding New Tools
|
||||
|
||||
1. **Create Tool File**
|
||||
- Create corresponding directory and file under `internal/tools`
|
||||
- File name should match tool name
|
||||
- Use separate file for each tool
|
||||
- Example: `internal/tools/app/create_application.go`
|
||||
|
||||
2. **Tool Implementation Template**
|
||||
```go
|
||||
package app
|
||||
|
||||
type ToolName struct{}
|
||||
|
||||
type ToolParams struct {
|
||||
// Parameter definitions
|
||||
Param1 string `json:"param1" desc:"parameter description" required:"true"`
|
||||
Param2 int `json:"param2" desc:"parameter description" required:"false"`
|
||||
}
|
||||
|
||||
type ToolResult struct {
|
||||
Field1 string `json:"field1"`
|
||||
}
|
||||
|
||||
func (t *ToolName) Name() string {
|
||||
return "tool_name"
|
||||
}
|
||||
|
||||
func (t *ToolName) Description() string {
|
||||
return "tool description"
|
||||
}
|
||||
|
||||
func (t *ToolName) Validate(params ToolParams) error {
|
||||
// Parameter validation logic
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *ToolName) Execute(ctx context.Context, params ToolParams) (result ToolResult, err error) {
|
||||
// Tool execution logic
|
||||
return result, nil
|
||||
}
|
||||
```
|
||||
|
||||
3. **[Optional]Create API Implementation**
|
||||
|
||||
If you need to use some APIs that have not been implemented yet, you need to create corresponding files in the api directory for implementation
|
||||
- Create same directory structure under `internal/api`
|
||||
- File name should match tool func
|
||||
- Example: `internal/api/app/create_application.go`
|
||||
|
||||
**API Implementation Template**
|
||||
```go
|
||||
package app
|
||||
|
||||
type RequestType struct {
|
||||
// Request parameter definitions
|
||||
Param1 string `json:"param1"`
|
||||
Param2 int `json:"param2"`
|
||||
}
|
||||
|
||||
func APIName(ctx context.Context, req *RequestType) (ResultType, error) {
|
||||
if req == nil {
|
||||
return nil, errors.New("request is required")
|
||||
}
|
||||
|
||||
var resp api.Response[ResultType]
|
||||
err := api.Service().Post(ctx, "/api/path", req, &resp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to execute")
|
||||
}
|
||||
|
||||
if resp.Err != nil {
|
||||
return nil, errors.New(resp.Msg)
|
||||
}
|
||||
|
||||
return resp.Data, nil
|
||||
}
|
||||
```
|
||||
4. **Tool Registration (init.go)**
|
||||
|
||||
The tool registration file `internal/tools/init.go` is used to centrally manage all tool registrations
|
||||
- Register all tools uniformly in the `init()` function
|
||||
- Use the `AppendTool()` method for registration
|
||||
- Example:
|
||||
```go
|
||||
// Register create application tool
|
||||
AppendTool(&app.CreateApp{})
|
||||
|
||||
// Register create blacklist rule tool
|
||||
AppendTool(&rule.CreateBlacklistRule{})
|
||||
```
|
||||
|
||||
### Development Standards
|
||||
|
||||
1. **Naming Conventions**
|
||||
- Use lowercase letters and underscores for tool names
|
||||
- File names should match tool names
|
||||
|
||||
2. **Directory Organization**
|
||||
- Divide directories by functional modules (e.g., app, rule, etc.)
|
||||
- Maintain consistent structure between tools and api directories
|
||||
- Keep related functionality in the same directory
|
||||
|
||||
3. **Code Standards**
|
||||
- Follow Go standard code conventions
|
||||
- Add necessary parameter validation
|
||||
- Use unified error handling approach
|
||||
- Add appropriate logging
|
||||
|
||||
4. **Documentation Requirements**
|
||||
- Provide clear functional description in tool Description
|
||||
- Add detailed description for parameters
|
||||
- Update API toolkit documentation in README
|
||||
|
||||
### Example
|
||||
|
||||
Refer to the implementation of the `create_application` tool:
|
||||
- Tool implementation: `internal/tools/app/create_application.go`
|
||||
- API implementation: `internal/api/app/create_application.go`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
3.13
|
||||
@@ -1,11 +1,28 @@
|
||||
FROM ghcr.io/astral-sh/uv:python3.13-alpine
|
||||
|
||||
COPY requirements.txt /app/requirements.txt
|
||||
FROM golang:1.24-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN uv pip sync requirements.txt --system -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
RUN apk add --no-cache git
|
||||
|
||||
COPY . /app
|
||||
COPY go.mod go.sum ./
|
||||
|
||||
CMD ["python", "__main__.py"]
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN CGO_ENABLED=0 go build -o mcp-server .
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache ca-certificates tzdata
|
||||
|
||||
COPY --from=builder /app/mcp-server .
|
||||
COPY --from=builder /app/config.yaml .
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
EXPOSE 5678
|
||||
|
||||
CMD ["./mcp-server"]
|
||||
@@ -1,48 +1,253 @@
|
||||
## mcp_server - A SafeLine WAF mcp server
|
||||
# SafeLine MCP Server
|
||||
|
||||
- Easy to use
|
||||
- one command to run mcp_server
|
||||
- Easy to develop
|
||||
- add yoor own tools to `tools` dirctory without modify other files
|
||||
SafeLine MCP Server is an implementation of the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) that provides complete management and control capabilities for SafeLine WAF.
|
||||
|
||||
### quick start
|
||||
[](docker-compose.yml)
|
||||
[](go.mod)
|
||||
|
||||
```shell
|
||||
docker compose -f docker-compose.yaml up -d
|
||||
## Use Cases
|
||||
|
||||
- Automated management and control of SafeLine WAF instances
|
||||
- WAF configuration and policy management through API
|
||||
- Building AI-based security protection tools and applications
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Install [Docker](https://www.docker.com/) (if running in container)
|
||||
2. Configure SafeLine API Token (obtained from SafeLine console)
|
||||
|
||||
## Features
|
||||
|
||||
- Complete MCP (Management Control Protocol) server implementation
|
||||
- Support for SafeLine WAF instance management and control
|
||||
- Flexible configuration system supporting file configuration and environment variables
|
||||
- Docker containerization support
|
||||
- Secure API communication
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Environment Variable | Description | Default Value | Required |
|
||||
|---------|------|--------|-----|
|
||||
| LISTEN_PORT | Service listening port | 5678 | No |
|
||||
| LISTEN_ADDRESS | Service listening address | 0.0.0.0 | No |
|
||||
| SAFELINE_SECRET | SSE server secret | - | No |
|
||||
| SAFELINE_ADDRESS | SafeLine API address | - | Yes |
|
||||
| SAFELINE_API_TOKEN | SafeLine API authentication token | - | Yes |
|
||||
|
||||
### Using Docker
|
||||
|
||||
#### Method 1: Using docker run
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name safeline-mcp \
|
||||
-p 5678:5678 \
|
||||
-e SAFELINE_API_TOKEN="your_api_token" \
|
||||
-e SAFELINE_ADDRESS="https://your.safeline.com" \
|
||||
-e LISTEN_PORT=5678 \
|
||||
-e LISTEN_ADDRESS="0.0.0.0" \
|
||||
chaitin/safeline-mcp:latest
|
||||
```
|
||||
|
||||
### custom your own tool
|
||||
#### Method 2: Using docker-compose
|
||||
|
||||
#### Hello Tool Example
|
||||
```bash
|
||||
# 1. Clone repository
|
||||
git clone https://github.com/chaitin/safeline-mcp.git
|
||||
cd safeline-mcp
|
||||
|
||||
This tool used to say hello to someone
|
||||
# 2. Edit docker-compose.yml to configure environment variables
|
||||
# Example docker-compose.yml:
|
||||
# version: '3'
|
||||
# services:
|
||||
# mcp:
|
||||
# image: chaitin/safeline-mcp:latest
|
||||
# container_name: safeline-mcp
|
||||
# ports:
|
||||
# - "5678:5678"
|
||||
# environment:
|
||||
# - SAFELINE_API_TOKEN=your_api_token
|
||||
# - SAFELINE_ADDRESS=https://your.safeline.com
|
||||
# - LISTEN_PORT=5678
|
||||
# - LISTEN_ADDRESS=0.0.0.0
|
||||
|
||||
1. create file `tools/hello.py`
|
||||
# 3. Start service
|
||||
docker compose -f docker-compose.yml up -d
|
||||
```
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, Field
|
||||
from tools import Tool, ABCTool, tools
|
||||
#### Method 3: Using Go
|
||||
|
||||
# register to global tools
|
||||
@tools.register
|
||||
# Hello describe function paramters
|
||||
class Hello(BaseModel, ABCTool):
|
||||
# tools paramters
|
||||
name: str = Field(description="username to say hello")
|
||||
```bash
|
||||
# 1. Clone repository
|
||||
git clone https://github.com/chaitin/SafeLine.git
|
||||
cd safeline-mcp
|
||||
|
||||
# run is tool logic, must use classmethod
|
||||
@classmethod
|
||||
async def run(arguments: dict) -> str:
|
||||
req = Hello.model_validate(arguments)
|
||||
return f"Hello {req.name}"
|
||||
# 2. Install dependencies
|
||||
go mod download
|
||||
|
||||
# tool description, must use classmethod
|
||||
@classmethod
|
||||
def tool(self) -> Tool:
|
||||
return Tool(
|
||||
name="hello",
|
||||
description="say hello to someone",
|
||||
inputSchema=self.model_json_schema()
|
||||
)
|
||||
# 3. Configure config.yaml
|
||||
cp config.yaml.example config.yaml
|
||||
# Edit config.yaml with necessary configurations
|
||||
|
||||
# 4. Run service
|
||||
go run main.go
|
||||
```
|
||||
|
||||
## Tools
|
||||
|
||||
### Application Management
|
||||
|
||||
- **create_application**
|
||||
|
||||
### Rule Management
|
||||
- **create_blacklist_rule**
|
||||
- **create_whitelist_rule**
|
||||
|
||||
For more API details, please refer to the [API Documentation](https://master.safeline-ce.staging.dev.in.chaitin.net:9443/swagger/index.html#).
|
||||
|
||||
## Development Guide
|
||||
|
||||
The Go API in this project is currently under development, and APIs may change. If you have specific requirements, please submit an Issue for discussion.
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
internal/
|
||||
├── api/ # API implementation
|
||||
│ ├── app/ # Application-related APIs
|
||||
│ │ └── create_application.go
|
||||
│ └── rule/ # Rule-related APIs
|
||||
│ └── create_rule.go
|
||||
└── tools/ # MCP tool implementation
|
||||
├── app/ # Application-related tools
|
||||
│ └── create_application.go
|
||||
└── rule/ # Rule-related tools
|
||||
└── create_rule.go
|
||||
```
|
||||
|
||||
### Adding New Tools
|
||||
|
||||
1. **Create Tool File**
|
||||
- Create corresponding directory and file under `internal/tools`
|
||||
- File name should match tool name
|
||||
- Use separate file for each tool
|
||||
- Example: `internal/tools/app/create_application.go`
|
||||
|
||||
2. **Tool Implementation Template**
|
||||
```go
|
||||
package app
|
||||
|
||||
type ToolName struct{}
|
||||
|
||||
type ToolParams struct {
|
||||
// Parameter definitions
|
||||
Param1 string `json:"param1" desc:"parameter description" required:"true"`
|
||||
Param2 int `json:"param2" desc:"parameter description" required:"false"`
|
||||
}
|
||||
|
||||
type ToolResult struct {
|
||||
Field1 string `json:"field1"`
|
||||
}
|
||||
|
||||
func (t *ToolName) Name() string {
|
||||
return "tool_name"
|
||||
}
|
||||
|
||||
func (t *ToolName) Description() string {
|
||||
return "tool description"
|
||||
}
|
||||
|
||||
func (t *ToolName) Validate(params ToolParams) error {
|
||||
// Parameter validation logic
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *ToolName) Execute(ctx context.Context, params ToolParams) (result ToolResult, err error) {
|
||||
// Tool execution logic
|
||||
return result, nil
|
||||
}
|
||||
```
|
||||
|
||||
3. **[Optional]Create API Implementation**
|
||||
|
||||
If you need to use some APIs that have not been implemented yet, you need to create corresponding files in the api directory for implementation
|
||||
- Create same directory structure under `internal/api`
|
||||
- File name should match tool func
|
||||
- Example: `internal/api/app/create_application.go`
|
||||
|
||||
**API Implementation Template**
|
||||
```go
|
||||
package app
|
||||
|
||||
type RequestType struct {
|
||||
// Request parameter definitions
|
||||
Param1 string `json:"param1"`
|
||||
Param2 int `json:"param2"`
|
||||
}
|
||||
|
||||
func APIName(ctx context.Context, req *RequestType) (ResultType, error) {
|
||||
if req == nil {
|
||||
return nil, errors.New("request is required")
|
||||
}
|
||||
|
||||
var resp api.Response[ResultType]
|
||||
err := api.Service().Post(ctx, "/api/path", req, &resp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to execute")
|
||||
}
|
||||
|
||||
if resp.Err != nil {
|
||||
return nil, errors.New(resp.Msg)
|
||||
}
|
||||
|
||||
return resp.Data, nil
|
||||
}
|
||||
```
|
||||
4. **Tool Registration (init.go)**
|
||||
|
||||
The tool registration file `internal/tools/init.go` is used to centrally manage all tool registrations
|
||||
- Register all tools uniformly in the `init()` function
|
||||
- Use the `AppendTool()` method for registration
|
||||
- Example:
|
||||
```go
|
||||
// Register create application tool
|
||||
AppendTool(&app.CreateApp{})
|
||||
|
||||
// Register create blacklist rule tool
|
||||
AppendTool(&rule.CreateBlacklistRule{})
|
||||
```
|
||||
|
||||
### Development Standards
|
||||
|
||||
1. **Naming Conventions**
|
||||
- Use lowercase letters and underscores for tool names
|
||||
- File names should match tool names
|
||||
|
||||
2. **Directory Organization**
|
||||
- Divide directories by functional modules (e.g., app, rule, etc.)
|
||||
- Maintain consistent structure between tools and api directories
|
||||
- Keep related functionality in the same directory
|
||||
|
||||
3. **Code Standards**
|
||||
- Follow Go standard code conventions
|
||||
- Add necessary parameter validation
|
||||
- Use unified error handling approach
|
||||
- Add appropriate logging
|
||||
|
||||
4. **Documentation Requirements**
|
||||
- Provide clear functional description in tool Description
|
||||
- Add detailed description for parameters
|
||||
- Update API toolkit documentation in README
|
||||
|
||||
### Example
|
||||
|
||||
Refer to the implementation of the `create_application` tool:
|
||||
- Tool implementation: `internal/tools/app/create_application.go`
|
||||
- API implementation: `internal/api/app/create_application.go`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
from .config import Config
|
||||
|
||||
GLOBAL_CONFIG = Config.from_env()
|
||||
@@ -1,4 +0,0 @@
|
||||
from server import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,3 +0,0 @@
|
||||
from .config import Config
|
||||
|
||||
GLOBAL_CONFIG = Config.from_env()
|
||||
@@ -1,56 +0,0 @@
|
||||
import os
|
||||
import logging
|
||||
|
||||
class Config:
|
||||
SAFELINE_ADDRESS: str
|
||||
SAFELINE_API_TOKEN: str
|
||||
SECRET: str
|
||||
LISTEN_PORT: int
|
||||
LISTEN_ADDRESS: str
|
||||
DEBUG: bool
|
||||
|
||||
def __init__(self):
|
||||
set_log_level()
|
||||
|
||||
if os.getenv("MCP_SERVER_DEBUG"):
|
||||
self.DEBUG = True
|
||||
else:
|
||||
self.DEBUG = False
|
||||
self.SAFELINE_ADDRESS = os.getenv("SAFELINE_ADDRESS")
|
||||
if self.SAFELINE_ADDRESS:
|
||||
self.SAFELINE_ADDRESS = self.SAFELINE_ADDRESS.removesuffix("/")
|
||||
self.SAFELINE_API_TOKEN = os.getenv("SAFELINE_API_TOKEN")
|
||||
self.SECRET = os.getenv("SAFELINE_SECRET")
|
||||
env_listen_port = os.getenv("LISTEN_PORT")
|
||||
if env_listen_port and env_listen_port.isdigit():
|
||||
self.LISTEN_PORT = int(env_listen_port)
|
||||
else:
|
||||
self.LISTEN_PORT = 5678
|
||||
env_listen_address = os.getenv("LISTEN_ADDRESS")
|
||||
if env_listen_address:
|
||||
self.LISTEN_ADDRESS = env_listen_address
|
||||
else:
|
||||
self.LISTEN_ADDRESS = "0.0.0.0"
|
||||
|
||||
@staticmethod
|
||||
def from_env():
|
||||
return Config()
|
||||
|
||||
|
||||
def set_log_level():
|
||||
level = logging.WARN
|
||||
log_level = os.getenv("MCO_SERVER_LOG_LEVEL")
|
||||
if log_level:
|
||||
match log_level.lower():
|
||||
case "debug":
|
||||
level = logging.DEBUG
|
||||
case "info":
|
||||
level = logging.INFO
|
||||
case "warn":
|
||||
level = logging.WARN
|
||||
case "error":
|
||||
level = logging.ERROR
|
||||
case "critical":
|
||||
level = logging.CRITICAL
|
||||
|
||||
logging.basicConfig(level=level, format="%(asctime)s - %(levelname)s - %(message)s")
|
||||
@@ -1,12 +0,0 @@
|
||||
services:
|
||||
mcp_server:
|
||||
image: chaitin/safeline-mcp:latest
|
||||
container_name: mcp_server
|
||||
ports:
|
||||
- "5678:5678"
|
||||
environment:
|
||||
- SAFELINE_SECRET=your_secret_key # optional, if you want to use secret key to authenticate
|
||||
- SAFELINE_ADDRESS=https://your_safeline_ip:9443 # required, your SafeLine WAF address
|
||||
- SAFELINE_API_TOKEN=your_safeline_api_token # required, your SafeLine WAF api token
|
||||
- LISTEN_PORT=5678 # optional, default is 5678
|
||||
- LISTEN_ADDRESS=0.0.0.0 # optional, default is 0.0.0.0
|
||||
@@ -1,21 +0,0 @@
|
||||
from starlette.requests import HTTPConnection
|
||||
from starlette.responses import PlainTextResponse
|
||||
from starlette.types import ASGIApp, Receive, Scope, Send
|
||||
from config import GLOBAL_CONFIG
|
||||
|
||||
|
||||
class AuthenticationMiddleware:
|
||||
def __init__(
|
||||
self,
|
||||
app: ASGIApp,
|
||||
) -> None:
|
||||
self.app = app
|
||||
|
||||
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
||||
conn = HTTPConnection(scope)
|
||||
if GLOBAL_CONFIG.SECRET and GLOBAL_CONFIG.SECRET != "" and conn.headers.get("Secret") != GLOBAL_CONFIG.SECRET:
|
||||
response = PlainTextResponse("Unauthorized", status_code=401)
|
||||
await response(scope, receive, send)
|
||||
return
|
||||
|
||||
await self.app(scope, receive, send)
|
||||
@@ -1,9 +0,0 @@
|
||||
[project]
|
||||
name = "mcp_server"
|
||||
version = "0.1.0"
|
||||
description = "SafeLine WAF mcp server"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"mcp[cli]>=1.6.0",
|
||||
]
|
||||
@@ -1,76 +0,0 @@
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile pyproject.toml -o requirements.txt
|
||||
annotated-types==0.7.0
|
||||
# via pydantic
|
||||
anyio==4.9.0
|
||||
# via
|
||||
# httpx
|
||||
# mcp
|
||||
# sse-starlette
|
||||
# starlette
|
||||
certifi==2025.1.31
|
||||
# via
|
||||
# httpcore
|
||||
# httpx
|
||||
click==8.1.8
|
||||
# via
|
||||
# typer
|
||||
# uvicorn
|
||||
h11==0.14.0
|
||||
# via
|
||||
# httpcore
|
||||
# uvicorn
|
||||
httpcore==1.0.7
|
||||
# via httpx
|
||||
httpx==0.28.1
|
||||
# via mcp
|
||||
httpx-sse==0.4.0
|
||||
# via mcp
|
||||
idna==3.10
|
||||
# via
|
||||
# anyio
|
||||
# httpx
|
||||
markdown-it-py==3.0.0
|
||||
# via rich
|
||||
mcp==1.6.0
|
||||
# via mcp_server (pyproject.toml)
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
pydantic==2.11.1
|
||||
# via
|
||||
# mcp
|
||||
# pydantic-settings
|
||||
pydantic-core==2.33.0
|
||||
# via pydantic
|
||||
pydantic-settings==2.8.1
|
||||
# via mcp
|
||||
pygments==2.19.1
|
||||
# via rich
|
||||
python-dotenv==1.1.0
|
||||
# via
|
||||
# mcp
|
||||
# pydantic-settings
|
||||
rich==14.0.0
|
||||
# via typer
|
||||
shellingham==1.5.4
|
||||
# via typer
|
||||
sniffio==1.3.1
|
||||
# via anyio
|
||||
sse-starlette==2.2.1
|
||||
# via mcp
|
||||
starlette==0.46.1
|
||||
# via
|
||||
# mcp
|
||||
# sse-starlette
|
||||
typer==0.15.2
|
||||
# via mcp
|
||||
typing-extensions==4.13.0
|
||||
# via
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
# typer
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.0
|
||||
# via pydantic
|
||||
uvicorn==0.34.0
|
||||
# via mcp
|
||||
@@ -1,46 +0,0 @@
|
||||
from mcp.server import Server
|
||||
from mcp.types import Tool, TextContent
|
||||
import mcp.server.sse
|
||||
from starlette.applications import Starlette
|
||||
from starlette.routing import Route, Mount
|
||||
from starlette.requests import Request
|
||||
import uvicorn
|
||||
from tools import tools
|
||||
from config import GLOBAL_CONFIG
|
||||
from middleware import AuthenticationMiddleware
|
||||
from starlette.middleware import Middleware
|
||||
|
||||
# Create an MCP server
|
||||
mcp_server = Server("SafeLine WAF mcp server")
|
||||
sse = mcp.server.sse.SseServerTransport("/messages/")
|
||||
|
||||
@mcp_server.list_tools()
|
||||
async def list_tools() -> list[Tool]:
|
||||
return tools.all()
|
||||
|
||||
@mcp_server.call_tool()
|
||||
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
|
||||
result = await tools.run(name, arguments)
|
||||
|
||||
return [TextContent(
|
||||
type="text",
|
||||
text=f"{name} result: {result}"
|
||||
)]
|
||||
|
||||
|
||||
|
||||
async def handle_sse(request: Request) -> None:
|
||||
async with sse.connect_sse(
|
||||
request.scope, request.receive, request._send
|
||||
) as [read_stream, write_stream]:
|
||||
await mcp_server.run(
|
||||
read_stream, write_stream, mcp_server.create_initialization_options()
|
||||
)
|
||||
|
||||
def main():
|
||||
starlette_app = Starlette(debug=GLOBAL_CONFIG.DEBUG,routes=[
|
||||
Route("/sse", endpoint=handle_sse),
|
||||
Mount("/messages/", app=sse.handle_post_message, middleware=[Middleware(AuthenticationMiddleware)]),
|
||||
])
|
||||
|
||||
uvicorn.run(starlette_app, host=GLOBAL_CONFIG.LISTEN_ADDRESS, port=GLOBAL_CONFIG.LISTEN_PORT)
|
||||
@@ -1,49 +0,0 @@
|
||||
from mcp.types import Tool
|
||||
from abc import ABC, abstractmethod
|
||||
import os
|
||||
import importlib
|
||||
import logging
|
||||
|
||||
class ABCTool(ABC):
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
async def run(self, arguments:dict) -> str:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def tool(self) -> Tool:
|
||||
pass
|
||||
|
||||
class ToolRegister:
|
||||
_dict: dict[str, ABCTool] = {}
|
||||
|
||||
def register(self, tool: ABCTool) -> ABCTool:
|
||||
tool_name = tool.tool().name
|
||||
logging.info(f"Registering tool: {tool_name}")
|
||||
if tool_name in self._dict:
|
||||
raise ValueError(f"Tool {tool_name} already registered")
|
||||
|
||||
self._dict[tool_name] = tool
|
||||
return tool
|
||||
|
||||
def all(self) -> list[Tool]:
|
||||
return [tool.tool() for tool in self._dict.values()]
|
||||
|
||||
async def run(self, name: str, arguments: dict) -> str:
|
||||
if name not in self._dict:
|
||||
raise ValueError(f"Unknown tool: {name}")
|
||||
|
||||
return await self._dict[name].run(arguments)
|
||||
|
||||
def import_all_tools():
|
||||
for module in os.listdir(os.path.dirname(__file__)):
|
||||
if module == "__init__.py" or len(module) < 3 or not module.endswith(".py"):
|
||||
continue
|
||||
|
||||
module_name = module[:-3]
|
||||
importlib.import_module(f".{module_name}", package=__name__)
|
||||
|
||||
tools = ToolRegister()
|
||||
|
||||
import_all_tools()
|
||||
@@ -1,43 +0,0 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from utils.request import post_slce_api
|
||||
from tools import Tool, ABCTool, tools
|
||||
from urllib.parse import urlparse
|
||||
|
||||
@tools.register
|
||||
class CreateHttpApplication(BaseModel, ABCTool):
|
||||
domain: str = Field(default="",description="application domain, if empty, match all domain")
|
||||
port: int = Field(min=1, max=65535,description="application listen port, must between 1 and 65535")
|
||||
upstream: str = Field(description="application proxy address, must be a valid url")
|
||||
|
||||
@classmethod
|
||||
async def run(self, arguments:dict) -> str:
|
||||
try:
|
||||
req = CreateHttpApplication.model_validate(arguments)
|
||||
parsed_upstream = urlparse(req.upstream)
|
||||
if parsed_upstream.scheme != "https" and parsed_upstream.scheme != "http":
|
||||
return "invalid upstream scheme"
|
||||
|
||||
if parsed_upstream.hostname == "":
|
||||
return "invalid upstream host"
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
return await post_slce_api("/api/open/site",{
|
||||
"server_names": [req.domain],
|
||||
"ports": [ str(req.port) ],
|
||||
"upstreams": [ req.upstream ],
|
||||
"type": 0,
|
||||
"static_default": 1,
|
||||
"health_check": True,
|
||||
"load_balance": {
|
||||
"balance_type": 1
|
||||
}
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def tool(self) -> Tool:
|
||||
return Tool(
|
||||
name="waf_create_http_application",
|
||||
description="在雷池 WAF 上创建一个站点应用",
|
||||
inputSchema=self.model_json_schema()
|
||||
)
|
||||
@@ -1,55 +0,0 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from utils.request import post_slce_api
|
||||
from tools import Tool, ABCTool, tools
|
||||
import ipaddress
|
||||
|
||||
@tools.register
|
||||
class CreateIPCustomRule(BaseModel, ABCTool):
|
||||
ip: str = Field(description="request ip to allow or block")
|
||||
action: int = Field(min=0, max=1,description="1: block, 0: allow")
|
||||
|
||||
@classmethod
|
||||
async def run(self, arguments:dict) -> str:
|
||||
try:
|
||||
req = CreateIPCustomRule.model_validate(arguments)
|
||||
ipaddress.ip_address(req.ip)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
name = ""
|
||||
match req.action:
|
||||
case 0:
|
||||
name += "allow "
|
||||
case 1:
|
||||
name += "block "
|
||||
case _:
|
||||
return "invalid action"
|
||||
|
||||
if not req.ip or req.ip == "":
|
||||
return "ip is required"
|
||||
|
||||
name += f"ip: {req.ip}"
|
||||
|
||||
return await post_slce_api("/api/open/policy",{
|
||||
"name": name,
|
||||
"is_enabled": True,
|
||||
"pattern": [
|
||||
[
|
||||
{
|
||||
"k": "src_ip",
|
||||
"op": "eq",
|
||||
"v": [req.ip],
|
||||
"sub_k": ""
|
||||
},
|
||||
]
|
||||
],
|
||||
"action": req.action
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def tool(self) -> Tool:
|
||||
return Tool(
|
||||
name="waf_create_ip_custom_rule",
|
||||
description="以 客户端 IP 地址为条件,在雷池 WAF 上创建一个黑/白名单",
|
||||
inputSchema=self.model_json_schema()
|
||||
)
|
||||
@@ -1,53 +0,0 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from utils.request import post_slce_api
|
||||
from tools import Tool, ABCTool, tools
|
||||
|
||||
@tools.register
|
||||
class CreatePathCustomRule(BaseModel, ABCTool):
|
||||
path: str = Field(description="request path to block or allow")
|
||||
action: int = Field(min=0, max=1,description="1: block, 0: allow")
|
||||
|
||||
@classmethod
|
||||
async def run(self, arguments:dict) -> str:
|
||||
try:
|
||||
req = CreatePathCustomRule.model_validate(arguments)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
name = ""
|
||||
match req.action:
|
||||
case 0:
|
||||
name += "allow "
|
||||
case 1:
|
||||
name += "block "
|
||||
case _:
|
||||
return "invalid action"
|
||||
|
||||
if not req.path or req.path == "":
|
||||
return "path is required"
|
||||
|
||||
name += f"path: {req.path}"
|
||||
|
||||
return await post_slce_api("/api/open/policy",{
|
||||
"name": name,
|
||||
"is_enabled": True,
|
||||
"pattern": [
|
||||
[
|
||||
{
|
||||
"k": "uri_no_query",
|
||||
"op": "eq",
|
||||
"v": [req.path],
|
||||
"sub_k": ""
|
||||
},
|
||||
]
|
||||
],
|
||||
"action": req.action
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def tool(self) -> Tool:
|
||||
return Tool(
|
||||
name="waf_create_path_custom_rule",
|
||||
description="以 URL Path 为条件,在雷池 WAF 上创建一个黑/白名单",
|
||||
inputSchema=self.model_json_schema()
|
||||
)
|
||||
@@ -1,29 +0,0 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from utils.request import get_slce_api
|
||||
from tools import Tool, ABCTool, tools
|
||||
|
||||
@tools.register
|
||||
class GetAttackEvents(BaseModel, ABCTool):
|
||||
ip: str = Field(default="", description="the attacker's client IP address")
|
||||
size: int = Field(default=10, min=1, max=100, description="the number of results to return")
|
||||
start: str = Field(default="", description="start time, millisecond timestamp")
|
||||
end: str = Field(default="", description="end time, millisecond timestamp")
|
||||
|
||||
@classmethod
|
||||
async def run(self, arguments:dict) -> str:
|
||||
try:
|
||||
req = GetAttackEvents.model_validate(arguments)
|
||||
if req.size < 1 or req.size > 100:
|
||||
return "size must be between 1 and 100"
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
return await get_slce_api(f"api/open/events?page=1&page_size={req.size}&ip={req.ip}&start={req.start}&end={req.end}")
|
||||
|
||||
@classmethod
|
||||
def tool(self) -> Tool:
|
||||
return Tool(
|
||||
name="waf_get_attack_events",
|
||||
description="获取雷池 WAF 所记录的攻击事件",
|
||||
inputSchema=self.model_json_schema()
|
||||
)
|
||||
@@ -1,62 +0,0 @@
|
||||
from httpx import AsyncClient
|
||||
from config import GLOBAL_CONFIG
|
||||
import httpx
|
||||
import json
|
||||
|
||||
def get_response_data(response: httpx.Response) -> dict:
|
||||
if response.status_code != 200:
|
||||
raise Exception(f"response status code: {response.status_code}")
|
||||
|
||||
data = response.json()
|
||||
if data["msg"] is not None and data["msg"] != "":
|
||||
raise Exception(f"request SafeLine API failed: {data['msg']}")
|
||||
|
||||
if data["err"] is not None and data["err"] != "":
|
||||
raise Exception(f"request SafeLine API failed: {data['err']}")
|
||||
|
||||
return data['data']
|
||||
|
||||
def check_slce_response(response: httpx.Response) -> str:
|
||||
try:
|
||||
get_response_data(response)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
return "success"
|
||||
|
||||
|
||||
def check_slce_get_response(response: httpx.Response) -> str:
|
||||
try:
|
||||
data = get_response_data(response)
|
||||
if data:
|
||||
return json.dumps(data)
|
||||
return "empty response data"
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
async def get_slce_api(path: str) -> str:
|
||||
if not path.startswith("/"):
|
||||
path = f"/{path}"
|
||||
|
||||
try:
|
||||
async with AsyncClient(verify=False) as client:
|
||||
response = await client.get(f"{GLOBAL_CONFIG.SAFELINE_ADDRESS}{path}", headers={
|
||||
"X-SLCE-API-TOKEN": f"{GLOBAL_CONFIG.SAFELINE_API_TOKEN}"
|
||||
})
|
||||
return check_slce_get_response(response)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
|
||||
async def post_slce_api(path: str, req_body: dict) -> str:
|
||||
if not path.startswith("/"):
|
||||
path = f"/{path}"
|
||||
|
||||
try:
|
||||
async with AsyncClient(verify=False) as client:
|
||||
response = await client.post(f"{GLOBAL_CONFIG.SAFELINE_ADDRESS}{path}", json=req_body, headers={
|
||||
"X-SLCE-API-TOKEN": f"{GLOBAL_CONFIG.SAFELINE_API_TOKEN}"
|
||||
})
|
||||
return check_slce_response(response)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
346
mcp_server/uv.lock
generated
346
mcp_server/uv.lock
generated
@@ -1,346 +0,0 @@
|
||||
version = 1
|
||||
revision = 1
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.7.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.9.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "idna" },
|
||||
{ name = "sniffio" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.1.31"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.8"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.14.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.7"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.28.1"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "certifi" },
|
||||
{ name = "httpcore" },
|
||||
{ name = "idna" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx-sse"
|
||||
version = "0.4.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.10"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markdown-it-py"
|
||||
version = "3.0.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "mdurl" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mcp"
|
||||
version = "1.6.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "httpx" },
|
||||
{ name = "httpx-sse" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "sse-starlette" },
|
||||
{ name = "starlette" },
|
||||
{ name = "uvicorn" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/d2/f587cb965a56e992634bebc8611c5b579af912b74e04eb9164bd49527d21/mcp-1.6.0.tar.gz", hash = "sha256:d9324876de2c5637369f43161cd71eebfd803df5a95e46225cab8d280e366723", size = 200031 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/10/30/20a7f33b0b884a9d14dd3aa94ff1ac9da1479fe2ad66dd9e2736075d2506/mcp-1.6.0-py3-none-any.whl", hash = "sha256:7bd24c6ea042dbec44c754f100984d186620d8b841ec30f1b19eda9b93a634d0", size = 76077 },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
cli = [
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "typer" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mcp-server"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "mcp", extra = ["cli"] },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "mcp", extras = ["cli"], specifier = ">=1.6.0" }]
|
||||
|
||||
[[package]]
|
||||
name = "mdurl"
|
||||
version = "0.1.2"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.11.1"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
{ name = "pydantic-core" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/93/a3/698b87a4d4d303d7c5f62ea5fbf7a79cab236ccfbd0a17847b7f77f8163e/pydantic-2.11.1.tar.gz", hash = "sha256:442557d2910e75c991c39f4b4ab18963d57b9b55122c8b2a9cd176d8c29ce968", size = 782817 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/12/f9221a949f2419e2e23847303c002476c26fbcfd62dc7f3d25d0bec5ca99/pydantic-2.11.1-py3-none-any.whl", hash = "sha256:5b6c415eee9f8123a14d859be0c84363fec6b1feb6b688d6435801230b56e0b8", size = 442648 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.33.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/05/91ce14dfd5a3a99555fce436318cc0fd1f08c4daa32b3248ad63669ea8b4/pydantic_core-2.33.0.tar.gz", hash = "sha256:40eb8af662ba409c3cbf4a8150ad32ae73514cd7cb1f1a2113af39763dd616b3", size = 434080 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/20/de2ad03ce8f5b3accf2196ea9b44f31b0cd16ac6e8cfc6b21976ed45ec35/pydantic_core-2.33.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f00e8b59e1fc8f09d05594aa7d2b726f1b277ca6155fc84c0396db1b373c4555", size = 2032214 },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/af/6817dfda9aac4958d8b516cbb94af507eb171c997ea66453d4d162ae8948/pydantic_core-2.33.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a73be93ecef45786d7d95b0c5e9b294faf35629d03d5b145b09b81258c7cd6d", size = 1852338 },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/f3/49193a312d9c49314f2b953fb55740b7c530710977cabe7183b8ef111b7f/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff48a55be9da6930254565ff5238d71d5e9cd8c5487a191cb85df3bdb8c77365", size = 1896913 },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/e0/c746677825b2e29a2fa02122a8991c83cdd5b4c5f638f0664d4e35edd4b2/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a4ea04195638dcd8c53dadb545d70badba51735b1594810e9768c2c0b4a5da", size = 1986046 },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/ec/44914e7ff78cef16afb5e5273d480c136725acd73d894affdbe2a1bbaad5/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41d698dcbe12b60661f0632b543dbb119e6ba088103b364ff65e951610cb7ce0", size = 2128097 },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/fe/f5/c6247d424d01f605ed2e3802f338691cae17137cee6484dce9f1ac0b872b/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae62032ef513fe6281ef0009e30838a01057b832dc265da32c10469622613885", size = 2681062 },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/85/114a2113b126fdd7cf9a9443b1b1fe1b572e5bd259d50ba9d5d3e1927fa9/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f225f3a3995dbbc26affc191d0443c6c4aa71b83358fd4c2b7d63e2f6f0336f9", size = 2007487 },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e6/40/3c05ed28d225c7a9acd2b34c5c8010c279683a870219b97e9f164a5a8af0/pydantic_core-2.33.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5bdd36b362f419c78d09630cbaebc64913f66f62bda6d42d5fbb08da8cc4f181", size = 2121382 },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/22/e70c086f41eebd323e6baa92cc906c3f38ddce7486007eb2bdb3b11c8f64/pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2a0147c0bef783fd9abc9f016d66edb6cac466dc54a17ec5f5ada08ff65caf5d", size = 2072473 },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/3e/84/d1614dedd8fe5114f6a0e348bcd1535f97d76c038d6102f271433cd1361d/pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:c860773a0f205926172c6644c394e02c25421dc9a456deff16f64c0e299487d3", size = 2249468 },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/c0/787061eef44135e00fddb4b56b387a06c303bfd3884a6df9bea5cb730230/pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:138d31e3f90087f42aa6286fb640f3c7a8eb7bdae829418265e7e7474bd2574b", size = 2254716 },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/e2/27262eb04963201e89f9c280f1e10c493a7a37bc877e023f31aa72d2f911/pydantic_core-2.33.0-cp313-cp313-win32.whl", hash = "sha256:d20cbb9d3e95114325780f3cfe990f3ecae24de7a2d75f978783878cce2ad585", size = 1916450 },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/13/8d/25ff96f1e89b19e0b70b3cd607c9ea7ca27e1dcb810a9cd4255ed6abf869/pydantic_core-2.33.0-cp313-cp313-win_amd64.whl", hash = "sha256:ca1103d70306489e3d006b0f79db8ca5dd3c977f6f13b2c59ff745249431a606", size = 1956092 },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/64/66a2efeff657b04323ffcd7b898cb0354d36dae3a561049e092134a83e9c/pydantic_core-2.33.0-cp313-cp313-win_arm64.whl", hash = "sha256:6291797cad239285275558e0a27872da735b05c75d5237bbade8736f80e4c225", size = 1908367 },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/54/295e38769133363d7ec4a5863a4d579f331728c71a6644ff1024ee529315/pydantic_core-2.33.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7b79af799630af263eca9ec87db519426d8c9b3be35016eddad1832bac812d87", size = 1813331 },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/4c/9c/0c8ea02db8d682aa1ef48938abae833c1d69bdfa6e5ec13b21734b01ae70/pydantic_core-2.33.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eabf946a4739b5237f4f56d77fa6668263bc466d06a8036c055587c130a46f7b", size = 1986653 },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/4f/3fb47d6cbc08c7e00f92300e64ba655428c05c56b8ab6723bd290bae6458/pydantic_core-2.33.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8a1d581e8cdbb857b0e0e81df98603376c1a5c34dc5e54039dcc00f043df81e7", size = 1931234 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-settings"
|
||||
version = "2.8.1"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "python-dotenv" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/82/c79424d7d8c29b994fb01d277da57b0a9b09cc03c3ff875f9bd8a86b2145/pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585", size = 83550 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.19.1"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.1.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "14.0.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "markdown-it-py" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shellingham"
|
||||
version = "1.5.4"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sse-starlette"
|
||||
version = "2.2.1"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "starlette" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/a4/80d2a11af59fe75b48230846989e93979c892d3a20016b42bb44edb9e398/sse_starlette-2.2.1.tar.gz", hash = "sha256:54470d5f19274aeed6b2d473430b08b4b379ea851d953b11d7f1c4a2c118b419", size = 17376 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/e0/5b8bd393f27f4a62461c5cf2479c75a2cc2ffa330976f9f00f5f6e4f50eb/sse_starlette-2.2.1-py3-none-any.whl", hash = "sha256:6410a3d3ba0c89e7675d4c273a301d64649c03a5ef1ca101f10b47f895fd0e99", size = 10120 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "0.46.1"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/1b/52b27f2e13ceedc79a908e29eac426a63465a1a01248e5f24aa36a62aeb3/starlette-0.46.1.tar.gz", hash = "sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230", size = 2580102 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/a0/4b/528ccf7a982216885a1ff4908e886b8fb5f19862d1962f56a3fce2435a70/starlette-0.46.1-py3-none-any.whl", hash = "sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227", size = 71995 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typer"
|
||||
version = "0.15.2"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "rich" },
|
||||
{ name = "shellingham" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/6f/3991f0f1c7fcb2df31aef28e0594d8d54b05393a0e4e34c65e475c2a5d41/typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5", size = 100711 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/7f/fc/5b29fea8cee020515ca82cc68e3b8e1e34bb19a3535ad854cac9257b414c/typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc", size = 45061 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.13.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/3e/b00a62db91a83fff600de219b6ea9908e6918664899a2d85db222f4fbf19/typing_extensions-4.13.0.tar.gz", hash = "sha256:0a4ac55a5820789d87e297727d229866c9650f6521b64206413c4fbada24d95b", size = 106520 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e0/86/39b65d676ec5732de17b7e3c476e45bb80ec64eb50737a8dce1a4178aba1/typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5", size = 45683 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-inspection"
|
||||
version = "0.4.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.34.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315 },
|
||||
]
|
||||
Reference in New Issue
Block a user