14 Commits

Author SHA1 Message Date
Sun-ZhenXing
0ffc5b69b4 feat: update config & cors 2025-10-02 16:20:15 +08:00
Sun-ZhenXing
477888de17 fix: docker-compose env 2025-09-24 22:27:58 +08:00
Sun-ZhenXing
8567b0804a feat: update log format 2025-09-22 17:56:39 +08:00
Sun-ZhenXing
a42e3a2954 feat: add helm/** 2025-09-18 11:31:01 +08:00
Sun-ZhenXing
177d797ad2 feat: use click 2025-09-18 11:13:23 +08:00
Sun-ZhenXing
dc8d7bb12d chore: update deps 2025-09-08 19:20:10 +08:00
Sun-ZhenXing
660f11cccb chore(deps): update & add Makefile 2025-08-28 14:41:00 +08:00
Sun-ZhenXing
79c5e87526 chore: update deps 2025-08-18 11:44:59 +08:00
Sun-ZhenXing
72388cbbf6 feat: use rootless docker image 2025-08-10 18:44:38 +08:00
Sun-ZhenXing
6a6dbe25d2 chore: update deps 2025-08-07 19:05:18 +08:00
Sun-ZhenXing
a837441bc4 chore: update deps 2025-07-25 09:28:12 +08:00
Sun-ZhenXing
7941617a8f chore: move ws support to feat-ws 2025-07-21 11:44:33 +08:00
Sun-ZhenXing
2b48a92596 feat: update config 2025-07-21 11:36:58 +08:00
Sun-ZhenXing
48f2efef7a feat: add /ws 2025-07-21 00:09:53 +08:00
35 changed files with 1944 additions and 571 deletions

View File

@@ -19,3 +19,6 @@ trim_trailing_whitespace = false
[Dockerfile]
indent_size = 4
[Makefile]
indent_style = tab

View File

@@ -1,2 +1,2 @@
MCP_DEFAULT_HOST=127.0.0.1
MCP_DEFAULT_PORT=3001
APP_DEFAULT_HOST=127.0.0.1
APP_DEFAULT_PORT=3001

3
.gitignore vendored
View File

@@ -3,6 +3,9 @@
.env.*
!.env.example
# Production configuration
values-production.yaml
# Python-generated files
__pycache__/
*.py[oc]

View File

@@ -2,6 +2,8 @@
"recommendations": [
"ms-python.python",
"charliermarsh.ruff",
"fill-labs.dependi"
"fill-labs.dependi",
"EditorConfig.EditorConfig",
"tamasfe.even-better-toml"
]
}

View File

@@ -17,10 +17,12 @@
],
"files.eol": "\n",
"cSpell.words": [
"dotenv",
"fastapi",
"fastmcp",
"localtime",
"mcps",
"noninteractive",
"pydantic",
"PYPI",
"pyproject",

View File

@@ -1,6 +1,7 @@
ARG PYPI_MIRROR_URL=https://pypi.org/simple
ARG DEBIAN_MIRROR=ftp.cn.debian.org
# Base stage
FROM python:3.12-bookworm AS deps
ARG DEBIAN_FRONTEND=noninteractive
ARG PYPI_MIRROR_URL
@@ -13,14 +14,22 @@ ENV UV_DEFAULT_INDEX=${PYPI_MIRROR_URL}
# Install dependencies
RUN pip -V && \
pip config set global.index-url ${PYPI_MIRROR_URL} && \
pip install uv
pip install --no-cache-dir uv
# Sync dependencies
RUN --mount=type=cache,target=/root/.cache/uv,id=uv-cache,sharing=locked \
uv sync --no-dev --no-install-project
# Runner stage
FROM python:3.12-slim-bookworm AS runner
ARG DEBIAN_FRONTEND=noninteractive
ARG DEBIAN_MIRROR
ARG PYPI_MIRROR_URL
# rootless user args
ARG APP_USER=app
ARG APP_UID=1000
ARG APP_GID=1000
WORKDIR /app
RUN sed -i "s/deb.debian.org/${DEBIAN_MIRROR}/g" /etc/apt/sources.list.d/debian.sources && \
@@ -34,15 +43,29 @@ RUN pip -V && \
pip config set global.index-url ${PYPI_MIRROR_URL} && \
pip install --no-cache-dir uv
COPY --from=deps /app/.venv/ ./.venv/
COPY . ./
# Create non-root user/group for rootless execution
RUN groupadd -g ${APP_GID} ${APP_USER} && \
useradd -m -u ${APP_UID} -g ${APP_GID} -s /bin/bash ${APP_USER}
# Copy venv and sources with proper ownership
COPY --from=deps --chown=${APP_UID}:${APP_GID} /app/.venv/ ./.venv/
COPY --chown=${APP_UID}:${APP_GID} . ./
# Ensure dependencies sync
RUN --mount=type=cache,target=/root/.cache/uv,id=uv-cache,sharing=locked \
uv sync --no-dev
uv sync --no-dev && \
chown -R ${APP_UID}:${APP_GID} /app
# Environment for venv
ENV VIRTUAL_ENV=/app/.venv
ENV PATH="${VIRTUAL_ENV}/bin:${PATH}"
ARG PORT=3001
ENV PORT=${PORT}
# Switch to non-root user
USER ${APP_UID}:${APP_GID}
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
CMD curl -f http://localhost:${PORT}/health || exit 1

50
Makefile Normal file
View File

@@ -0,0 +1,50 @@
.PHONY: i dev prod build clean update lint docker-build docker-run helm-install helm-upgrade helm-uninstall helm-lint
i:
uv sync --all-extras --all-packages $(filter-out i,$(MAKECMDGOALS))
dev:
uv run --no-sync python -c "__import__('mcp_template_python.__main__').__main__.dev()" $(filter-out dev,$(MAKECMDGOALS)) || echo shutdown
prod:
uv run --no-sync python -c "__import__('mcp_template_python.__main__').__main__.main()" $(filter-out prod,$(MAKECMDGOALS))
build:
uv build $(filter-out build,$(MAKECMDGOALS))
clean:
rm -rf .venv .ruff_cache dist/ build/ *.egg-info $(filter-out clean,$(MAKECMDGOALS))
update:
uv sync --all-extras --all-packages -U $(filter-out update,$(MAKECMDGOALS))
lint:
uv run ruff check . $(filter-out lint,$(MAKECMDGOALS))
docker-build:
docker compose build $(filter-out docker-build,$(MAKECMDGOALS))
docker-run:
docker compose up -d $(filter-out docker-run,$(MAKECMDGOALS))
# Helm deployment commands
helm-lint:
helm lint helm/mcp-template-python
helm-install:
helm install mcp-template-python helm/mcp-template-python
helm-upgrade:
helm upgrade mcp-template-python helm/mcp-template-python
helm-install-prod:
helm install mcp-template-python helm/mcp-template-python -f values-production.yaml
helm-upgrade-prod:
helm upgrade mcp-template-python helm/mcp-template-python -f values-production.yaml
helm-uninstall:
helm uninstall mcp-template-python
%:
@:

View File

@@ -1,57 +1,59 @@
# MCP FastAPI 应用模板
# MCP FastAPI Application Template
本项目提供了 FastAPI 集成的 MCP 应用模板。
🌏 [中文](./README.zh.md) | [English](./README.md)
- [x] 支持多 MCP 挂载
- [x] 支持命令行调用 Stdio 模式
- [x] 支持 SSE / Streamable HTTP 兼容
- [x] 支持打包分发
This project provides an MCP application template integrated with FastAPI.
## 开始
- [x] Support for multiple MCP mounting
- [x] Support for command-line invocation in Stdio mode
- [x] Support for SSE / StreamableHTTP
- [x] Support for packaging and distribution
安装依赖:
## Getting Started
Install dependencies:
```bash
uv sync
make
```
开发:
Development:
```bash
uv run dev
make 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
uv run prod --stdio
make prod -- --stdio
```
## 部署
## Deployment
生产:
Production:
```bash
uv run --no-sync prod
make prod
```
构建 Python Wheel 包:
Build Python Wheel package:
```bash
uv build
make build
```
## Docker 部署
## Docker Deployment
运行:
Run:
```bash
docker compose up -d
```
仅构建:
Build only:
```bash
docker compose build

60
README.zh.md Normal file
View File

@@ -0,0 +1,60 @@
# MCP FastAPI 应用模板
🌏 [中文](./README.zh.md) | [English](./README.md)
本项目提供了 FastAPI 集成的 MCP 应用模板。
- [x] 支持多 MCP 挂载
- [x] 支持命令行调用 Stdio 模式
- [x] 支持 SSE / StreamableHTTP
- [x] 支持打包分发
## 开始
安装依赖:
```bash
make
```
开发:
```bash
make dev
```
可通过 <http://127.0.0.1:3001/math/mcp> 访问示例 MCP 接口Streamable HTTP<http://127.0.0.1:3001/math/compatible/sse> 访问 SSE 接口。
通过 `--stdio` 来调用命令行:
```bash
make prod -- --stdio
```
## 部署
生产:
```bash
make prod
```
构建 Python Wheel 包:
```bash
make build
```
## Docker 部署
运行:
```bash
docker compose up -d
```
仅构建:
```bash
docker compose build
```

View File

@@ -6,7 +6,7 @@ x-default: &default
logging:
driver: json-file
options:
max-size: 1m
max-size: 100m
services:
app:
@@ -16,8 +16,16 @@ services:
dockerfile: Dockerfile
args:
- PORT=${PORT:-3001}
image: ${DOCKER_REGISTRY:-docker.io}/local/mcp-template-python:${BUILD_VERSION:-latest}
image: ${DOCKER_REGISTRY:-docker.io}/${SERVICE_NAME:-mcp-template-python}:${BUILD_VERSION:-latest}
ports:
- "${EXPOSE_PORT:-3001}:${PORT:-3001}"
- "${MCP_PORT_OVERRIDE:-3001}:${PORT:-3001}"
env_file:
- .env
deploy:
resources:
limits:
cpus: '0.50'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M

View File

@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View File

@@ -0,0 +1,24 @@
apiVersion: v2
name: mcp-template-python
description: MCP Template Python - A Model Context Protocol server template
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "latest"

View File

@@ -0,0 +1,232 @@
# MCP Template Python - Helm Chart Configuration Guide
## Quick Start
### 1. Basic Deployment
```bash
# Validate configuration
make helm-lint
# Development environment deployment
make helm-install
# Update deployment
make helm-upgrade
# Uninstall
make helm-uninstall
```
### 2. Production Environment Deployment
```bash
# Initial deployment
make helm-install-prod
# Update deployment
make helm-upgrade-prod
```
## Core Configuration
### Image Configuration
```yaml
image:
repository: docker.io/mcp-template-python
tag: "latest"
pullPolicy: IfNotPresent
```
### Service Configuration
```yaml
service:
type: ClusterIP
port: 3001
targetPort: 3001
```
### Environment Variables
```yaml
env:
MCP_DEFAULT_HOST: "0.0.0.0"
MCP_DEFAULT_PORT: "3001"
```
### Health Checks
```yaml
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 5
```
## Production Environment Configuration
### 1. Resource Configuration (Required)
```yaml
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
```
### 2. Image Registry
```yaml
image:
repository: your-registry.com/mcp-template-python
tag: "v1.0.0"
```
### 3. Auto Scaling
```yaml
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
```
### 4. Ingress Configuration
```yaml
ingress:
enabled: true
className: "nginx"
hosts:
- host: mcp-template-python.your-domain.com
paths:
- path: /
pathType: ImplementationSpecific
```
## Configuration File Management
### ConfigMap Configuration
```yaml
configMap:
enabled: true
data:
app.conf: |
# Application configuration
log_level=info
debug=false
```
### Mounting Configuration Files
Add to `values.yaml`:
```yaml
volumeMounts:
- name: config
mountPath: /app/config
readOnly: true
volumes:
- name: config
configMap:
name: mcp-template-python-config
```
## Security Configuration
Application runs as non-root user:
```yaml
securityContext:
runAsNonRoot: true
runAsUser: 1000
capabilities:
drop:
- ALL
```
## Pre-Deployment Checklist
### Development Environment
- [ ] Update image repository in `values.yaml`
- [ ] Confirm port configuration
- [ ] Set environment variables
### Production Environment
- [ ] Update image tag in `values-production.yaml`
- [ ] Set resource limits and requests
- [ ] Configure domain name and TLS
- [ ] Confirm replica count
- [ ] Check health check paths
## Common Commands
```bash
# Check deployment status
kubectl get pods -l app.kubernetes.io/name=mcp-template-python
# Check service
kubectl get svc mcp-template-python
# View logs
kubectl logs -l app.kubernetes.io/name=mcp-template-python
# Enter container
kubectl exec -it deployment/mcp-template-python -- /bin/bash
# View configuration
helm get values mcp-template-python
```
## Troubleshooting
### Common Issues
1. **Pod Startup Failure**
- Check if image exists
- Confirm resource configuration is reasonable
- View Pod events: `kubectl describe pod <pod-name>`
2. **Health Check Failure**
- Confirm application provides `/health` endpoint
- Check port configuration is correct
- Adjust probe delay times
3. **Service Inaccessible**
- Confirm Service configuration
- Check network policies
- Verify Ingress configuration
### Getting Help
```bash
# View Chart information
helm show chart helm/mcp-template-python
# View all configuration options
helm show values helm/mcp-template-python
# Validate template rendering
helm template mcp-template-python helm/mcp-template-python
```
---
**Note**: Before production deployment, make sure to update the resource configuration and domain settings in `values-production.yaml`.

View File

@@ -0,0 +1,22 @@
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "mcp-template-python.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "mcp-template-python.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "mcp-template-python.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "mcp-template-python.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}

View File

@@ -0,0 +1,62 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "mcp-template-python.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "mcp-template-python.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "mcp-template-python.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "mcp-template-python.labels" -}}
helm.sh/chart: {{ include "mcp-template-python.chart" . }}
{{ include "mcp-template-python.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "mcp-template-python.selectorLabels" -}}
app.kubernetes.io/name: {{ include "mcp-template-python.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "mcp-template-python.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "mcp-template-python.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,12 @@
{{- if .Values.configMap.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "mcp-template-python.fullname" . }}-config
labels:
{{- include "mcp-template-python.labels" . | nindent 4 }}
data:
{{- with .Values.configMap.data }}
{{- toYaml . | nindent 2 }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,83 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "mcp-template-python.fullname" . }}
labels:
{{- include "mcp-template-python.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "mcp-template-python.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "mcp-template-python.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "mcp-template-python.serviceAccountName" . }}
{{- with .Values.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
{{- with .Values.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
env:
{{- range $key, $value := .Values.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
{{- with .Values.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.volumeMounts }}
volumeMounts:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.volumes }}
volumes:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -0,0 +1,32 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "mcp-template-python.fullname" . }}
labels:
{{- include "mcp-template-python.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "mcp-template-python.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,43 @@
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "mcp-template-python.fullname" . }}
labels:
{{- include "mcp-template-python.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- with .Values.ingress.className }}
ingressClassName: {{ . }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
{{- with .pathType }}
pathType: {{ . }}
{{- end }}
backend:
service:
name: {{ include "mcp-template-python.fullname" $ }}
port:
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "mcp-template-python.fullname" . }}
labels:
{{- include "mcp-template-python.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.targetPort | default .Values.service.port }}
protocol: TCP
name: http
selector:
{{- include "mcp-template-python.selectorLabels" . | nindent 4 }}

View File

@@ -0,0 +1,13 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "mcp-template-python.serviceAccountName" . }}
labels:
{{- include "mcp-template-python.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
{{- end }}

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "mcp-template-python.fullname" . }}-test-connection"
labels:
{{- include "mcp-template-python.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "mcp-template-python.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never

View File

@@ -0,0 +1,145 @@
# Default values for mcp-template-python.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
# This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/
replicaCount: 1
# This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/
image:
repository: docker.io/mcp-template-python
# This sets the pull policy for images.
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
# This is for the secrets for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
imagePullSecrets: []
# This is to override the chart name.
nameOverride: ""
fullnameOverride: ""
# This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/
serviceAccount:
# Specifies whether a service account should be created
create: true
# Automatically mount a ServiceAccount's API credentials?
automount: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
# This is for setting Kubernetes Annotations to a Pod.
# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
podAnnotations: {}
# This is for setting Kubernetes Labels to a Pod.
# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
podLabels: {}
podSecurityContext:
fsGroup: 1000
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
runAsNonRoot: true
runAsUser: 1000
# Environment variables
env:
# From .env.example
MCP_DEFAULT_HOST: "0.0.0.0"
MCP_DEFAULT_PORT: "3001"
# This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/
service:
# This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types
type: ClusterIP
# This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports
port: 3001
targetPort: 3001
# This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/
ingress:
enabled: false
className: ""
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: chart-example.local
paths:
- path: /
pathType: ImplementationSpecific
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
# This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
# This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
# Additional volumes on the output Deployment definition.
volumes: []
# - name: foo
# secret:
# secretName: mysecret
# optional: false
# Additional volumeMounts on the output Deployment definition.
volumeMounts: []
# - name: foo
# mountPath: "/etc/foo"
# readOnly: true
nodeSelector: {}
tolerations: []
affinity: {}
# ConfigMap configuration
configMap:
enabled: true
data: {}
# Add any configuration files here

View File

@@ -1,22 +1,21 @@
[project]
name = "mcp-template-python"
version = "0.1.1"
description = "Add your description here"
version = "0.1.3"
description = "MCP Template for Python Projects"
readme = "README.md"
authors = [
{ name = "Sun-ZhenXing", email = "1006925066@qq.com" }
]
requires-python = ">=3.12"
authors = []
requires-python = ">=3.10"
dependencies = [
"fastapi[standard]>=0.116.1",
"mcp[cli]>=1.12.0",
"pydantic-settings>=2.10.1",
"click>=8.0.0",
"fastapi[standard]>=0.115.0",
"mcp[cli]>=1.10.0",
"pydantic>=2.11.7",
"pydantic-settings>=2.5.0",
"rich-toolkit>=0.15.1",
]
[project.scripts]
mcp-template-python = "mcp_template_python.__main__:main"
dev = "mcp_template_python.__main__:dev"
prod = "mcp_template_python.__main__:main"
[build-system]
requires = ["hatchling"]
@@ -27,3 +26,14 @@ path = "src/mcp_template_python/__about__.py"
[tool.hatch.build.targets.sdist]
only-include = ["src/mcp_template_python"]
[tool.ruff.lint]
select = ["E4", "E7", "E9", "F", "TID"]
[tool.ruff.lint.flake8-tidy-imports]
ban-relative-imports = "parents"
[dependency-groups]
dev = [
"ruff>=0.12.7",
]

View File

@@ -1,2 +1,2 @@
__version__ = "0.1.0"
__version__ = "0.1.2"
__module_name__ = "mcp_template_python"

View File

@@ -1,77 +1,95 @@
import argparse
import sys
import click
from .__about__ import __module_name__, __version__
from .app import MCP_MAP
from .config import settings
def main():
parser = argparse.ArgumentParser(description="MCP Server")
parser.add_argument(
"--stdio",
action="store_true",
help="Run the server with STDIO (default: False)",
)
parser.add_argument(
"--host",
default=settings.default_host,
help=f"Host to bind to (default: {settings.default_host})",
)
parser.add_argument(
"--port",
type=int,
default=settings.default_port,
help=f"Port to listen on (default: {settings.default_port})",
)
parser.add_argument(
"--dev",
default=False,
action="store_true",
help="Run the server in development mode (default: False)",
)
parser.add_argument(
"--version",
action="version",
version=f"%(prog)s {__version__}",
help="Show the version of the MCP server",
)
args = parser.parse_args()
if args.dev:
dev(args.host, args.port)
sys.exit(0)
if args.stdio:
mcp = MCP_MAP[settings.default_mcp]
mcp.run()
else:
import uvicorn
from .server import app
uvicorn.run(
app,
host=args.host,
port=args.port,
)
def dev(
def run_server(
module: str | None = None,
host: str = settings.default_host,
port: int = settings.default_port,
reload: bool = False,
**kwargs,
):
"""Run the MCP server in development mode."""
import uvicorn
if module is None:
module = f"{__module_name__}.server:app"
uvicorn.run(
f"{__module_name__}.server:app",
module,
host=host,
port=port,
reload=True,
reload=reload,
log_config=None,
**kwargs,
)
@click.command()
@click.option(
"--stdio",
is_flag=True,
help="Run the server with STDIO (default: False)",
)
@click.option(
"--mcp",
type=click.Choice(list(MCP_MAP.keys()), case_sensitive=False),
default=settings.mcp.default_mcp,
help=f"Select the MCP to run in STDIO mode (default: {settings.mcp.default_mcp})",
)
@click.option(
"--host",
default=settings.default_host,
help=f"Host to bind to (default: {settings.default_host})",
)
@click.option(
"--port",
type=int,
default=settings.default_port,
help=f"Port to listen on (default: {settings.default_port})",
)
@click.option(
"--dev",
is_flag=True,
help="Run the server in development mode (default: False)",
)
@click.version_option(
version=__version__,
prog_name="mcp-template-python",
help="Show the version of the MCP server",
)
def main(
stdio: bool,
mcp: str,
host: str,
port: int,
dev: bool,
):
"""MCP Server"""
if stdio:
selected_mcp = MCP_MAP.get(mcp)
if selected_mcp is None:
click.echo(f"Error: MCP '{mcp}' not found.", err=True)
sys.exit(1)
selected_mcp.run()
else:
run_server(
host=host,
port=port,
reload=dev,
)
@click.command()
def dev():
"""Run the server in development mode."""
run_server(reload=True)
if __name__ == "__main__":
main()

View File

@@ -2,38 +2,30 @@ from operator import add, mul, sub, truediv
from mcp.server.fastmcp import FastMCP
from ..config import settings
from mcp_template_python.config import settings
mcp = FastMCP("math", settings=settings.instructions)
mcp = FastMCP("math", instructions=settings.mcp.instructions)
@mcp.tool()
async def add_nums(a: float, b: float) -> float:
"""
Adds two numbers.
"""
async def add_num(a: float, b: float) -> float:
"""Adds two numbers."""
return add(a, b)
@mcp.tool()
async def sub_nums(a: float, b: float) -> float:
"""
Subtracts the second number from the first.
"""
async def sub_num(a: float, b: float) -> float:
"""Subtracts the second number from the first."""
return sub(a, b)
@mcp.tool()
async def mul_nums(a: float, b: float) -> float:
"""
Multiplies two numbers.
"""
async def mul_num(a: float, b: float) -> float:
"""Multiplies two numbers."""
return mul(a, b)
@mcp.tool()
async def div_nums(a: float, b: float) -> float:
"""
Divides the first number by the second.
"""
async def div_num(a: float, b: float) -> float:
"""Divides the first number by the second."""
return truediv(a, b)

View File

@@ -1,23 +0,0 @@
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
"""
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(
env_prefix="MCP_",
env_file=".env",
env_file_encoding="utf-8",
extra="allow",
)
settings = Settings()

View File

@@ -0,0 +1,3 @@
from .app import settings
__all__ = ["settings"]

View File

@@ -0,0 +1,46 @@
from dotenv import load_dotenv
from pydantic_settings import BaseSettings, SettingsConfigDict
from mcp_template_python.config.cors import CORSSettings
from .mcp import MCPSettings
load_dotenv()
class AppSettings(BaseSettings):
"""
Configuration settings for the MCP template application.
"""
model_config = SettingsConfigDict(
env_prefix="APP_",
extra="allow",
)
mcp: MCPSettings = MCPSettings()
"""MCP settings, defaults to MCPSettings()."""
cors: CORSSettings = CORSSettings()
"""CORS settings, defaults to CORSSettings()."""
title: str = "MCP Template Application"
"""Title of the MCP application, defaults to 'MCP Template Application'."""
description: str = "A template application for MCP using FastAPI."
"""Description of the MCP application, defaults to 'A template application for MCP using FastAPI.'"""
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."""
log_level: str = "INFO"
"""Logging level for the MCP server, defaults to 'info'."""
rich_console: bool = False
"""Enable rich console output, defaults to False."""
settings = AppSettings()

View File

@@ -0,0 +1,24 @@
from pydantic_settings import BaseSettings, SettingsConfigDict
class CORSSettings(BaseSettings):
"""
Configuration settings for CORS (Cross-Origin Resource Sharing).
"""
model_config = SettingsConfigDict(
env_prefix="CORS_",
extra="allow",
)
allow_origins: str = "*"
"""CORS allow origins, defaults to '*'."""
allow_credentials: bool = True
"""CORS allow credentials, defaults to True."""
allow_methods: str = "*"
"""CORS allow methods, defaults to '*'."""
allow_headers: str = "*"
"""CORS allow headers, defaults to '*'."""

View File

@@ -0,0 +1,27 @@
from pydantic_settings import BaseSettings, SettingsConfigDict
class MCPSettings(BaseSettings):
"""
Configuration settings for the MCP template application.
"""
model_config = SettingsConfigDict(
env_prefix="MCP_",
extra="allow",
)
default_mcp: str = "math"
"""Default MCP to be used by the application."""
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."""

View File

@@ -1,7 +1,7 @@
from fastapi import APIRouter
from ..app import MCP_MAP
from ..models.helpers import ArgumentsRequest
from mcp_template_python.app import MCP_MAP
from mcp_template_python.models.helpers import ArgumentsRequest
router = APIRouter(prefix="/v1", tags=["helpers"])

View File

@@ -1,8 +1,11 @@
import contextlib
from fastapi import FastAPI
from fastapi import FastAPI, Response
from fastapi.middleware.cors import CORSMiddleware
from .__about__ import __version__
from .app import MCP_MAP
from .config import settings
from .routers.helpers import router as helpers_router
@@ -14,13 +17,26 @@ async def lifespan(app: FastAPI):
yield
app = FastAPI(lifespan=lifespan)
app = FastAPI(
title=settings.title,
description=settings.description,
version=__version__,
lifespan=lifespan,
)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors.allow_origins.split(","),
allow_credentials=settings.cors.allow_credentials,
allow_methods=settings.cors.allow_methods.split(","),
allow_headers=settings.cors.allow_headers.split(","),
)
@app.get("/")
async def root():
"""Root endpoint."""
return {"message": "Welcome!"}
return Response("<script>location.href='/docs'</script>")
@app.get("/health")
@@ -28,12 +44,14 @@ async def health():
"""Check the health of the server and list available tools."""
return {
"status": "healthy",
"tools": list(MCP_MAP.keys()),
}
app.include_router(helpers_router)
if settings.mcp.enable_helpers_router:
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())
if settings.mcp.enable_sse:
app.mount(f"/{name}/compatible", mcp.sse_app())
if settings.mcp.enable_streamable_http:
app.mount(f"/{name}", mcp.streamable_http_app())

1226
uv.lock generated

File diff suppressed because it is too large Load Diff