feat: add /v1/** api

This commit is contained in:
Sun-ZhenXing
2025-07-19 22:50:04 +08:00
parent 43c7a970dd
commit bcb5994356
18 changed files with 177 additions and 44 deletions

21
.editorconfig Normal file
View File

@@ -0,0 +1,21 @@
root = true
[*]
indent_size = 2
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.py]
indent_size = 4
[*.toml]
indent_size = 4
[*.md]
trim_trailing_whitespace = false
[Dockerfile]
indent_size = 4

2
.env.example Normal file
View File

@@ -0,0 +1,2 @@
MCP_DEFAULT_HOST=127.0.0.1
MCP_DEFAULT_PORT=3001

View File

@@ -20,6 +20,7 @@
"fastapi",
"fastmcp",
"localtime",
"mcps",
"pydantic",
"PYPI",
"pyproject",

View File

@@ -23,12 +23,18 @@ uv run dev
可通过 <http://127.0.0.1:3001/math/mcp> 访问示例 MCP 接口Streamable HTTP<http://127.0.0.1:3001/math/compatible/sse> 访问 SSE 接口。
通过 `--stdio` 来调用命令行:
```bash
uv run prod --stdio
```
## 部署
生产:
```bash
uv run prod
uv run --no-sync prod
```
构建 Python Wheel 包:

View File

@@ -14,6 +14,10 @@ services:
build:
context: .
dockerfile: Dockerfile
args:
- PORT=${PORT:-3001}
image: ${DOCKER_REGISTRY:-docker.io}/local/mcp-template-python:${BUILD_VERSION:-latest}
ports:
- "3001:${PORT:-3001}"
- "${EXPOSE_PORT:-3001}:${PORT:-3001}"
env_file:
- .env

View File

@@ -9,7 +9,7 @@ authors = [
requires-python = ">=3.12"
dependencies = [
"fastapi[standard]>=0.116.1",
"mcp>=1.11.0",
"mcp[cli]>=1.12.0",
"pydantic-settings>=2.10.1",
]

View File

@@ -2,7 +2,8 @@ import argparse
import sys
from .__about__ import __module_name__, __version__
from .config import MCP_MAP, settings
from .app import MCP_MAP
from .config import settings
def main():

View File

@@ -0,0 +1,5 @@
from .math import mcp as math_mcp
MCP_MAP = {
"math": math_mcp,
}

View File

@@ -2,7 +2,9 @@ from operator import add, mul, sub, truediv
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("math")
from ..config import settings
mcp = FastMCP("math", settings=settings.instructions)
@mcp.tool()

View File

@@ -1,10 +1,4 @@
from pydantic_settings import BaseSettings
from .app.math import mcp as math
MCP_MAP = {
"math": math,
}
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
@@ -16,9 +10,14 @@ class Settings(BaseSettings):
default_host: str = "127.0.0.1"
default_port: int = 3001
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
instructions: str | None = None
model_config = SettingsConfigDict(
env_prefix="MCP_",
env_file=".env",
env_file_encoding="utf-8",
extra="allow",
)
settings = Settings()

View File

@@ -0,0 +1,9 @@
from typing import Any, Dict
from pydantic import BaseModel
class ArgumentsRequest(BaseModel):
"""Request model for tool and prompt calls."""
arguments: Dict[str, Any]

View File

@@ -0,0 +1,66 @@
from fastapi import APIRouter
from ..app import MCP_MAP
from ..models.helpers import ArgumentsRequest
router = APIRouter(prefix="/v1", tags=["helpers"])
@router.get("/details")
async def details():
"""Get details about all available tools, prompts, and resources."""
tools = [
{
"name": name,
"server_name": mcp.name,
"dependencies": mcp.dependencies,
"instructions": mcp.instructions,
"prompts": await mcp.list_prompts(),
"tools": await mcp.list_tools(),
"resources": await mcp.list_resources(),
"resource_templates": await mcp.list_resource_templates(),
}
for name, mcp in MCP_MAP.items()
]
return {"tools": tools}
@router.post("/mcps/{mcp_name}/tools/{tool_name}/call")
async def call_tool(
mcp_name: str,
tool_name: str,
request: ArgumentsRequest,
):
"""Call a specific tool with parameters."""
if mcp_name not in MCP_MAP:
return {"error": "MCP not found"}
mcp = MCP_MAP[mcp_name]
result = await mcp.call_tool(tool_name, request.arguments)
return result
@router.post("/mcps/{mcp_name}/prompts/{prompt_name}/call")
async def call_prompt(
mcp_name: str,
prompt_name: str,
request: ArgumentsRequest,
):
"""Call a specific prompt with parameters."""
if mcp_name not in MCP_MAP:
return {"error": "MCP not found"}
mcp = MCP_MAP[mcp_name]
result = await mcp.get_prompt(prompt_name, request.arguments)
return result
@router.get("/mcps/{mcp_name}/resources/{uri}")
async def get_resource(mcp_name: str, uri: str):
"""Get a specific resource."""
if mcp_name not in MCP_MAP:
return {"error": "MCP not found"}
mcp = MCP_MAP[mcp_name]
result = await mcp.read_resource(uri)
return result

View File

@@ -2,7 +2,8 @@ import contextlib
from fastapi import FastAPI
from .config import MCP_MAP
from .app import MCP_MAP
from .routers.helpers import router as helpers_router
@contextlib.asynccontextmanager
@@ -18,14 +19,21 @@ app = FastAPI(lifespan=lifespan)
@app.get("/")
async def root():
return {"message": "Welcome to the MCP Template Python Server!"}
"""Root endpoint."""
return {"message": "Welcome!"}
@app.get("/health")
async def health():
return {"status": "healthy"}
"""Check the health of the server and list available tools."""
return {
"status": "healthy",
"tools": list(MCP_MAP.keys()),
}
app.include_router(helpers_router)
for name, mcp in MCP_MAP.items():
app.mount(f"/{name}/compatible", mcp.sse_app())
app.mount(f"/{name}", mcp.streamable_http_app())

47
uv.lock generated
View File

@@ -239,7 +239,7 @@ wheels = [
[[package]]
name = "jsonschema"
version = "4.24.0"
version = "4.25.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
@@ -247,9 +247,9 @@ dependencies = [
{ name = "referencing" },
{ name = "rpds-py" },
]
sdist = { url = "https://files.pythonhosted.org/packages/bf/d3/1cf5326b923a53515d8f3a2cd442e6d7e94fcc444716e879ea70a0ce3177/jsonschema-4.24.0.tar.gz", hash = "sha256:0b4e8069eb12aedfa881333004bccaec24ecef5a8a6a4b6df142b2cc9599d196", size = 353480, upload_time = "2025-05-26T18:48:10.459Z" }
sdist = { url = "https://files.pythonhosted.org/packages/d5/00/a297a868e9d0784450faa7365c2172a7d6110c763e30ba861867c32ae6a9/jsonschema-4.25.0.tar.gz", hash = "sha256:e63acf5c11762c0e6672ffb61482bdf57f0876684d8d249c0fe2d730d48bc55f", size = 356830, upload_time = "2025-07-18T15:39:45.11Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a2/3d/023389198f69c722d039351050738d6755376c8fd343e91dc493ea485905/jsonschema-4.24.0-py3-none-any.whl", hash = "sha256:a462455f19f5faf404a7902952b6f0e3ce868f3ee09a359b05eca6673bd8412d", size = 88709, upload_time = "2025-05-26T18:48:08.417Z" },
{ url = "https://files.pythonhosted.org/packages/fe/54/c86cd8e011fe98803d7e382fd67c0df5ceab8d2b7ad8c5a81524f791551c/jsonschema-4.25.0-py3-none-any.whl", hash = "sha256:24c2e8da302de79c8b9382fee3e76b355e44d2a4364bb207159ce10b517bd716", size = 89184, upload_time = "2025-07-18T15:39:42.956Z" },
]
[[package]]
@@ -316,7 +316,7 @@ wheels = [
[[package]]
name = "mcp"
version = "1.11.0"
version = "1.12.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
@@ -331,25 +331,31 @@ dependencies = [
{ name = "starlette" },
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3a/f5/9506eb5578d5bbe9819ee8ba3198d0ad0e2fbe3bab8b257e4131ceb7dfb6/mcp-1.11.0.tar.gz", hash = "sha256:49a213df56bb9472ff83b3132a4825f5c8f5b120a90246f08b0dac6bedac44c8", size = 406907, upload_time = "2025-07-10T16:41:09.388Z" }
sdist = { url = "https://files.pythonhosted.org/packages/45/94/caa0f4754e2437f7033068989f13fee784856f95870c786b0b5c2c0f511e/mcp-1.12.0.tar.gz", hash = "sha256:853f6b17a3f31ea6e2f278c2ec7d3b38457bc80c7c2c675260dd7f04a6fd0e70", size = 424678, upload_time = "2025-07-17T19:46:35.522Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/92/9c/c9ca79f9c512e4113a5d07043013110bb3369fc7770040c61378c7fbcf70/mcp-1.11.0-py3-none-any.whl", hash = "sha256:58deac37f7483e4b338524b98bc949b7c2b7c33d978f5fafab5bde041c5e2595", size = 155880, upload_time = "2025-07-10T16:41:07.935Z" },
{ url = "https://files.pythonhosted.org/packages/ed/da/c7eaab6a58f1034de115b7902141ad8f81b4f3bbf7dc0cc267594947a4d7/mcp-1.12.0-py3-none-any.whl", hash = "sha256:19a498b2bf273283e463b4dd1ed83f791fbba5c25bfa16b8b34cfd5571673e7f", size = 158470, upload_time = "2025-07-17T19:46:34.166Z" },
]
[package.optional-dependencies]
cli = [
{ name = "python-dotenv" },
{ name = "typer" },
]
[[package]]
name = "mcp-template-python"
version = "0.1.0"
version = "0.1.1"
source = { editable = "." }
dependencies = [
{ name = "fastapi", extra = ["standard"] },
{ name = "mcp" },
{ name = "mcp", extra = ["cli"] },
{ name = "pydantic-settings" },
]
[package.metadata]
requires-dist = [
{ name = "fastapi", extras = ["standard"], specifier = ">=0.116.1" },
{ name = "mcp", specifier = ">=1.11.0" },
{ name = "mcp", extras = ["cli"], specifier = ">=1.12.0" },
{ name = "pydantic-settings", specifier = ">=2.10.1" },
]
@@ -467,15 +473,18 @@ wheels = [
[[package]]
name = "pywin32"
version = "310"
version = "311"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6b/ec/4fdbe47932f671d6e348474ea35ed94227fb5df56a7c30cbbb42cd396ed0/pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d", size = 8796239, upload_time = "2025-03-17T00:55:58.807Z" },
{ url = "https://files.pythonhosted.org/packages/e3/e5/b0627f8bb84e06991bea89ad8153a9e50ace40b2e1195d68e9dff6b03d0f/pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060", size = 9503839, upload_time = "2025-03-17T00:56:00.8Z" },
{ url = "https://files.pythonhosted.org/packages/1f/32/9ccf53748df72301a89713936645a664ec001abd35ecc8578beda593d37d/pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966", size = 8459470, upload_time = "2025-03-17T00:56:02.601Z" },
{ url = "https://files.pythonhosted.org/packages/1c/09/9c1b978ffc4ae53999e89c19c77ba882d9fce476729f23ef55211ea1c034/pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab", size = 8794384, upload_time = "2025-03-17T00:56:04.383Z" },
{ url = "https://files.pythonhosted.org/packages/45/3c/b4640f740ffebadd5d34df35fecba0e1cfef8fde9f3e594df91c28ad9b50/pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e", size = 9503039, upload_time = "2025-03-17T00:56:06.207Z" },
{ url = "https://files.pythonhosted.org/packages/b4/f4/f785020090fb050e7fb6d34b780f2231f302609dc964672f72bfaeb59a28/pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33", size = 8458152, upload_time = "2025-03-17T00:56:07.819Z" },
{ url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload_time = "2025-07-14T20:13:20.765Z" },
{ url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload_time = "2025-07-14T20:13:22.543Z" },
{ url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload_time = "2025-07-14T20:13:24.682Z" },
{ url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload_time = "2025-07-14T20:13:26.471Z" },
{ url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload_time = "2025-07-14T20:13:28.243Z" },
{ url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload_time = "2025-07-14T20:13:30.348Z" },
{ url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload_time = "2025-07-14T20:13:32.449Z" },
{ url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload_time = "2025-07-14T20:13:34.312Z" },
{ url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload_time = "2025-07-14T20:13:36.379Z" },
]
[[package]]
@@ -669,15 +678,15 @@ wheels = [
[[package]]
name = "sentry-sdk"
version = "2.32.0"
version = "2.33.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/10/59/eb90c45cb836cf8bec973bba10230ddad1c55e2b2e9ffa9d7d7368948358/sentry_sdk-2.32.0.tar.gz", hash = "sha256:9016c75d9316b0f6921ac14c8cd4fb938f26002430ac5be9945ab280f78bec6b", size = 334932, upload_time = "2025-06-27T08:10:02.89Z" }
sdist = { url = "https://files.pythonhosted.org/packages/09/0b/6139f589436c278b33359845ed77019cd093c41371f898283bbc14d26c02/sentry_sdk-2.33.0.tar.gz", hash = "sha256:cdceed05e186846fdf80ceea261fe0a11ebc93aab2f228ed73d076a07804152e", size = 335233, upload_time = "2025-07-15T12:07:42.413Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/01/a1/fc4856bd02d2097324fb7ce05b3021fb850f864b83ca765f6e37e92ff8ca/sentry_sdk-2.32.0-py2.py3-none-any.whl", hash = "sha256:6cf51521b099562d7ce3606da928c473643abe99b00ce4cb5626ea735f4ec345", size = 356122, upload_time = "2025-06-27T08:10:01.424Z" },
{ url = "https://files.pythonhosted.org/packages/93/e5/f24e9f81c9822a24a2627cfcb44c10a3971382e67e5015c6e068421f5787/sentry_sdk-2.33.0-py2.py3-none-any.whl", hash = "sha256:a762d3f19a1c240e16c98796f2a5023f6e58872997d5ae2147ac3ed378b23ec2", size = 356397, upload_time = "2025-07-15T12:07:40.729Z" },
]
[[package]]