Skip to content

Commit f0e488a

Browse files
authored
feat: support vpn mcp server (#206)
1 parent 57d4d78 commit f0e488a

12 files changed

Lines changed: 2589 additions & 0 deletions

File tree

server/mcp_server_vpn/README.md

Lines changed: 485 additions & 0 deletions
Large diffs are not rendered by default.

server/mcp_server_vpn/README_zh.md

Lines changed: 510 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[project]
2+
name = "mcp-server-vpn"
3+
version = "1.0.0"
4+
description = "VPN MCP Server"
5+
readme = "README.md"
6+
requires-python = ">=3.10"
7+
dependencies = [
8+
"mcp>=1.10.0",
9+
"volcengine-python-sdk>=3.0.1",
10+
"volcengine>=1.0.101",
11+
]
12+
13+
[project.scripts]
14+
mcp-server-vpn = "mcp_server_vpn.main:main"
15+
16+
[build-system]
17+
requires = ["hatchling"]
18+
build-backend = "hatchling.build"
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# This file was autogenerated by uv via the following command:
2+
# uv pip compile pyproject.toml -o requirements.txt
3+
annotated-types==0.7.0
4+
# via pydantic
5+
anyio==4.9.0
6+
# via
7+
# httpx
8+
# mcp
9+
# sse-starlette
10+
# starlette
11+
attrs==25.3.0
12+
# via
13+
# jsonschema
14+
# referencing
15+
beautifulsoup4==4.13.4
16+
# via google
17+
certifi==2025.4.26
18+
# via
19+
# httpcore
20+
# httpx
21+
# requests
22+
# volcengine-python-sdk
23+
charset-normalizer==3.4.2
24+
# via requests
25+
click==8.2.0
26+
# via uvicorn
27+
decorator==5.2.1
28+
# via retry
29+
google==3.0.0
30+
# via volcengine
31+
h11==0.16.0
32+
# via
33+
# httpcore
34+
# uvicorn
35+
httpcore==1.0.9
36+
# via httpx
37+
httpx==0.28.1
38+
# via mcp
39+
httpx-sse==0.4.0
40+
# via mcp
41+
idna==3.10
42+
# via
43+
# anyio
44+
# httpx
45+
# requests
46+
jsonschema==4.25.0
47+
# via mcp
48+
jsonschema-specifications==2025.4.1
49+
# via jsonschema
50+
mcp==1.12.2
51+
# via mcp-server-vpn (pyproject.toml)
52+
protobuf==3.19.6
53+
# via volcengine
54+
py==1.11.0
55+
# via retry
56+
pycryptodome==3.9.9
57+
# via volcengine
58+
pydantic==2.11.4
59+
# via
60+
# mcp
61+
# pydantic-settings
62+
pydantic-core==2.33.2
63+
# via pydantic
64+
pydantic-settings==2.9.1
65+
# via mcp
66+
python-dateutil==2.9.0.post0
67+
# via volcengine-python-sdk
68+
python-dotenv==1.1.0
69+
# via pydantic-settings
70+
python-multipart==0.0.20
71+
# via mcp
72+
pytz==2020.5
73+
# via volcengine
74+
referencing==0.36.2
75+
# via
76+
# jsonschema
77+
# jsonschema-specifications
78+
requests==2.32.4
79+
# via volcengine
80+
retry==0.9.2
81+
# via volcengine
82+
rpds-py==0.26.0
83+
# via
84+
# jsonschema
85+
# referencing
86+
six==1.17.0
87+
# via
88+
# python-dateutil
89+
# volcengine
90+
# volcengine-python-sdk
91+
sniffio==1.3.1
92+
# via anyio
93+
soupsieve==2.7
94+
# via beautifulsoup4
95+
sse-starlette==2.3.5
96+
# via mcp
97+
starlette==0.46.2
98+
# via
99+
# mcp
100+
# sse-starlette
101+
typing-extensions==4.13.2
102+
# via
103+
# anyio
104+
# beautifulsoup4
105+
# pydantic
106+
# pydantic-core
107+
# referencing
108+
# typing-inspection
109+
typing-inspection==0.4.0
110+
# via
111+
# pydantic
112+
# pydantic-settings
113+
urllib3==2.4.0
114+
# via
115+
# requests
116+
# volcengine-python-sdk
117+
uvicorn==0.34.3
118+
# via mcp
119+
volcengine==1.0.101
120+
# via mcp-server-vpn (pyproject.toml)
121+
volcengine-python-sdk==3.0.1
122+
# via mcp-server-vpn (pyproject.toml)

server/mcp_server_vpn/src/mcp_server_vpn/__init__.py

Whitespace-only changes.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from .base import BaseApi
2+
from .vpn import VPNClient
3+
from .models import (
4+
DescribeVpnConnectionAttributesResponse,
5+
DescribeVpnGatewayAttributesResponse,
6+
DescribeVpnConnectionsResponse,
7+
DescribeVpnGatewaysResponse,
8+
DescribeCustomerGatewaysResponse,
9+
)
10+
11+
__all__ = [
12+
"BaseApi",
13+
"VPNClient",
14+
"DescribeVpnConnectionAttributesResponse",
15+
"DescribeVpnGatewayAttributesResponse",
16+
"DescribeVpnConnectionsResponse",
17+
"DescribeVpnGatewaysResponse",
18+
"DescribeCustomerGatewaysResponse",
19+
]
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import json
2+
3+
from volcengine.ApiInfo import ApiInfo
4+
from typing import Any
5+
from volcengine import Credentials
6+
from volcengine import ServiceInfo
7+
from volcengine.base import Service
8+
9+
10+
class BaseApi(Service.Service):
11+
def __init__(self, region, endpoint, api_info, service, ak, sk):
12+
"""init function.
13+
:param region: region of request
14+
:param api_info: an object of volcengine.ApiInfo.ApiInfo()
15+
:param endpoint: endpoint of top gateway
16+
:param service: a specific service name registered on top gateway
17+
:param ak: account ak
18+
:param sk: account ak
19+
"""
20+
self.connection_timeout = 10
21+
self.socket_timeout = 10
22+
self.schema = 'https'
23+
self.header = dict()
24+
self.header["Content-Type"] = "application/json"
25+
self.endpoint = endpoint
26+
27+
self.credential = Credentials.Credentials(ak, sk, service, region)
28+
self.service_info = ServiceInfo.ServiceInfo(
29+
self.endpoint,
30+
self.header,
31+
self.credential,
32+
self.connection_timeout,
33+
self.socket_timeout,
34+
self.schema,
35+
)
36+
self.api_info = api_info
37+
Service.Service.__init__(self, self.service_info, self.api_info)
38+
39+
@staticmethod
40+
def to_params(obj: Any) -> dict[str, Any]:
41+
"""Convert a request model to a parameters dict, dropping None values.
42+
43+
``volcengine-python-sdk`` request models expose ``attribute_map``
44+
describing how Python attribute names map to request parameter keys. This
45+
helper applies that mapping so that callers can use snake_case attribute
46+
names while the API receives the expected camel case keys.
47+
"""
48+
49+
if hasattr(obj, "to_dict"):
50+
data = obj.to_dict()
51+
elif hasattr(obj, "model_dump"):
52+
data = obj.model_dump()
53+
elif isinstance(obj, dict):
54+
data = obj
55+
else:
56+
data = getattr(obj, "__dict__", {})
57+
58+
attr_map = getattr(obj, "attribute_map", None)
59+
if isinstance(attr_map, dict):
60+
data = {attr_map.get(k, k): v for k, v in data.items()}
61+
62+
return {k: v for k, v in data.items() if v is not None}
63+
64+
def get(self, action, params, doseq=0):
65+
res = super(BaseApi, self).get(action, params, doseq)
66+
try:
67+
res_json = json.loads(res)
68+
except Exception as e:
69+
raise Exception("res body is not json, %s, %s" % (e, res))
70+
if "ResponseMetadata" not in res_json:
71+
raise Exception(
72+
"ResponseMetadata not in resp body, action %s, resp %s" % (action, res)
73+
)
74+
elif "Error" in res_json["ResponseMetadata"]:
75+
raise Exception("%s failed, %s" % (action, res))
76+
return res_json
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from pydantic import BaseModel, ConfigDict
2+
from pydantic.version import VERSION
3+
4+
5+
class BaseResponseModel(BaseModel):
6+
"""Generic response model allowing extra fields."""
7+
8+
if VERSION.startswith("2"):
9+
model_config = ConfigDict(
10+
extra="allow",
11+
arbitrary_types_allowed=True,
12+
)
13+
else:
14+
class Config:
15+
extra = "allow"
16+
arbitrary_types_allowed = True
17+
18+
19+
class DescribeVpnConnectionAttributesResponse(BaseResponseModel):
20+
pass
21+
22+
23+
class DescribeVpnGatewayAttributesResponse(BaseResponseModel):
24+
pass
25+
26+
27+
class DescribeVpnConnectionsResponse(BaseResponseModel):
28+
pass
29+
30+
31+
class DescribeVpnGatewaysResponse(BaseResponseModel):
32+
pass
33+
34+
35+
class DescribeVpnGatewayRouteAttributesResponse(BaseResponseModel):
36+
pass
37+
38+
39+
class DescribeVpnGatewayRoutesResponse(BaseResponseModel):
40+
pass
41+
42+
43+
class DescribeCustomerGatewaysResponse(BaseResponseModel):
44+
pass
45+
46+
47+
class DescribeSslVpnClientCertAttributesResponse(BaseResponseModel):
48+
pass
49+
50+
51+
class DescribeSslVpnClientCertsResponse(BaseResponseModel):
52+
pass
53+
54+
55+
class DescribeSslVpnServersResponse(BaseResponseModel):
56+
pass

0 commit comments

Comments
 (0)