mirror of
https://github.com/Sun-ZhenXing/mcp-template-python.git
synced 2026-02-04 02:03:32 +00:00
feat: add /ws
This commit is contained in:
41
README.md
41
README.md
@@ -1,57 +1,64 @@
|
|||||||
# MCP FastAPI 应用模板
|
# MCP FastAPI Application Template
|
||||||
|
|
||||||
本项目提供了 FastAPI 集成的 MCP 应用模板。
|
🌏 [中文](./README.zh.md) | [English](./README.md)
|
||||||
|
|
||||||
- [x] 支持多 MCP 挂载
|
This project provides an MCP application template integrated with FastAPI.
|
||||||
- [x] 支持命令行调用 Stdio 模式
|
|
||||||
- [x] 支持 SSE / Streamable HTTP 兼容
|
|
||||||
- [x] 支持打包分发
|
|
||||||
|
|
||||||
## 开始
|
- [x] Support for multiple MCP mounting
|
||||||
|
- [x] Support for command-line invocation in Stdio mode
|
||||||
|
- [x] Support for SSE / StreamableHTTP / WebSocket
|
||||||
|
- [x] Support for packaging and distribution
|
||||||
|
|
||||||
安装依赖:
|
Starting from v0.1.2, we use `BetterFastMCP` to replace `FastMCP`, providing more comprehensive features than the official `FastMCP`:
|
||||||
|
|
||||||
|
- [x] Support for Pydantic models as input parameters, enabling more complex input parameter types and convenient description addition
|
||||||
|
- [x] Support for WebSocket as transport layer, access by `/{mcp_name}/websocket/ws`
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Install dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv sync
|
uv sync
|
||||||
```
|
```
|
||||||
|
|
||||||
开发:
|
Development:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv run dev
|
uv run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
可通过 <http://127.0.0.1:3001/math/mcp> 访问示例 MCP 接口(Streamable HTTP),或 <http://127.0.0.1:3001/math/compatible/sse> 访问 SSE 接口。
|
You can access the example MCP interface (Streamable HTTP) via <http://127.0.0.1:3001/math/mcp>, or access the SSE interface via <http://127.0.0.1:3001/math/compatible/sse>.
|
||||||
|
|
||||||
通过 `--stdio` 来调用命令行:
|
Call via command line with `--stdio`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv run prod --stdio
|
uv run prod --stdio
|
||||||
```
|
```
|
||||||
|
|
||||||
## 部署
|
## Deployment
|
||||||
|
|
||||||
生产:
|
Production:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv run --no-sync prod
|
uv run --no-sync prod
|
||||||
```
|
```
|
||||||
|
|
||||||
构建 Python Wheel 包:
|
Build Python Wheel package:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv build
|
uv build
|
||||||
```
|
```
|
||||||
|
|
||||||
## Docker 部署
|
## Docker Deployment
|
||||||
|
|
||||||
运行:
|
Run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
仅构建:
|
Build only:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose build
|
docker compose build
|
||||||
|
|||||||
65
README.zh.md
Normal file
65
README.zh.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# MCP FastAPI 应用模板
|
||||||
|
|
||||||
|
🌏 [中文](./README.zh.md) | [English](./README.md)
|
||||||
|
|
||||||
|
本项目提供了 FastAPI 集成的 MCP 应用模板。
|
||||||
|
|
||||||
|
- [x] 支持多 MCP 挂载
|
||||||
|
- [x] 支持命令行调用 Stdio 模式
|
||||||
|
- [x] 支持 SSE / StreamableHTTP / WebSocket 兼容
|
||||||
|
- [x] 支持打包分发
|
||||||
|
|
||||||
|
从 v0.1.2 开始,我们使用 `BetterFastMCP` 替换 `FastMCP`,提供比官方 `FastMCP` 更完善的功能:
|
||||||
|
|
||||||
|
- [x] 支持入参为 Pydantic 模型,以便支持更复杂的输入参数类型并方便添加描述
|
||||||
|
- [x] 支持 WebSocket 作为传输层,通过 `/{mcp_name}/websocket/ws` 访问
|
||||||
|
|
||||||
|
## 开始
|
||||||
|
|
||||||
|
安装依赖:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv sync
|
||||||
|
```
|
||||||
|
|
||||||
|
开发:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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 --no-sync prod
|
||||||
|
```
|
||||||
|
|
||||||
|
构建 Python Wheel 包:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docker 部署
|
||||||
|
|
||||||
|
运行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
仅构建:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose build
|
||||||
|
```
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "mcp-template-python"
|
name = "mcp-template-python"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
description = "Add your description here"
|
description = "Add your description here"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [
|
authors = [
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import argparse
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from .__about__ import __module_name__, __version__
|
from .__about__ import __module_name__, __version__
|
||||||
from .app import MCP_MAP
|
|
||||||
from .config import settings
|
from .config import settings
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
from .app import MCP_MAP
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="MCP Server")
|
parser = argparse.ArgumentParser(description="MCP Server")
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
@@ -14,6 +15,13 @@ def main():
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="Run the server with STDIO (default: False)",
|
help="Run the server with STDIO (default: False)",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--mcp",
|
||||||
|
type=str,
|
||||||
|
default=settings.default_mcp,
|
||||||
|
choices=list(MCP_MAP.keys()),
|
||||||
|
help=f"Select the MCP to run in STDIO mode (default: {settings.default_mcp})",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--host",
|
"--host",
|
||||||
default=settings.default_host,
|
default=settings.default_host,
|
||||||
@@ -44,7 +52,10 @@ def main():
|
|||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
if args.stdio:
|
if args.stdio:
|
||||||
mcp = MCP_MAP[settings.default_mcp]
|
mcp = MCP_MAP.get(args.mcp)
|
||||||
|
if mcp is None:
|
||||||
|
print(f"Error: MCP '{args.mcp}' not found.")
|
||||||
|
sys.exit(1)
|
||||||
mcp.run()
|
mcp.run()
|
||||||
else:
|
else:
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|||||||
@@ -1,39 +1,31 @@
|
|||||||
from operator import add, mul, sub, truediv
|
from operator import add, mul, sub, truediv
|
||||||
|
|
||||||
from mcp.server.fastmcp import FastMCP
|
from mcp_template_python.lib.better_mcp import BetterFastMCP
|
||||||
|
|
||||||
from ..config import settings
|
from ..config import settings
|
||||||
|
|
||||||
mcp = FastMCP("math", settings=settings.instructions)
|
mcp = BetterFastMCP("math", settings=settings.instructions)
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def add_nums(a: float, b: float) -> float:
|
async def add_num(a: float, b: float) -> float:
|
||||||
"""
|
"""Adds two numbers."""
|
||||||
Adds two numbers.
|
|
||||||
"""
|
|
||||||
return add(a, b)
|
return add(a, b)
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def sub_nums(a: float, b: float) -> float:
|
async def sub_num(a: float, b: float) -> float:
|
||||||
"""
|
"""Subtracts the second number from the first."""
|
||||||
Subtracts the second number from the first.
|
|
||||||
"""
|
|
||||||
return sub(a, b)
|
return sub(a, b)
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def mul_nums(a: float, b: float) -> float:
|
async def mul_num(a: float, b: float) -> float:
|
||||||
"""
|
"""Multiplies two numbers."""
|
||||||
Multiplies two numbers.
|
|
||||||
"""
|
|
||||||
return mul(a, b)
|
return mul(a, b)
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def div_nums(a: float, b: float) -> float:
|
async def div_num(a: float, b: float) -> float:
|
||||||
"""
|
"""Divides the first number by the second."""
|
||||||
Divides the first number by the second.
|
|
||||||
"""
|
|
||||||
return truediv(a, b)
|
return truediv(a, b)
|
||||||
|
|||||||
@@ -6,12 +6,6 @@ class Settings(BaseSettings):
|
|||||||
Configuration settings for the MCP template application.
|
Configuration settings for the MCP template application.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
default_mcp: str = "math"
|
|
||||||
default_host: str = "127.0.0.1"
|
|
||||||
default_port: int = 3001
|
|
||||||
|
|
||||||
instructions: str | None = None
|
|
||||||
|
|
||||||
model_config = SettingsConfigDict(
|
model_config = SettingsConfigDict(
|
||||||
env_prefix="MCP_",
|
env_prefix="MCP_",
|
||||||
env_file=".env",
|
env_file=".env",
|
||||||
@@ -19,5 +13,38 @@ class Settings(BaseSettings):
|
|||||||
extra="allow",
|
extra="allow",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
app_title: str = "MCP Template Application"
|
||||||
|
"""Title of the MCP application, defaults to 'MCP Template Application'."""
|
||||||
|
|
||||||
|
app_description: str = "A template application for MCP using FastAPI."
|
||||||
|
"""Description of the MCP application, defaults to 'A template application for MCP using FastAPI.'"""
|
||||||
|
|
||||||
|
default_mcp: str = "math"
|
||||||
|
"""Default MCP to be used by the application."""
|
||||||
|
|
||||||
|
default_host: str = "127.0.0.1"
|
||||||
|
"""Default host for the MCP server, defaults to 127.0.0.1."""
|
||||||
|
|
||||||
|
default_port: int = 3001
|
||||||
|
"""Default port for the MCP server, defaults to 3001."""
|
||||||
|
|
||||||
|
instructions: str | None = None
|
||||||
|
"""Instructions to be used by the MCP server, defaults to None."""
|
||||||
|
|
||||||
|
enable_helpers_router: bool = True
|
||||||
|
"""Enable the helpers router for the MCP server."""
|
||||||
|
|
||||||
|
enable_sse: bool = True
|
||||||
|
"""Enable Server-Sent Events (SSE) for the MCP server."""
|
||||||
|
|
||||||
|
enable_streamable_http: bool = True
|
||||||
|
"""Enable streamable HTTP for the MCP server."""
|
||||||
|
|
||||||
|
enable_websocket: bool = False
|
||||||
|
"""Enable WebSocket for the MCP server."""
|
||||||
|
|
||||||
|
websocket_path: str = "/ws"
|
||||||
|
"""Path for the WebSocket endpoint."""
|
||||||
|
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
|
|||||||
0
src/mcp_template_python/lib/__init__.py
Normal file
0
src/mcp_template_python/lib/__init__.py
Normal file
180
src/mcp_template_python/lib/better_mcp.py
Normal file
180
src/mcp_template_python/lib/better_mcp.py
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
import logging
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
from mcp.server.auth.middleware.auth_context import AuthContextMiddleware
|
||||||
|
from mcp.server.auth.middleware.bearer_auth import (
|
||||||
|
BearerAuthBackend,
|
||||||
|
RequireAuthMiddleware,
|
||||||
|
)
|
||||||
|
from mcp.server.fastmcp import FastMCP
|
||||||
|
from mcp.server.websocket import websocket_server
|
||||||
|
from mcp.types import ToolAnnotations
|
||||||
|
from starlette.applications import Starlette
|
||||||
|
from starlette.middleware import Middleware
|
||||||
|
from starlette.middleware.authentication import AuthenticationMiddleware
|
||||||
|
from starlette.routing import Mount, Route
|
||||||
|
from starlette.websockets import WebSocket
|
||||||
|
|
||||||
|
from ..config import settings
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class BetterFastMCP(FastMCP):
|
||||||
|
def run(
|
||||||
|
self,
|
||||||
|
transport: Literal["stdio", "sse", "streamable-http", "ws"] = "stdio",
|
||||||
|
mount_path: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
import anyio
|
||||||
|
|
||||||
|
if transport == "ws":
|
||||||
|
anyio.run(self.run_ws_async)
|
||||||
|
else:
|
||||||
|
super().run(transport=transport, mount_path=mount_path)
|
||||||
|
|
||||||
|
async def run_ws_async(self) -> None:
|
||||||
|
"""Run the server using WebSocket transport."""
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
starlette_app = self.ws_app()
|
||||||
|
|
||||||
|
config = uvicorn.Config(
|
||||||
|
app=starlette_app,
|
||||||
|
host=self.settings.host,
|
||||||
|
port=self.settings.port,
|
||||||
|
log_level=self.settings.log_level.lower(),
|
||||||
|
)
|
||||||
|
server = uvicorn.Server(config)
|
||||||
|
await server.serve()
|
||||||
|
|
||||||
|
def ws_app(self) -> Starlette:
|
||||||
|
"""Return an instance of the Websocket server app."""
|
||||||
|
|
||||||
|
async def handle_ws(websocket: WebSocket):
|
||||||
|
async with websocket_server(
|
||||||
|
websocket.scope, websocket.receive, websocket.send
|
||||||
|
) as (ws_read_stream, ws_write_stream):
|
||||||
|
await self._mcp_server.run(
|
||||||
|
ws_read_stream,
|
||||||
|
ws_write_stream,
|
||||||
|
self._mcp_server.create_initialization_options(),
|
||||||
|
raise_exceptions=self.settings.debug,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create routes
|
||||||
|
routes: list[Route | Mount] = []
|
||||||
|
middleware: list[Middleware] = []
|
||||||
|
required_scopes = []
|
||||||
|
|
||||||
|
# Set up auth if configured
|
||||||
|
if self.settings.auth:
|
||||||
|
required_scopes = self.settings.auth.required_scopes or []
|
||||||
|
|
||||||
|
# Add auth middleware if token verifier is available
|
||||||
|
if self._token_verifier:
|
||||||
|
middleware = [
|
||||||
|
Middleware(
|
||||||
|
AuthenticationMiddleware,
|
||||||
|
backend=BearerAuthBackend(self._token_verifier),
|
||||||
|
),
|
||||||
|
Middleware(AuthContextMiddleware),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add auth endpoints if auth server provider is configured
|
||||||
|
if self._auth_server_provider:
|
||||||
|
from mcp.server.auth.routes import create_auth_routes
|
||||||
|
|
||||||
|
routes.extend(
|
||||||
|
create_auth_routes(
|
||||||
|
provider=self._auth_server_provider,
|
||||||
|
issuer_url=self.settings.auth.issuer_url,
|
||||||
|
service_documentation_url=self.settings.auth.service_documentation_url,
|
||||||
|
client_registration_options=self.settings.auth.client_registration_options,
|
||||||
|
revocation_options=self.settings.auth.revocation_options,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set up routes with or without auth
|
||||||
|
if self._token_verifier:
|
||||||
|
# Determine resource metadata URL
|
||||||
|
resource_metadata_url = None
|
||||||
|
if self.settings.auth and self.settings.auth.resource_server_url:
|
||||||
|
from pydantic import AnyHttpUrl
|
||||||
|
|
||||||
|
resource_metadata_url = AnyHttpUrl(
|
||||||
|
str(self.settings.auth.resource_server_url).rstrip("/")
|
||||||
|
+ "/.well-known/oauth-protected-resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
routes.append(
|
||||||
|
Route(
|
||||||
|
settings.websocket_path,
|
||||||
|
endpoint=RequireAuthMiddleware(
|
||||||
|
handle_ws, required_scopes, resource_metadata_url
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Auth is disabled, no wrapper needed
|
||||||
|
routes.append(
|
||||||
|
Route(
|
||||||
|
settings.websocket_path,
|
||||||
|
endpoint=handle_ws,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add protected resource metadata endpoint if configured as RS
|
||||||
|
if self.settings.auth and self.settings.auth.resource_server_url:
|
||||||
|
from mcp.server.auth.handlers.metadata import (
|
||||||
|
ProtectedResourceMetadataHandler,
|
||||||
|
)
|
||||||
|
from mcp.server.auth.routes import cors_middleware
|
||||||
|
from mcp.shared.auth import ProtectedResourceMetadata
|
||||||
|
|
||||||
|
protected_resource_metadata = ProtectedResourceMetadata(
|
||||||
|
resource=self.settings.auth.resource_server_url,
|
||||||
|
authorization_servers=[self.settings.auth.issuer_url],
|
||||||
|
scopes_supported=self.settings.auth.required_scopes,
|
||||||
|
)
|
||||||
|
routes.append(
|
||||||
|
Route(
|
||||||
|
"/.well-known/oauth-protected-resource",
|
||||||
|
endpoint=cors_middleware(
|
||||||
|
ProtectedResourceMetadataHandler(
|
||||||
|
protected_resource_metadata
|
||||||
|
).handle,
|
||||||
|
["GET", "OPTIONS"],
|
||||||
|
),
|
||||||
|
methods=["GET", "OPTIONS"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
routes.extend(self._custom_starlette_routes)
|
||||||
|
|
||||||
|
return Starlette(
|
||||||
|
debug=self.settings.debug,
|
||||||
|
routes=routes,
|
||||||
|
middleware=middleware,
|
||||||
|
lifespan=lambda app: self.session_manager.run(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def better_tool(
|
||||||
|
self,
|
||||||
|
name: str | None = None,
|
||||||
|
title: str | None = None,
|
||||||
|
description: str | None = None,
|
||||||
|
annotations: ToolAnnotations | None = None,
|
||||||
|
structured_output: bool | None = None,
|
||||||
|
):
|
||||||
|
"""Decorator to register a tool.
|
||||||
|
TODO: Implement a better tool function decorator.
|
||||||
|
"""
|
||||||
|
# tool_mcp = self._tool_manager._tools
|
||||||
|
# existing = tool_mcp.get(name)
|
||||||
|
# if existing:
|
||||||
|
# if self._tool_manager.warn_on_duplicate_tools:
|
||||||
|
# logger.warning(f"Tool already exists: {tool.name}")
|
||||||
|
# return existing
|
||||||
|
# self._tools[tool.name] = tool
|
||||||
|
# return tool
|
||||||
@@ -2,7 +2,9 @@ import contextlib
|
|||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
from .__about__ import __version__
|
||||||
from .app import MCP_MAP
|
from .app import MCP_MAP
|
||||||
|
from .config import settings
|
||||||
from .routers.helpers import router as helpers_router
|
from .routers.helpers import router as helpers_router
|
||||||
|
|
||||||
|
|
||||||
@@ -14,13 +16,21 @@ async def lifespan(app: FastAPI):
|
|||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
||||||
app = FastAPI(lifespan=lifespan)
|
app = FastAPI(
|
||||||
|
title=settings.app_title,
|
||||||
|
description=settings.app_description,
|
||||||
|
version=__version__,
|
||||||
|
lifespan=lifespan,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
async def root():
|
async def root():
|
||||||
"""Root endpoint."""
|
"""Root endpoint."""
|
||||||
return {"message": "Welcome!"}
|
return {
|
||||||
|
"message": "Welcome!",
|
||||||
|
"tools": list(MCP_MAP.keys()),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
@@ -28,12 +38,16 @@ async def health():
|
|||||||
"""Check the health of the server and list available tools."""
|
"""Check the health of the server and list available tools."""
|
||||||
return {
|
return {
|
||||||
"status": "healthy",
|
"status": "healthy",
|
||||||
"tools": list(MCP_MAP.keys()),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
app.include_router(helpers_router)
|
if settings.enable_helpers_router:
|
||||||
|
app.include_router(helpers_router)
|
||||||
|
|
||||||
for name, mcp in MCP_MAP.items():
|
for name, mcp in MCP_MAP.items():
|
||||||
app.mount(f"/{name}/compatible", mcp.sse_app())
|
if settings.enable_sse:
|
||||||
app.mount(f"/{name}", mcp.streamable_http_app())
|
app.mount(f"/{name}/compatible", mcp.sse_app())
|
||||||
|
if settings.enable_streamable_http:
|
||||||
|
app.mount(f"/{name}", mcp.streamable_http_app())
|
||||||
|
if settings.enable_websocket:
|
||||||
|
app.mount(f"/{name}/websocket", mcp.ws_app())
|
||||||
|
|||||||
2
uv.lock
generated
2
uv.lock
generated
@@ -344,7 +344,7 @@ cli = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mcp-template-python"
|
name = "mcp-template-python"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "fastapi", extra = ["standard"] },
|
{ name = "fastapi", extra = ["standard"] },
|
||||||
|
|||||||
Reference in New Issue
Block a user