Add initial implementation of MCP server and related tools for BMI calculation
This commit is contained in:
BIN
2026 年上海职工职业技能系列竞赛—人工智能训练师技能大赛暨第九届全国职工职业技能大赛上海选拔赛-决赛技术文件(1).pdf
Normal file
BIN
2026 年上海职工职业技能系列竞赛—人工智能训练师技能大赛暨第九届全国职工职业技能大赛上海选拔赛-决赛技术文件(1).pdf
Normal file
Binary file not shown.
0
YOLOv5n.py
Normal file
0
YOLOv5n.py
Normal file
1
labelme-and-yolo-format.py
Normal file
1
labelme-and-yolo-format.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Labelme 标注与 YOLO 格式
|
||||||
377
mcp-from-scratch-gpt.py
Normal file
377
mcp-from-scratch-gpt.py
Normal file
@@ -0,0 +1,377 @@
|
|||||||
|
"""
|
||||||
|
Minimal MCP stdio server
|
||||||
|
Only uses Python standard library: sys + json
|
||||||
|
|
||||||
|
Tool:
|
||||||
|
calculate_bmi(height, weight)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
PROTOCOL_VERSION = "2026-07-28"
|
||||||
|
|
||||||
|
SERVER_INFO = {
|
||||||
|
"name": "bmi-mcp-server",
|
||||||
|
"version": "0.1.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
# JSON-RPC helpers
|
||||||
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
def rpc_result(request_id, result):
|
||||||
|
return {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": request_id,
|
||||||
|
"result": result
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def rpc_error(request_id, code, message, data=None):
|
||||||
|
error = {
|
||||||
|
"code": code,
|
||||||
|
"message": message
|
||||||
|
}
|
||||||
|
|
||||||
|
if data is not None:
|
||||||
|
error["data"] = data
|
||||||
|
|
||||||
|
return {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": request_id,
|
||||||
|
"error": error
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def result_meta():
|
||||||
|
return {
|
||||||
|
"io.modelcontextprotocol/serverInfo": SERVER_INFO
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def tool_error(request_id, message):
|
||||||
|
"""
|
||||||
|
MCP request itself is valid,
|
||||||
|
but the tool cannot execute successfully.
|
||||||
|
"""
|
||||||
|
return rpc_result(
|
||||||
|
request_id,
|
||||||
|
{
|
||||||
|
"resultType": "complete",
|
||||||
|
"_meta": result_meta(),
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": message
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isError": True
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
# MCP tool definition
|
||||||
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
BMI_TOOL = {
|
||||||
|
"name": "calculate_bmi",
|
||||||
|
"description": "Calculate BMI from height in meters and weight in kilograms.",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"height": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Height in meters"
|
||||||
|
},
|
||||||
|
"weight": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Weight in kilograms"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"height",
|
||||||
|
"weight"
|
||||||
|
],
|
||||||
|
"additionalProperties": False
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
# MCP methods
|
||||||
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
def handle_discover(request):
|
||||||
|
return rpc_result(
|
||||||
|
request["id"],
|
||||||
|
{
|
||||||
|
"resultType": "complete",
|
||||||
|
"supportedVersions": [
|
||||||
|
PROTOCOL_VERSION
|
||||||
|
],
|
||||||
|
"capabilities": {
|
||||||
|
"tools": {
|
||||||
|
"listChanged": False
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"_meta": result_meta(),
|
||||||
|
"instructions": "Provides a BMI calculation tool.",
|
||||||
|
"ttlMs": 3600000,
|
||||||
|
"cacheScope": "public"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_tools_list(request):
|
||||||
|
return rpc_result(
|
||||||
|
request["id"],
|
||||||
|
{
|
||||||
|
"resultType": "complete",
|
||||||
|
"_meta": result_meta(),
|
||||||
|
"tools": [
|
||||||
|
BMI_TOOL
|
||||||
|
],
|
||||||
|
"ttlMs": 300000,
|
||||||
|
"cacheScope": "public"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_tools_call(request):
|
||||||
|
request_id = request["id"]
|
||||||
|
|
||||||
|
params = request.get("params", {})
|
||||||
|
|
||||||
|
name = params.get("name")
|
||||||
|
arguments = params.get("arguments", {})
|
||||||
|
|
||||||
|
# -------------------------------
|
||||||
|
# Protocol-level error
|
||||||
|
# -------------------------------
|
||||||
|
|
||||||
|
if name != "calculate_bmi":
|
||||||
|
return rpc_error(
|
||||||
|
request_id,
|
||||||
|
-32602,
|
||||||
|
f"Unknown tool: {name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# -------------------------------
|
||||||
|
# Tool input validation
|
||||||
|
# -------------------------------
|
||||||
|
|
||||||
|
height = arguments.get("height")
|
||||||
|
weight = arguments.get("weight")
|
||||||
|
|
||||||
|
if height is None:
|
||||||
|
return tool_error(
|
||||||
|
request_id,
|
||||||
|
"height is required"
|
||||||
|
)
|
||||||
|
|
||||||
|
if weight is None:
|
||||||
|
return tool_error(
|
||||||
|
request_id,
|
||||||
|
"weight is required"
|
||||||
|
)
|
||||||
|
|
||||||
|
# bool is a subclass of int in Python,
|
||||||
|
# so explicitly reject True / False.
|
||||||
|
if (
|
||||||
|
not isinstance(height, (int, float))
|
||||||
|
or isinstance(height, bool)
|
||||||
|
):
|
||||||
|
return tool_error(
|
||||||
|
request_id,
|
||||||
|
"height must be a number"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
not isinstance(weight, (int, float))
|
||||||
|
or isinstance(weight, bool)
|
||||||
|
):
|
||||||
|
return tool_error(
|
||||||
|
request_id,
|
||||||
|
"weight must be a number"
|
||||||
|
)
|
||||||
|
|
||||||
|
if height <= 0:
|
||||||
|
return tool_error(
|
||||||
|
request_id,
|
||||||
|
"height must be greater than 0"
|
||||||
|
)
|
||||||
|
|
||||||
|
if weight <= 0:
|
||||||
|
return tool_error(
|
||||||
|
request_id,
|
||||||
|
"weight must be greater than 0"
|
||||||
|
)
|
||||||
|
|
||||||
|
# -------------------------------
|
||||||
|
# Actual business logic
|
||||||
|
# -------------------------------
|
||||||
|
|
||||||
|
bmi = weight / (height ** 2)
|
||||||
|
bmi = round(bmi, 2)
|
||||||
|
|
||||||
|
return rpc_result(
|
||||||
|
request_id,
|
||||||
|
{
|
||||||
|
"resultType": "complete",
|
||||||
|
"_meta": result_meta(),
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": str(bmi)
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isError": False
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
# Protocol validation / dispatch
|
||||||
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
def process(request):
|
||||||
|
|
||||||
|
# JSON-RPC message must be an object
|
||||||
|
if not isinstance(request, dict):
|
||||||
|
return rpc_error(
|
||||||
|
None,
|
||||||
|
-32600,
|
||||||
|
"Invalid Request"
|
||||||
|
)
|
||||||
|
|
||||||
|
if request.get("jsonrpc") != "2.0":
|
||||||
|
return rpc_error(
|
||||||
|
request.get("id"),
|
||||||
|
-32600,
|
||||||
|
"Invalid Request"
|
||||||
|
)
|
||||||
|
|
||||||
|
# No id means notification.
|
||||||
|
# This simple server does not need to respond.
|
||||||
|
if "id" not in request:
|
||||||
|
return None
|
||||||
|
|
||||||
|
method = request.get("method")
|
||||||
|
|
||||||
|
if not isinstance(method, str):
|
||||||
|
return rpc_error(
|
||||||
|
request.get("id"),
|
||||||
|
-32600,
|
||||||
|
"Invalid Request"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Modern MCP request metadata
|
||||||
|
params = request.get("params", {})
|
||||||
|
meta = params.get("_meta", {}) if isinstance(params, dict) else {}
|
||||||
|
|
||||||
|
requested_version = meta.get(
|
||||||
|
"io.modelcontextprotocol/protocolVersion"
|
||||||
|
)
|
||||||
|
|
||||||
|
if requested_version != PROTOCOL_VERSION:
|
||||||
|
return rpc_error(
|
||||||
|
request.get("id"),
|
||||||
|
-32022,
|
||||||
|
"Unsupported protocol version",
|
||||||
|
{
|
||||||
|
"supported": [
|
||||||
|
PROTOCOL_VERSION
|
||||||
|
],
|
||||||
|
"requested": requested_version
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# -------------------------------
|
||||||
|
# MCP method dispatcher
|
||||||
|
# -------------------------------
|
||||||
|
|
||||||
|
if method == "server/discover":
|
||||||
|
return handle_discover(request)
|
||||||
|
|
||||||
|
if method == "tools/list":
|
||||||
|
return handle_tools_list(request)
|
||||||
|
|
||||||
|
if method == "tools/call":
|
||||||
|
return handle_tools_call(request)
|
||||||
|
|
||||||
|
return rpc_error(
|
||||||
|
request.get("id"),
|
||||||
|
-32601,
|
||||||
|
f"Method not found: {method}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
# stdio transport
|
||||||
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
def send(message):
|
||||||
|
text = json.dumps(
|
||||||
|
message,
|
||||||
|
ensure_ascii=False,
|
||||||
|
separators=(",", ":")
|
||||||
|
)
|
||||||
|
|
||||||
|
sys.stdout.write(text + "\n")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
# stdin remains open while the MCP server is alive
|
||||||
|
for line in sys.stdin:
|
||||||
|
|
||||||
|
line = line.strip()
|
||||||
|
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
request = json.loads(line)
|
||||||
|
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
send(
|
||||||
|
rpc_error(
|
||||||
|
None,
|
||||||
|
-32700,
|
||||||
|
"Parse error"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = process(request)
|
||||||
|
|
||||||
|
if response is not None:
|
||||||
|
send(response)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Logs MUST go to stderr, never stdout
|
||||||
|
print(
|
||||||
|
f"Internal error: {e}",
|
||||||
|
file=sys.stderr,
|
||||||
|
flush=True
|
||||||
|
)
|
||||||
|
|
||||||
|
send(
|
||||||
|
rpc_error(
|
||||||
|
request.get("id")
|
||||||
|
if isinstance(request, dict)
|
||||||
|
else None,
|
||||||
|
-32603,
|
||||||
|
"Internal error"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
76
mcp-from-scratch-mytry.py
Normal file
76
mcp-from-scratch-mytry.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
"""只用 sys + json,从零实现一个 MCP Server
|
||||||
|
提供 calculate_bmi(height, weight) 工具。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
|
||||||
|
def process(request):
|
||||||
|
if request.get("method") == "ping":
|
||||||
|
result = {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": request.get("id"),
|
||||||
|
"result": "pong"
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
if request.get("method") == "tools/list":
|
||||||
|
result = {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"result": {
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "calculate_bmi",
|
||||||
|
"description": "Calculate BMI from height and weight",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"height": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"weight": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"height",
|
||||||
|
"weight"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
if request.get("method") == "tools/call":
|
||||||
|
name = request.get("params", {}).get("name")
|
||||||
|
arguments = request.get("params", {}).get("arguments", {})
|
||||||
|
if name == "calculate_bmi":
|
||||||
|
height = arguments.get("height")
|
||||||
|
weight = arguments.get("weight")
|
||||||
|
if height is not None and weight is not None:
|
||||||
|
bmi = weight / (height ** 2)
|
||||||
|
result = {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": request.get("id"),
|
||||||
|
"result": {
|
||||||
|
"resultType": "complete",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": str(bmi)
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isError": False
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
for line in sys.stdin:
|
||||||
|
request = json.loads(line)
|
||||||
|
|
||||||
|
output = process(request)
|
||||||
|
if output is not None:
|
||||||
|
print(json.dumps(output, ensure_ascii=False), flush=True)
|
||||||
94
rag.py
Normal file
94
rag.py
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
"""稀疏检索 / 简单 RAG"""
|
||||||
|
from sklearn.feature_extraction.text import TfidfVectorizer
|
||||||
|
from sklearn.metrics.pairwise import cosine_similarity
|
||||||
|
|
||||||
|
|
||||||
|
# 1. 简单切块
|
||||||
|
def chunk_text(text, chunk_size=60, overlap=15):
|
||||||
|
chunks = []
|
||||||
|
|
||||||
|
start = 0
|
||||||
|
|
||||||
|
while start < len(text):
|
||||||
|
end = start + chunk_size
|
||||||
|
|
||||||
|
chunk = text[start:end]
|
||||||
|
chunks.append(chunk)
|
||||||
|
|
||||||
|
if end >= len(text):
|
||||||
|
break
|
||||||
|
|
||||||
|
start = end - overlap
|
||||||
|
|
||||||
|
return chunks
|
||||||
|
|
||||||
|
|
||||||
|
# 2. 知识库原文
|
||||||
|
document = """
|
||||||
|
职工请事假,应当提前一个工作日通过OA系统提交申请,
|
||||||
|
经部门负责人审批同意后方可休假。
|
||||||
|
|
||||||
|
职工因疾病无法正常工作的,可以申请病假。
|
||||||
|
病假应当提供医院出具的相关证明材料。
|
||||||
|
|
||||||
|
员工因工作需要出差的,应提前提交出差申请。
|
||||||
|
住宿费用按照公司差旅管理制度规定的标准报销。
|
||||||
|
|
||||||
|
职工连续工作满一年后可以享受带薪年休假。
|
||||||
|
年休假天数根据职工累计工作年限确定。
|
||||||
|
""".replace("\n", "")
|
||||||
|
|
||||||
|
|
||||||
|
# 3. 文档切块
|
||||||
|
chunks = chunk_text(
|
||||||
|
document,
|
||||||
|
chunk_size=60,
|
||||||
|
overlap=15
|
||||||
|
)
|
||||||
|
|
||||||
|
print("=== chunks ===")
|
||||||
|
|
||||||
|
for i, chunk in enumerate(chunks):
|
||||||
|
print(i, chunk)
|
||||||
|
|
||||||
|
|
||||||
|
# 4. 给所有 chunk 建 TF-IDF 索引
|
||||||
|
vectorizer = TfidfVectorizer(
|
||||||
|
analyzer="char",
|
||||||
|
ngram_range=(1, 3)
|
||||||
|
)
|
||||||
|
|
||||||
|
doc_vectors = vectorizer.fit_transform(chunks)
|
||||||
|
|
||||||
|
|
||||||
|
# 5. 用户问题
|
||||||
|
query = "事假怎么申请?"
|
||||||
|
|
||||||
|
|
||||||
|
# 6. Query 转成同一个 TF-IDF 空间
|
||||||
|
query_vector = vectorizer.transform([query])
|
||||||
|
|
||||||
|
|
||||||
|
# 7. 计算 query 与所有 chunk 的相似度
|
||||||
|
scores = cosine_similarity(
|
||||||
|
query_vector,
|
||||||
|
doc_vectors
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
|
||||||
|
# 8. Top-K
|
||||||
|
top_k = 3
|
||||||
|
|
||||||
|
top_indices = scores.argsort()[::-1][:top_k]
|
||||||
|
|
||||||
|
|
||||||
|
print("\n=== query ===")
|
||||||
|
print(query)
|
||||||
|
|
||||||
|
print("\n=== retrieval result ===")
|
||||||
|
|
||||||
|
for index in top_indices:
|
||||||
|
print(
|
||||||
|
f"score={scores[index]:.4f}",
|
||||||
|
chunks[index]
|
||||||
|
)
|
||||||
87
text-cls-baseline.py
Normal file
87
text-cls-baseline.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
"""文本分类 baseline"""
|
||||||
|
from sklearn.feature_extraction.text import TfidfVectorizer
|
||||||
|
from sklearn.svm import LinearSVC
|
||||||
|
from sklearn.model_selection import train_test_split
|
||||||
|
from sklearn.metrics import accuracy_score, classification_report
|
||||||
|
|
||||||
|
|
||||||
|
# 1. 数据
|
||||||
|
texts = [
|
||||||
|
"我要申请退款",
|
||||||
|
"这个订单可以退款吗",
|
||||||
|
"钱什么时候退回来",
|
||||||
|
"我要取消订单退款",
|
||||||
|
"商品有问题申请退款",
|
||||||
|
"怎么退货退款",
|
||||||
|
|
||||||
|
"我的密码忘记了",
|
||||||
|
"怎么修改登录密码",
|
||||||
|
"账号登录不上去了",
|
||||||
|
"如何重置密码",
|
||||||
|
"账户无法登录",
|
||||||
|
"密码在哪里修改",
|
||||||
|
|
||||||
|
"快递什么时候到",
|
||||||
|
"物流怎么还没更新",
|
||||||
|
"我的包裹到哪里了",
|
||||||
|
"查询一下配送进度",
|
||||||
|
"快递一直没有收到",
|
||||||
|
"帮我查一下物流"
|
||||||
|
]
|
||||||
|
|
||||||
|
labels = [
|
||||||
|
"refund", "refund", "refund",
|
||||||
|
"refund", "refund", "refund",
|
||||||
|
|
||||||
|
"account", "account", "account",
|
||||||
|
"account", "account", "account",
|
||||||
|
|
||||||
|
"delivery", "delivery", "delivery",
|
||||||
|
"delivery", "delivery", "delivery"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# 2. 切分训练集 / 验证集
|
||||||
|
X_train, X_val, y_train, y_val = train_test_split(
|
||||||
|
texts,
|
||||||
|
labels,
|
||||||
|
test_size=0.33,
|
||||||
|
random_state=42,
|
||||||
|
stratify=labels
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# 3. 文本 -> TF-IDF 稀疏向量
|
||||||
|
vectorizer = TfidfVectorizer(
|
||||||
|
analyzer="char",
|
||||||
|
ngram_range=(1, 3)
|
||||||
|
)
|
||||||
|
|
||||||
|
X_train_vec = vectorizer.fit_transform(X_train)
|
||||||
|
X_val_vec = vectorizer.transform(X_val)
|
||||||
|
|
||||||
|
|
||||||
|
# 4. 训练分类器
|
||||||
|
model = LinearSVC(C=1.0)
|
||||||
|
model.fit(X_train_vec, y_train)
|
||||||
|
|
||||||
|
|
||||||
|
# 5. 验证
|
||||||
|
pred = model.predict(X_val_vec)
|
||||||
|
|
||||||
|
print("accuracy:", accuracy_score(y_val, pred))
|
||||||
|
print(classification_report(y_val, pred))
|
||||||
|
|
||||||
|
|
||||||
|
# 6. 推理
|
||||||
|
test_texts = [
|
||||||
|
"我的东西怎么还没送过来",
|
||||||
|
"登录密码忘了怎么办",
|
||||||
|
"我要把这个订单退掉"
|
||||||
|
]
|
||||||
|
|
||||||
|
X_test = vectorizer.transform(test_texts)
|
||||||
|
pred = model.predict(X_test)
|
||||||
|
|
||||||
|
for text, label in zip(test_texts, pred):
|
||||||
|
print(text, "->", label)
|
||||||
Reference in New Issue
Block a user