serverz-mcp 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- serverz_mcp-0.1.1/PKG-INFO +13 -0
- serverz_mcp-0.1.1/README.md +0 -0
- serverz_mcp-0.1.1/pyproject.toml +14 -0
- serverz_mcp-0.1.1/setup.cfg +4 -0
- serverz_mcp-0.1.1/src/serverz_mcp/__init__.py +13 -0
- serverz_mcp-0.1.1/src/serverz_mcp/log.py +112 -0
- serverz_mcp-0.1.1/src/serverz_mcp/server/__init__.py +0 -0
- serverz_mcp-0.1.1/src/serverz_mcp/server/__main__.py +148 -0
- serverz_mcp-0.1.1/src/serverz_mcp/server/mcp/__init__.py +5 -0
- serverz_mcp-0.1.1/src/serverz_mcp/server/mcp/content.py +113 -0
- serverz_mcp-0.1.1/src/serverz_mcp/server/mcp/math.py +33 -0
- serverz_mcp-0.1.1/src/serverz_mcp/server/mcp/mcp.py +51 -0
- serverz_mcp-0.1.1/src/serverz_mcp/server/mcp/resource.py +35 -0
- serverz_mcp-0.1.1/src/serverz_mcp/server/mcp/weather.py +25 -0
- serverz_mcp-0.1.1/src/serverz_mcp/server/models/__init__.py +0 -0
- serverz_mcp-0.1.1/src/serverz_mcp/server/models/models.py +48 -0
- serverz_mcp-0.1.1/src/serverz_mcp/server/routers/__init__.py +2 -0
- serverz_mcp-0.1.1/src/serverz_mcp/server/routers/admin.py +42 -0
- serverz_mcp-0.1.1/src/serverz_mcp/server/routers/user.py +24 -0
- serverz_mcp-0.1.1/src/serverz_mcp/server/utils/__init__.py +3 -0
- serverz_mcp-0.1.1/src/serverz_mcp/server/utils/auth_backends.py +15 -0
- serverz_mcp-0.1.1/src/serverz_mcp/server/utils/database.py +27 -0
- serverz_mcp-0.1.1/src/serverz_mcp/server/utils/user_manager.py +87 -0
- serverz_mcp-0.1.1/src/serverz_mcp/utils.py +1 -0
- serverz_mcp-0.1.1/src/serverz_mcp.egg-info/PKG-INFO +13 -0
- serverz_mcp-0.1.1/src/serverz_mcp.egg-info/SOURCES.txt +28 -0
- serverz_mcp-0.1.1/src/serverz_mcp.egg-info/dependency_links.txt +1 -0
- serverz_mcp-0.1.1/src/serverz_mcp.egg-info/requires.txt +7 -0
- serverz_mcp-0.1.1/src/serverz_mcp.egg-info/top_level.txt +1 -0
- serverz_mcp-0.1.1/tests/test_main.py +0 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: serverz-mcp
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: anyio>=4.11.0
|
|
8
|
+
Requires-Dist: fastapi>=0.121.1
|
|
9
|
+
Requires-Dist: pytest>=9.0.0
|
|
10
|
+
Requires-Dist: pytest-asyncio>=1.3.0
|
|
11
|
+
Requires-Dist: pytest-tornasync>=0.6.0.post2
|
|
12
|
+
Requires-Dist: toml>=0.10.2
|
|
13
|
+
Requires-Dist: uvicorn>=0.38.0
|
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "serverz-mcp"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [ "anyio>=4.11.0", "fastapi>=0.121.1", "pytest>=9.0.0", "pytest-asyncio>=1.3.0", "pytest-tornasync>=0.6.0.post2", "toml>=0.10.2", "uvicorn>=0.38.0",]
|
|
8
|
+
|
|
9
|
+
[tool.setuptools.package-data]
|
|
10
|
+
serverz_mcp = [ "config.yaml",]
|
|
11
|
+
|
|
12
|
+
[tool.pytest.ini_options]
|
|
13
|
+
testpaths = [ "tests",]
|
|
14
|
+
pythonpath = [ "src",]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from dotenv import load_dotenv, find_dotenv
|
|
2
|
+
|
|
3
|
+
dotenv_path = find_dotenv()
|
|
4
|
+
load_dotenv(".env", override=True)
|
|
5
|
+
|
|
6
|
+
from .log import Log
|
|
7
|
+
import logging
|
|
8
|
+
Log_ = Log(console_level = logging.INFO, # 显示控制台的等级
|
|
9
|
+
log_file_name="app.log")
|
|
10
|
+
logger = Log_.logger
|
|
11
|
+
|
|
12
|
+
inference_save_case = False
|
|
13
|
+
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
|
|
4
|
+
import inspect
|
|
5
|
+
class Log:
|
|
6
|
+
_instance = None
|
|
7
|
+
def __new__(cls, *args, **kwargs):
|
|
8
|
+
if cls._instance is None:
|
|
9
|
+
cls._instance = super().__new__(cls)
|
|
10
|
+
return cls._instance
|
|
11
|
+
|
|
12
|
+
def __init__(self, console_level = logging.INFO, log_file_name="app.log"):
|
|
13
|
+
self.Console_LOG_LEVEL = console_level
|
|
14
|
+
self.log_file_name = log_file_name
|
|
15
|
+
self.LOG_FILE_PATH = os.path.join("logs", log_file_name)
|
|
16
|
+
os.makedirs(os.path.dirname(self.LOG_FILE_PATH), exist_ok=True)
|
|
17
|
+
self.logger = self.get_logger()
|
|
18
|
+
|
|
19
|
+
def get_logger(self):
|
|
20
|
+
logger = logging.getLogger()
|
|
21
|
+
logger.setLevel(logging.DEBUG)
|
|
22
|
+
if not logger.handlers:
|
|
23
|
+
# --- 4. 配置 Formatter (格式化器) ---
|
|
24
|
+
# 以后有一个标准化的日志要使用logger 而非标的则使用super-log
|
|
25
|
+
formatter = logging.Formatter(
|
|
26
|
+
"%(asctime)s $ %(created)f $ %(levelname)s $ %(funcName)s $ :%(lineno)d $ %(pathname)s $ %(message)s||"
|
|
27
|
+
)
|
|
28
|
+
# --- 5. 配置 Handler (处理器) ---
|
|
29
|
+
|
|
30
|
+
# 5.1 控制台处理器 (StreamHandler)
|
|
31
|
+
console_handler = logging.StreamHandler()
|
|
32
|
+
console_handler.setLevel(self.Console_LOG_LEVEL) # 控制台只显示 INFO 及以上级别的日志
|
|
33
|
+
console_handler.setFormatter(formatter)
|
|
34
|
+
logger.addHandler(console_handler)
|
|
35
|
+
|
|
36
|
+
# 文件系统
|
|
37
|
+
## 主日志本
|
|
38
|
+
file_handler = RotatingFileHandler( # RotatingFileHandler: 按文件大小轮转
|
|
39
|
+
self.LOG_FILE_PATH,
|
|
40
|
+
maxBytes=10 * 1024 * 1024, # 10 MB # maxBytes: 单个日志文件的最大字节数 (例如 10MB)
|
|
41
|
+
backupCount=10, # backupCount: 保留的旧日志文件数量
|
|
42
|
+
encoding="utf-8",
|
|
43
|
+
)
|
|
44
|
+
file_handler.setLevel(logging.DEBUG) # 文件中显示所有指定级别的日志
|
|
45
|
+
file_handler.setFormatter(formatter)
|
|
46
|
+
logger.addHandler(file_handler)
|
|
47
|
+
|
|
48
|
+
## 运行日志本
|
|
49
|
+
file_handler_info = RotatingFileHandler(
|
|
50
|
+
self.LOG_FILE_PATH.replace('.log','_info.log'),
|
|
51
|
+
maxBytes=10 * 1024 * 1024, # 10 MB
|
|
52
|
+
backupCount=5,
|
|
53
|
+
encoding="utf-8",
|
|
54
|
+
)
|
|
55
|
+
file_handler_info.setLevel(logging.INFO) # 文件中显示所有指定级别的日志
|
|
56
|
+
file_handler_info.setFormatter(formatter)
|
|
57
|
+
logger.addHandler(file_handler_info)
|
|
58
|
+
|
|
59
|
+
## 错误日志本
|
|
60
|
+
file_handler_warning = RotatingFileHandler(
|
|
61
|
+
self.LOG_FILE_PATH.replace('.log','_err.log'),
|
|
62
|
+
maxBytes=10 * 1024 * 1024, # 10 MB
|
|
63
|
+
backupCount=5,
|
|
64
|
+
encoding="utf-8",
|
|
65
|
+
)
|
|
66
|
+
file_handler_warning.setLevel(logging.WARNING) # 文件中显示所有指定级别的日志
|
|
67
|
+
file_handler_warning.setFormatter(formatter)
|
|
68
|
+
logger.addHandler(file_handler_warning)
|
|
69
|
+
|
|
70
|
+
## 指定日志本
|
|
71
|
+
file_handler_super = RotatingFileHandler(
|
|
72
|
+
self.LOG_FILE_PATH.replace('.log','_s.log'),
|
|
73
|
+
maxBytes=5 * 1024 * 1024, # 10 MB
|
|
74
|
+
backupCount=5,
|
|
75
|
+
encoding="utf-8",
|
|
76
|
+
)
|
|
77
|
+
file_handler_super.setLevel(logging.CRITICAL) # 文件中显示所有指定级别的日志
|
|
78
|
+
file_handler_super.setFormatter(formatter)
|
|
79
|
+
logger.addHandler(file_handler_super)
|
|
80
|
+
|
|
81
|
+
# class ConfigFilter(logging.Filter):
|
|
82
|
+
# # 初始化的方案
|
|
83
|
+
# def __init__(self, config_value, name=''):
|
|
84
|
+
# super().__init__(name)
|
|
85
|
+
# self.config_value = config_value
|
|
86
|
+
|
|
87
|
+
# def filter(self, record):
|
|
88
|
+
# record.config_info = self.config_value # 添加到LogRecord
|
|
89
|
+
# return True
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# class CustomFilter(logging.Filter):
|
|
93
|
+
# def filter(self, record):
|
|
94
|
+
# # 添加自定义的用户ID
|
|
95
|
+
# print(record,'recordrecordrecordrecordrecord')
|
|
96
|
+
# record.user_id = os.getenv("CURRENT_USER_ID", "anonymous")
|
|
97
|
+
# # 添加会话ID
|
|
98
|
+
# # record.session_id = "SESSION_XYZ123" # 实际应用中可能从request或全局变量获取
|
|
99
|
+
# frame = inspect.currentframe()
|
|
100
|
+
# info = inspect.getframeinfo(frame)
|
|
101
|
+
# # logger.error(f"Function name: {info.function} : {e}")
|
|
102
|
+
# record.session_id = f"Function name: {info.function}"
|
|
103
|
+
|
|
104
|
+
# return True # 必须返回True才能继续处理该日志记录
|
|
105
|
+
|
|
106
|
+
# # 将自定义过滤器添加到处理器或记录器
|
|
107
|
+
# logger.addFilter(CustomFilter())
|
|
108
|
+
# handler.addFilter(CustomFilter()) # 也可以加到handler上
|
|
109
|
+
|
|
110
|
+
logger.info("这是一个包含自定义信息的日志")
|
|
111
|
+
return logger
|
|
112
|
+
|
|
File without changes
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# server
|
|
2
|
+
from fastapi import FastAPI, HTTPException
|
|
3
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
4
|
+
from dotenv import load_dotenv, find_dotenv
|
|
5
|
+
from contextlib import asynccontextmanager, AsyncExitStack
|
|
6
|
+
from fastapi import FastAPI, Request
|
|
7
|
+
from fastapi.routing import APIRoute
|
|
8
|
+
import argparse
|
|
9
|
+
import uvicorn
|
|
10
|
+
|
|
11
|
+
from .mcp.math import mcp as fm_math
|
|
12
|
+
from .mcp.weather import mcp as fm_weather
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
default = 8007
|
|
16
|
+
|
|
17
|
+
dotenv_path = find_dotenv()
|
|
18
|
+
load_dotenv(dotenv_path, override=True)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Combine both lifespans
|
|
22
|
+
@asynccontextmanager
|
|
23
|
+
async def combined_lifespan(app: FastAPI):
|
|
24
|
+
# Run both lifespans
|
|
25
|
+
async with AsyncExitStack() as stack:
|
|
26
|
+
await stack.enter_async_context(fm_math.session_manager.run())
|
|
27
|
+
await stack.enter_async_context(fm_weather.session_manager.run())
|
|
28
|
+
yield
|
|
29
|
+
|
|
30
|
+
app = FastAPI(
|
|
31
|
+
title="LLM Service",
|
|
32
|
+
description="Provides an OpenAI-compatible API for custom large language models.",
|
|
33
|
+
version="1.0.1",
|
|
34
|
+
# debug=True,
|
|
35
|
+
# docs_url="/api-docs",
|
|
36
|
+
lifespan=combined_lifespan
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# --- Configure CORS ---
|
|
40
|
+
origins = [
|
|
41
|
+
"*",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
app.add_middleware(
|
|
45
|
+
CORSMiddleware,
|
|
46
|
+
allow_origins=origins, # Specifies the allowed origins
|
|
47
|
+
allow_credentials=True, # Allows cookies/authorization headers
|
|
48
|
+
allow_methods=["*"], # Allows all methods (GET, POST, OPTIONS, etc.)
|
|
49
|
+
allow_headers=["*"], # Allows all headers (Content-Type, Authorization, etc.)
|
|
50
|
+
)
|
|
51
|
+
# --- End CORS Configuration ---
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
app.mount("/math", fm_math.streamable_http_app()) # /math/mcp
|
|
57
|
+
app.mount("/weather", fm_weather.streamable_http_app()) # /weather/mcp
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@app.get("/")
|
|
64
|
+
async def root():
|
|
65
|
+
"""server run"""
|
|
66
|
+
return {"message": "LLM Service is running."}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@app.get("/api/status")
|
|
70
|
+
def status():
|
|
71
|
+
return {"status": "ok"}
|
|
72
|
+
|
|
73
|
+
@app.get("/api/list-routes/")
|
|
74
|
+
async def list_fastapi_routes(request: Request):
|
|
75
|
+
routes_data = []
|
|
76
|
+
for route in request.app.routes:
|
|
77
|
+
if isinstance(route, APIRoute):
|
|
78
|
+
routes_data.append({
|
|
79
|
+
"path": route.path,
|
|
80
|
+
"name": route.name,
|
|
81
|
+
"methods": list(route.methods),
|
|
82
|
+
"endpoint": route.endpoint.__name__ # Get the name of the function
|
|
83
|
+
})
|
|
84
|
+
return {"routes": routes_data}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
if __name__ == "__main__":
|
|
89
|
+
# 这是一个标准的 Python 入口点惯用法
|
|
90
|
+
# 当脚本直接运行时 (__name__ == "__main__"),这里的代码会被执行
|
|
91
|
+
# 当通过 python -m YourPackageName 执行 __main__.py 时,__name__ 也是 "__main__"
|
|
92
|
+
# 27
|
|
93
|
+
|
|
94
|
+
parser = argparse.ArgumentParser(
|
|
95
|
+
description="Start a simple HTTP server similar to http.server."
|
|
96
|
+
)
|
|
97
|
+
parser.add_argument(
|
|
98
|
+
"port",
|
|
99
|
+
metavar="PORT",
|
|
100
|
+
type=int,
|
|
101
|
+
nargs="?", # 端口是可选的
|
|
102
|
+
default=default,
|
|
103
|
+
help=f"Specify alternate port [default: {default}]",
|
|
104
|
+
)
|
|
105
|
+
# 创建一个互斥组用于环境选择
|
|
106
|
+
group = parser.add_mutually_exclusive_group()
|
|
107
|
+
|
|
108
|
+
# 添加 --dev 选项
|
|
109
|
+
group.add_argument(
|
|
110
|
+
"--dev",
|
|
111
|
+
action="store_true", # 当存在 --dev 时,该值为 True
|
|
112
|
+
help="Run in development mode (default).",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# 添加 --prod 选项
|
|
116
|
+
group.add_argument(
|
|
117
|
+
"--prod",
|
|
118
|
+
action="store_true", # 当存在 --prod 时,该值为 True
|
|
119
|
+
help="Run in production mode.",
|
|
120
|
+
)
|
|
121
|
+
args = parser.parse_args()
|
|
122
|
+
|
|
123
|
+
if args.prod:
|
|
124
|
+
env = "prod"
|
|
125
|
+
else:
|
|
126
|
+
# 如果 --prod 不存在,默认就是 dev
|
|
127
|
+
env = "dev"
|
|
128
|
+
|
|
129
|
+
port = args.port
|
|
130
|
+
|
|
131
|
+
if env == "dev":
|
|
132
|
+
port += 100
|
|
133
|
+
reload = True
|
|
134
|
+
app_import_string = (
|
|
135
|
+
f"{__package__}.__main__:app" # <--- 关键修改:传递导入字符串
|
|
136
|
+
)
|
|
137
|
+
elif env == "prod":
|
|
138
|
+
reload = False
|
|
139
|
+
app_import_string = app
|
|
140
|
+
else:
|
|
141
|
+
reload = False
|
|
142
|
+
app_import_string = app
|
|
143
|
+
|
|
144
|
+
# 使用 uvicorn.run() 来启动服务器
|
|
145
|
+
# 参数对应于命令行选项
|
|
146
|
+
uvicorn.run(
|
|
147
|
+
app_import_string, host="0.0.0.0", port=port, reload=reload # 启用热重载
|
|
148
|
+
)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
|
|
2
|
+
from mcp.server.fastmcp import Context, FastMCP, Icon
|
|
3
|
+
from prompt_writing_assistant.file_manager import ContentManager, TextType
|
|
4
|
+
from mcp.server.fastmcp import FastMCP
|
|
5
|
+
from mcp.server.fastmcp.prompts import base
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
from prompt_writing_assistant.prompt_helper import IntellectType,Intel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
content_manager = ContentManager()
|
|
11
|
+
intels = Intel()
|
|
12
|
+
|
|
13
|
+
mcp = FastMCP("Content")
|
|
14
|
+
|
|
15
|
+
@mcp.prompt(title="Code Review")
|
|
16
|
+
def review_code(code: str) -> str:
|
|
17
|
+
return f"Please review this code:\n\n{code}"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@mcp.prompt(title="Debug Assistant")
|
|
21
|
+
def debug_error(error: str) -> list[base.Message]:
|
|
22
|
+
return [
|
|
23
|
+
base.UserMessage("I'm seeing this error:"),
|
|
24
|
+
base.UserMessage(error),
|
|
25
|
+
base.AssistantMessage("I'll help debug that. What have you tried so far?"),
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@mcp.tool()
|
|
30
|
+
def save_content(text:str)->str:
|
|
31
|
+
"""
|
|
32
|
+
text : 需要存储的内容
|
|
33
|
+
return : 返回是否存储成功的信息
|
|
34
|
+
"""
|
|
35
|
+
result =content_manager.save_content_auto(
|
|
36
|
+
text = text)
|
|
37
|
+
return result
|
|
38
|
+
|
|
39
|
+
@mcp.tool()
|
|
40
|
+
def similarit_content(text: str,limit: int):
|
|
41
|
+
"""
|
|
42
|
+
text : 待查询的文字, 进行匹配
|
|
43
|
+
limit : 查询的数量
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
result = content_manager.similarity(
|
|
47
|
+
content = text,
|
|
48
|
+
limit = limit
|
|
49
|
+
)
|
|
50
|
+
return result
|
|
51
|
+
|
|
52
|
+
@mcp.tool()
|
|
53
|
+
def get_prompts(prompt_id: str,version: str = "1.0"):
|
|
54
|
+
"""
|
|
55
|
+
prompt_id : 待查询的文字, 进行匹配
|
|
56
|
+
limit : 查询的数量
|
|
57
|
+
"""
|
|
58
|
+
result = intels.get_prompts_from_sql(
|
|
59
|
+
table_name = "prompts_data",
|
|
60
|
+
prompt_id = prompt_id,
|
|
61
|
+
version=version,
|
|
62
|
+
)
|
|
63
|
+
return result
|
|
64
|
+
|
|
65
|
+
@mcp.tool()
|
|
66
|
+
def list_cities() -> list[str]:
|
|
67
|
+
"""Get a list of cities"""
|
|
68
|
+
return ["London", "Paris", "Tokyo"]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class WeatherData(BaseModel):
|
|
76
|
+
"""Weather information structure."""
|
|
77
|
+
|
|
78
|
+
temperature: float = Field(description="Temperature in Celsius")
|
|
79
|
+
humidity: float = Field(description="Humidity percentage")
|
|
80
|
+
condition: str
|
|
81
|
+
wind_speed: float
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@mcp.tool() # icons=[icon]
|
|
85
|
+
def get_weather(city: str) -> WeatherData:
|
|
86
|
+
"""Get weather for a city - returns structured data."""
|
|
87
|
+
# Simulated weather data
|
|
88
|
+
return WeatherData(
|
|
89
|
+
temperature=22.5,
|
|
90
|
+
humidity=45.0,
|
|
91
|
+
condition="sunny",
|
|
92
|
+
wind_speed=5.2,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
from PIL import Image as PILImage
|
|
97
|
+
from mcp.server.fastmcp import FastMCP, Image
|
|
98
|
+
|
|
99
|
+
@mcp.tool()
|
|
100
|
+
def create_thumbnail(image_path: str) -> Image:
|
|
101
|
+
"""Create a thumbnail from an image"""
|
|
102
|
+
img = PILImage.open(image_path)
|
|
103
|
+
img.thumbnail((100, 100))
|
|
104
|
+
return Image(data=img.tobytes(), format="png")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
if __name__ == "__main__":
|
|
109
|
+
mcp.run(transport="streamable-http")
|
|
110
|
+
# mcp.run(transport="sse")
|
|
111
|
+
# search_mcp.run(transport="sse", mount_path="/search")
|
|
112
|
+
|
|
113
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from mcp.server.fastmcp import FastMCP
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# region MCP Math
|
|
5
|
+
mcp = FastMCP("Math")
|
|
6
|
+
|
|
7
|
+
@mcp.tool(description="A simple add tool")
|
|
8
|
+
def add(a: int, b: int) -> int:
|
|
9
|
+
"""Adds two integers.
|
|
10
|
+
Args:
|
|
11
|
+
a: The first integer.
|
|
12
|
+
b: The second integer.
|
|
13
|
+
"""
|
|
14
|
+
return a + b
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@mcp.tool()
|
|
18
|
+
def multiply(a: int, b: int) -> int:
|
|
19
|
+
"""Multiply two integers.
|
|
20
|
+
Args:
|
|
21
|
+
a: The first integer.
|
|
22
|
+
b: The second integer.
|
|
23
|
+
"""
|
|
24
|
+
return a * b
|
|
25
|
+
# endregion
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
if __name__ == "__main__":
|
|
29
|
+
mcp.run(transport="streamable-http")
|
|
30
|
+
# mcp.run(transport="sse")
|
|
31
|
+
# search_mcp.run(transport="sse", mount_path="/search")
|
|
32
|
+
|
|
33
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastMCP quickstart example.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from mcp.server.fastmcp import FastMCP
|
|
7
|
+
from prompt_writing_assistant.file_manager import ContentManager, TextType
|
|
8
|
+
|
|
9
|
+
# uv run mcp dev src/obsidian_sdk/mcp.py
|
|
10
|
+
# Create an MCP server
|
|
11
|
+
mcp = FastMCP("Demo")
|
|
12
|
+
content_manager = ContentManager()
|
|
13
|
+
|
|
14
|
+
@mcp.tool()
|
|
15
|
+
def save_content(text:str)->str:
|
|
16
|
+
"""
|
|
17
|
+
text : 需要存储的内容
|
|
18
|
+
|
|
19
|
+
return : 返回是否存储成功的信息
|
|
20
|
+
"""
|
|
21
|
+
result =content_manager.save_content_auto(
|
|
22
|
+
text = text)
|
|
23
|
+
return result
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@mcp.tool()
|
|
27
|
+
def similarit_content(text: str,limit: int):
|
|
28
|
+
"""
|
|
29
|
+
text : 待查询的文字, 进行匹配
|
|
30
|
+
limit : 查询的数量
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
result = content_manager.similarity(
|
|
34
|
+
content = text,
|
|
35
|
+
limit = limit
|
|
36
|
+
)
|
|
37
|
+
return result
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@mcp.resource("file://notes/{name}")
|
|
42
|
+
def read_notes(name:str)-> str:
|
|
43
|
+
""" 阅读备忘录"""
|
|
44
|
+
|
|
45
|
+
return "你要做王子"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
# mcp.run(transport="streamable-http")
|
|
50
|
+
mcp.run(transport="sse")
|
|
51
|
+
# search_mcp.run(transport="sse", mount_path="/search")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
|
|
2
|
+
from mcp.server.fastmcp import Context, FastMCP
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# region MCP Math
|
|
6
|
+
mcp = FastMCP("resource")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@mcp.resource("config://settings")
|
|
10
|
+
def get_settings() -> str:
|
|
11
|
+
"""Get application settings."""
|
|
12
|
+
return """{
|
|
13
|
+
"theme": "dark",
|
|
14
|
+
"language": "en",
|
|
15
|
+
"debug": false
|
|
16
|
+
}"""
|
|
17
|
+
|
|
18
|
+
@mcp.resource("file://documents/{name}")
|
|
19
|
+
def read_document(name: str) -> str:
|
|
20
|
+
"""Read a document by name."""
|
|
21
|
+
# This would normally read from disk
|
|
22
|
+
path = "/Users/zhaoxuefeng/GitHub/prompt_writing_assistant/tests/temp_file/"
|
|
23
|
+
path += name
|
|
24
|
+
with open(path,'r') as f:
|
|
25
|
+
return f.read()
|
|
26
|
+
|
|
27
|
+
@mcp.resource("file://notes/{name}")
|
|
28
|
+
def read_notes(name:str)-> str:
|
|
29
|
+
""" 阅读备忘录"""
|
|
30
|
+
|
|
31
|
+
return "你要做王子"
|
|
32
|
+
|
|
33
|
+
if __name__ == "__main__":
|
|
34
|
+
mcp.run(transport="streamable-http")
|
|
35
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from mcp.server.fastmcp import FastMCP
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def create_mcp():
|
|
6
|
+
# region MCP Weather
|
|
7
|
+
mcp = FastMCP("Weather")
|
|
8
|
+
|
|
9
|
+
@mcp.tool()
|
|
10
|
+
def get_weather(location: str) -> str:
|
|
11
|
+
return "Cloudy"
|
|
12
|
+
|
|
13
|
+
@mcp.tool()
|
|
14
|
+
def get_time() -> str:
|
|
15
|
+
return datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
16
|
+
# endregion
|
|
17
|
+
return mcp
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
if __name__ == "__main__":
|
|
22
|
+
mcp = create_mcp()
|
|
23
|
+
mcp.run(transport="streamable-http")
|
|
24
|
+
|
|
25
|
+
|
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
|
|
2
|
+
import uuid
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from sqlalchemy import String, Boolean, Column
|
|
6
|
+
from sqlalchemy.dialects.mysql import CHAR # Use CHAR for UUID to store as fixed length string
|
|
7
|
+
from fastapi_users.db import SQLAlchemyBaseUserTableUUID
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from umanager.server.utils import Base # 引入Base
|
|
11
|
+
|
|
12
|
+
# SQLAlchemy 用户模型
|
|
13
|
+
# 使用 SQLAlchemyBaseUserTableUUID 来自动处理 UUID 主键
|
|
14
|
+
class User(SQLAlchemyBaseUserTableUUID, Base):
|
|
15
|
+
__tablename__ = "users" # 表名
|
|
16
|
+
|
|
17
|
+
# 可以在这里添加额外的用户属性
|
|
18
|
+
first_name: str = Column(String(255), nullable=True)
|
|
19
|
+
last_name: str = Column(String(255), nullable=True)
|
|
20
|
+
is_admin: bool = Column(Boolean, default=False)
|
|
21
|
+
|
|
22
|
+
# Pydantic 模型 (用于请求和响应)
|
|
23
|
+
class UserRead(BaseModel):
|
|
24
|
+
id: uuid.UUID
|
|
25
|
+
email: str
|
|
26
|
+
is_active: bool = True
|
|
27
|
+
is_superuser: bool = False
|
|
28
|
+
is_verified: bool = False
|
|
29
|
+
first_name: Optional[str] = None
|
|
30
|
+
last_name: Optional[str] = None
|
|
31
|
+
is_admin: bool = False
|
|
32
|
+
|
|
33
|
+
class Config:
|
|
34
|
+
from_attributes = True # updated from orm_mode = True
|
|
35
|
+
|
|
36
|
+
class UserCreate(BaseModel):
|
|
37
|
+
email: str
|
|
38
|
+
password: str
|
|
39
|
+
first_name: Optional[str] = None
|
|
40
|
+
last_name: Optional[str] = None
|
|
41
|
+
|
|
42
|
+
class UserUpdate(UserCreate):
|
|
43
|
+
is_active: Optional[bool] = None
|
|
44
|
+
is_superuser: Optional[bool] = None
|
|
45
|
+
is_verified: Optional[bool] = None
|
|
46
|
+
first_name: Optional[str] = None
|
|
47
|
+
last_name: Optional[str] = None
|
|
48
|
+
is_admin: Optional[bool] = None
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
|
|
2
|
+
# app/routers/admin.py
|
|
3
|
+
from fastapi import APIRouter, Depends, HTTPException, status
|
|
4
|
+
from fastapi import FastAPI, HTTPException, Header
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# 模拟获取当前用户的依赖项
|
|
8
|
+
async def get_current_user(x_token: str = Header(...)):
|
|
9
|
+
if x_token != "valid-secret-token":
|
|
10
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid X-Token header")
|
|
11
|
+
return {"username": "admin", "roles": ["user", "admin"]}
|
|
12
|
+
|
|
13
|
+
# 模拟一个管理员权限检查的依赖项
|
|
14
|
+
async def verify_admin_role(user: dict = Depends(get_current_user)):
|
|
15
|
+
if "admin" not in user.get("roles", []):
|
|
16
|
+
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not enough permissions")
|
|
17
|
+
return user
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
router = APIRouter(
|
|
21
|
+
tags=["Admin"],
|
|
22
|
+
dependencies=[Depends(get_current_user), Depends(verify_admin_role)] # 统一的依赖项列表
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@router.get("/dashboard/")
|
|
28
|
+
async def get_admin_dashboard_data(user):
|
|
29
|
+
# 这个函数会自动接收到 get_current_user 和 verify_admin_role 的结果(如果需要)
|
|
30
|
+
# 但我们在这里不需要显式接收它们,因为它们只是做权限检查
|
|
31
|
+
print(user,'user')
|
|
32
|
+
return {"message": "Welcome to the admin dashboard!"}
|
|
33
|
+
|
|
34
|
+
@router.post("/settings/")
|
|
35
|
+
async def update_admin_settings(settings: dict):
|
|
36
|
+
# 这个函数也会自动执行 get_current_user 和 verify_admin_role
|
|
37
|
+
return {"message": "Admin settings updated", "settings": settings}
|
|
38
|
+
|
|
39
|
+
# 这个API也需要认证和管理员权限
|
|
40
|
+
@router.delete("/users/{user_id}")
|
|
41
|
+
async def delete_user(user_id: int):
|
|
42
|
+
return {"message": f"User {user_id} deleted by admin."}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
# app/routers/admin.py
|
|
3
|
+
from fastapi import APIRouter, Depends, HTTPException, status
|
|
4
|
+
from fastapi import FastAPI, Header
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# 模拟获取当前用户的依赖项
|
|
8
|
+
async def get_current_user(x_token: str = Header(...)):
|
|
9
|
+
if x_token not in ["1234",
|
|
10
|
+
"5678"]:
|
|
11
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid X-Token header")
|
|
12
|
+
return {"username": "admin", "roles": ["user", "admin"]}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
router = APIRouter(
|
|
16
|
+
tags=["users"],
|
|
17
|
+
dependencies=[Depends(get_current_user)] # 统一的依赖项列表
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@router.get("/dashboard")
|
|
22
|
+
async def get_user(user_info :dict = Depends(get_current_user)):
|
|
23
|
+
pass
|
|
24
|
+
return {"message": "Welcome to the admin dashboard!"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# auth_backends.py
|
|
2
|
+
from fastapi_users.authentication import BearerTransport, JWTStrategy, AuthenticationBackend
|
|
3
|
+
|
|
4
|
+
from .user_manager import SECRET # 从 user_manager 引入 SECRET
|
|
5
|
+
|
|
6
|
+
bearer_transport = BearerTransport(tokenUrl="auth/jwt/login")
|
|
7
|
+
|
|
8
|
+
def get_jwt_strategy() -> JWTStrategy:
|
|
9
|
+
return JWTStrategy(secret=SECRET, lifetime_seconds=3600) # JWT有效期1小时
|
|
10
|
+
|
|
11
|
+
auth_backend = AuthenticationBackend(
|
|
12
|
+
name="jwt",
|
|
13
|
+
transport=bearer_transport,
|
|
14
|
+
get_strategy=get_jwt_strategy,
|
|
15
|
+
)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# database.py
|
|
2
|
+
from typing import AsyncGenerator
|
|
3
|
+
|
|
4
|
+
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
|
5
|
+
from sqlalchemy.orm import sessionmaker, DeclarativeBase
|
|
6
|
+
|
|
7
|
+
# 您的 MySQL 连接字符串
|
|
8
|
+
# 格式:mysql+aiomysql://user:password@host:port/database
|
|
9
|
+
# 注意:fastapi_users 的 sqlalchemy 适配器通常需要异步驱动,
|
|
10
|
+
# 所以我们使用 `aiomysql` (虽然你安装的是 mysql-connector-python,
|
|
11
|
+
# 但为了异步,我们实际会用 aiomysql,请确保安装它:pip install aiomysql)
|
|
12
|
+
DATABASE_URL = "mysql+aiomysql://root:1234@localhost:3306/prompts"
|
|
13
|
+
|
|
14
|
+
class Base(DeclarativeBase):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
engine = create_async_engine(DATABASE_URL)
|
|
18
|
+
async_session_maker = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
|
19
|
+
|
|
20
|
+
async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
|
|
21
|
+
async with async_session_maker() as session:
|
|
22
|
+
yield session
|
|
23
|
+
|
|
24
|
+
# 创建所有定义的表(在应用启动时调用)
|
|
25
|
+
async def create_db_and_tables():
|
|
26
|
+
async with engine.begin() as conn:
|
|
27
|
+
await conn.run_sync(Base.metadata.create_all)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# user_manager.py
|
|
2
|
+
import uuid
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from fastapi import Depends, Request
|
|
6
|
+
from fastapi_users import BaseUserManager, UUIDIDMixin
|
|
7
|
+
# from fastapi_users_sqlalchemy import SQLAlchemyUserDatabase
|
|
8
|
+
from fastapi_users.db import SQLAlchemyUserDatabase
|
|
9
|
+
from .database import get_async_session
|
|
10
|
+
|
|
11
|
+
from fastapi import Depends, Request, HTTPException, status # <-- 确保 HTTPException 在这里
|
|
12
|
+
from sqlalchemy.exc import IntegrityError # <-- 确保这一行存在
|
|
13
|
+
|
|
14
|
+
from umanager.server.models.models import User, UserCreate # <--- 在这里添加 UserCreate 的导入
|
|
15
|
+
|
|
16
|
+
from passlib.context import CryptContext # 确保这里引入了 CryptContext
|
|
17
|
+
|
|
18
|
+
# ---------- 确保 pwd_context 在模块级别定义 ----------
|
|
19
|
+
# 它应该在任何使用它的类或函数之外
|
|
20
|
+
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
21
|
+
|
|
22
|
+
# 替换为您的安全密钥,最好从环境变量中获取
|
|
23
|
+
SECRET = "YOUR_SUPER_SECRET_KEY_REPLACE_ME" # !!! 重要:在生产环境中,请务必使用环境变量
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):
|
|
27
|
+
# ... (reset_password_token_secret, verification_token_secret 保持不变)
|
|
28
|
+
|
|
29
|
+
async def create(
|
|
30
|
+
self,
|
|
31
|
+
user_create: UserCreate,
|
|
32
|
+
safe: bool = False,
|
|
33
|
+
request: Optional[Request] = None,
|
|
34
|
+
) -> User:
|
|
35
|
+
existing_user = await self.user_db.get_by_email(user_create.email)
|
|
36
|
+
if existing_user is not None:
|
|
37
|
+
raise HTTPException(
|
|
38
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
39
|
+
detail="A user with this email already exists."
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
hashed_password = pwd_context.hash(user_create.password)
|
|
43
|
+
|
|
44
|
+
# 使用 model_dump 获取 Pydantic V2 的字典表示
|
|
45
|
+
user_data = user_create.model_dump(exclude_unset=True)
|
|
46
|
+
user_data["hashed_password"] = hashed_password
|
|
47
|
+
user_data["id"] = uuid.uuid4()
|
|
48
|
+
|
|
49
|
+
if "password" in user_data: # 确保移除明文密码
|
|
50
|
+
del user_data["password"]
|
|
51
|
+
|
|
52
|
+
# 创建 SQLAlchemy User 实例
|
|
53
|
+
# 这里的 self.user_db.user_table 就是 models.User
|
|
54
|
+
new_user = self.user_db.user_table(**user_data)
|
|
55
|
+
|
|
56
|
+
# 确保默认字段被设置
|
|
57
|
+
new_user.is_active = True
|
|
58
|
+
new_user.is_superuser = False
|
|
59
|
+
new_user.is_verified = False
|
|
60
|
+
|
|
61
|
+
# created_user = await self.user_db.create(new_user)
|
|
62
|
+
try:
|
|
63
|
+
self.user_db.session.add(new_user)
|
|
64
|
+
await self.user_db.session.commit()
|
|
65
|
+
await self.user_db.session.refresh(new_user) # 刷新以获取可能由数据库生成的字段(如默认值)
|
|
66
|
+
except IntegrityError:
|
|
67
|
+
await self.user_db.session.rollback()
|
|
68
|
+
raise HTTPException(
|
|
69
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
70
|
+
detail="A database integrity error occurred (e.g., duplicate email)."
|
|
71
|
+
)
|
|
72
|
+
# await self.on_after_register(created_user, request)
|
|
73
|
+
# return created_user
|
|
74
|
+
await self.on_after_register(new_user, request) # 这里也是 new_user
|
|
75
|
+
return new_user
|
|
76
|
+
|
|
77
|
+
async def get_user_db(session = Depends(get_async_session)):
|
|
78
|
+
yield SQLAlchemyUserDatabase(session, User)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
async def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)):
|
|
83
|
+
yield UserManager(user_db)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# uv venv .venv --python 3.11
|
|
87
|
+
# python -c "import pydantic; print(pydantic.VERSION)"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from utils_tool import *
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: serverz-mcp
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: anyio>=4.11.0
|
|
8
|
+
Requires-Dist: fastapi>=0.121.1
|
|
9
|
+
Requires-Dist: pytest>=9.0.0
|
|
10
|
+
Requires-Dist: pytest-asyncio>=1.3.0
|
|
11
|
+
Requires-Dist: pytest-tornasync>=0.6.0.post2
|
|
12
|
+
Requires-Dist: toml>=0.10.2
|
|
13
|
+
Requires-Dist: uvicorn>=0.38.0
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/serverz_mcp/__init__.py
|
|
4
|
+
src/serverz_mcp/log.py
|
|
5
|
+
src/serverz_mcp/utils.py
|
|
6
|
+
src/serverz_mcp.egg-info/PKG-INFO
|
|
7
|
+
src/serverz_mcp.egg-info/SOURCES.txt
|
|
8
|
+
src/serverz_mcp.egg-info/dependency_links.txt
|
|
9
|
+
src/serverz_mcp.egg-info/requires.txt
|
|
10
|
+
src/serverz_mcp.egg-info/top_level.txt
|
|
11
|
+
src/serverz_mcp/server/__init__.py
|
|
12
|
+
src/serverz_mcp/server/__main__.py
|
|
13
|
+
src/serverz_mcp/server/mcp/__init__.py
|
|
14
|
+
src/serverz_mcp/server/mcp/content.py
|
|
15
|
+
src/serverz_mcp/server/mcp/math.py
|
|
16
|
+
src/serverz_mcp/server/mcp/mcp.py
|
|
17
|
+
src/serverz_mcp/server/mcp/resource.py
|
|
18
|
+
src/serverz_mcp/server/mcp/weather.py
|
|
19
|
+
src/serverz_mcp/server/models/__init__.py
|
|
20
|
+
src/serverz_mcp/server/models/models.py
|
|
21
|
+
src/serverz_mcp/server/routers/__init__.py
|
|
22
|
+
src/serverz_mcp/server/routers/admin.py
|
|
23
|
+
src/serverz_mcp/server/routers/user.py
|
|
24
|
+
src/serverz_mcp/server/utils/__init__.py
|
|
25
|
+
src/serverz_mcp/server/utils/auth_backends.py
|
|
26
|
+
src/serverz_mcp/server/utils/database.py
|
|
27
|
+
src/serverz_mcp/server/utils/user_manager.py
|
|
28
|
+
tests/test_main.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
serverz_mcp
|
|
File without changes
|