Cargo.toml
[package]
name = "time-mcp-server-rust"
version = "0.1.0"
edition = "2024"
[dependencies]
# Async runtime
tokio = { version = "1", features = ["full"] }
# Web framework
axum = { version = "0.7", features = ["macros"] }
tower = "0.4"
tower-http = { version = "0.5", features = ["cors"] }
# Serialization
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# Time handling
chrono = { version = "0.4", features = ["serde"] }
chrono-tz = "0.8"
# Utils
once_cell = "1.19"
anyhow = "1.0"
main.rs
use axum::{
extract::{Query, State},
http::StatusCode,
response::{IntoResponse, Json as AxumJson},
routing::{get, post},
Router,
};
use chrono::{DateTime, TimeZone, Utc};
use chrono_tz::Tz;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::collections::{HashMap, HashSet};
use std::env;
use std::sync::Arc;
use tower_http::cors::{Any, CorsLayer};
// --- 配置 ---
struct Config {
port: u16,
host: String,
allowed_keys: HashSet<String>,
}
static CONFIG: Lazy<Config> = Lazy::new(|| {
let key = env::var("TIME_SERVER_API_KEY").unwrap_or_else(|_| "default_key_123456".to_string());
let mut keys = HashSet::new();
keys.insert(key);
Config {
port: 8700,
host: "0.0.0.0".to_string(),
allowed_keys: keys,
}
});
// --- MCP 协议结构 ---
#[allow(dead_code)]
#[derive(Deserialize, Debug)]
struct JsonRpcRequest {
jsonrpc: String,
id: Option<Value>,
method: String,
params: Option<Value>,
}
#[derive(Serialize)]
struct JsonRpcResponse {
jsonrpc: String,
id: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
result: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
error: Option<JsonRpcError>,
}
#[derive(Serialize)]
struct JsonRpcError {
code: i32,
message: String,
}
// --- 工具定义 ---
// 1. 获取当前时间
#[derive(Deserialize)]
struct GetCurrentTimeArgs {
#[serde(default = "default_timezone")]
timezone: String,
#[serde(default = "default_format")]
format: String,
}
fn default_timezone() -> String { "UTC".to_string() }
fn default_format() -> String { "iso".to_string() }
fn handle_get_current_time(args: Value) -> Result<Value, String> {
let args: GetCurrentTimeArgs = serde_json::from_value(args).map_err(|e| e.to_string())?;
let tz: Tz = args.timezone.parse().map_err(|_| format!("无效的时区: {}", args.timezone))?;
let now = Utc::now().with_timezone(&tz);
let formatted_time = match args.format.as_str() {
"iso" => now.to_rfc3339(),
"datetime" => now.format("%Y-%m-%d %H:%M:%S").to_string(),
"timestamp" => now.timestamp().to_string(),
"relative" => "刚刚".to_string(), // 简化处理,实际逻辑需对比现在
_ => now.to_rfc3339(),
};
Ok(json!({
"success": true,
"timezone": args.timezone,
"format": args.format,
"time": formatted_time,
"timestamp": now.timestamp(),
"iso": now.to_rfc3339()
}))
}
// 2. 时间差异
#[derive(Deserialize)]
struct TimeDiffArgs {
from: String,
to: String,
}
fn parse_time_str(s: &str) -> Result<DateTime<Utc>, String> {
if s.chars().all(|c| c.is_ascii_digit()) {
let ts = s.parse::<i64>().map_err(|_| "时间戳解析错误")?;
Ok(Utc.timestamp_opt(ts, 0).single().unwrap())
} else {
DateTime::parse_from_rfc3339(s)
.map(|d| d.with_timezone(&Utc))
.or_else(|_| {
// 尝试其他格式或返回错误
Err("时间解析错误".to_string())
})
}
}
fn handle_time_diff(args: Value) -> Result<Value, String> {
let args: TimeDiffArgs = serde_json::from_value(args).map_err(|e| e.to_string())?;
let from_time = parse_time_str(&args.from)?;
let to_time = parse_time_str(&args.to)?;
let diff = (to_time - from_time).num_seconds().abs();
let days = diff / 86400;
let hours = (diff % 86400) / 3600;
let mins = (diff % 3600) / 60;
let secs = diff % 60;
Ok(json!({
"success": true,
"from": from_time.to_rfc3339(),
"to": to_time.to_rfc3339(),
"difference": {
"seconds": diff,
"minutes": diff / 60,
"hours": diff / 3600,
"days": days,
"weeks": days / 7,
"description": format!("{} 天 {} 小时 {} 分钟 {} 秒", days, hours, mins, secs)
}
}))
}
// 3. 时间戳转换
#[derive(Deserialize)]
struct TimestampConvertArgs {
value: String,
#[serde(default = "default_target_format")]
target_format: String,
}
fn default_target_format() -> String { "datetime".to_string() }
fn handle_timestamp_convert(args: Value) -> Result<Value, String> {
let args: TimestampConvertArgs = serde_json::from_value(args).map_err(|e| e.to_string())?;
let date = parse_time_str(&args.value)?;
let result = match args.target_format.as_str() {
"iso" => date.to_rfc3339(),
"datetime" => date.format("%Y-%m-%d %H:%M:%S").to_string(),
"timestamp" => date.timestamp().to_string(),
"human" => {
let now = Utc::now();
let diff = date.signed_duration_since(now);
if diff.num_seconds().abs() < 60 {
"不到 1 分钟".to_string()
} else {
format!("{} 分钟", diff.num_minutes().abs())
}
},
_ => date.to_rfc3339(),
};
Ok(json!({
"success": true,
"input": args.value,
"result": result,
"parsed": date.to_rfc3339(),
"timestamp": date.timestamp()
}))
}
// 4. 相对时间
#[derive(Deserialize)]
struct RelativeTimeArgs {
time: String,
reference: Option<String>,
}
fn handle_relative_time(args: Value) -> Result<Value, String> {
let args: RelativeTimeArgs = serde_json::from_value(args).map_err(|e| e.to_string())?;
let target = parse_time_str(&args.time)?;
let reference = match args.reference {
Some(ref s) => parse_time_str(s)?,
None => Utc::now(),
};
let diff = target.signed_duration_since(reference);
let is_past = diff.num_seconds() < 0;
let abs_secs = diff.num_seconds().abs();
let description = if abs_secs < 60 {
"刚刚".to_string()
} else if abs_secs < 3600 {
format!("{} 分钟{}", abs_secs / 60, if is_past { "前" } else { "后" })
} else if abs_secs < 86400 {
format!("{} 小时{}", abs_secs / 3600, if is_past { "前" } else { "后" })
} else {
format!("{} 天{}", abs_secs / 86400, if is_past { "前" } else { "后" })
};
Ok(json!({
"success": true,
"targetTime": target.to_rfc3339(),
"referenceTime": reference.to_rfc3339(),
"description": description,
"isPast": is_past
}))
}
// 5. 世界时间
#[derive(Deserialize)]
struct GetWorldTimeArgs {
city: String,
}
fn handle_get_world_time(args: Value) -> Result<Value, String> {
let args: GetWorldTimeArgs = serde_json::from_value(args).map_err(|e| e.to_string())?;
let tz_map: HashMap<&str, &str> = [
("beijing", "Asia/Shanghai"), ("shanghai", "Asia/Shanghai"),
("london", "Europe/London"), ("newyork", "America/New_York"),
// ... 此处省略完整映射,逻辑同 JS 代码
].iter().cloned().collect();
let tz_str = tz_map.get(args.city.as_str()).ok_or("未知城市")?;
let tz: Tz = tz_str.parse().map_err(|_| "时区错误")?;
let now = Utc::now().with_timezone(&tz);
Ok(json!({
"success": true,
"city": args.city,
"timezone": tz_str,
"time": now.format("%Y-%m-%d %H:%M:%S").to_string()
}))
}
// 6. 创建提醒
#[derive(Deserialize)]
struct CreateReminderArgs {
from: Option<String>,
delay: DelaySpec,
}
#[derive(Deserialize)]
struct DelaySpec {
value: i64,
unit: String, // seconds, minutes, etc.
}
fn handle_create_reminder(args: Value) -> Result<Value, String> {
let args: CreateReminderArgs = serde_json::from_value(args).map_err(|e| e.to_string())?;
let start = match args.from {
Some(ref s) if s != "now" => parse_time_str(s)?,
_ => Utc::now(),
};
let delay_ms = match args.delay.unit.as_str() {
"seconds" => args.delay.value * 1000,
"minutes" => args.delay.value * 60 * 1000,
"hours" => args.delay.value * 60 * 60 * 1000,
"days" => args.delay.value * 24 * 60 * 60 * 1000,
_ => return Err("无效单位".to_string()),
};
let reminder_time = start + chrono::Duration::milliseconds(delay_ms);
Ok(json!({
"success": true,
"reminderTime": reminder_time.to_rfc3339()
}))
}
// --- 服务器逻辑 ---
type AppState = Arc<HashSet<String>>;
#[tokio::main]
async fn main() {
// 初始化配置
let _config = &*CONFIG;
// 共享状态 (API Keys)
let state = Arc::new(CONFIG.allowed_keys.clone());
// CORS 配置
let cors = CorsLayer::new()
.allow_origin(Any)
.allow_methods(Any)
.allow_headers(Any);
let app = Router::new()
.route("/health", get(health_check))
.route("/mcp", post(mcp_handler))
.route("/test", get(test_handler))
.layer(cors)
.with_state(state);
let addr = format!("{}:{}", CONFIG.host, CONFIG.port);
println!("=== 时间 MCP 服务器已启动 ===");
println!("服务器地址: http://{}", addr);
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
axum::serve(listener, app).await.unwrap();
}
// API Key 中间件逻辑 (在 Handler 中处理)
fn check_auth(key: Option<&String>, state: &HashSet<String>) -> Result<(), (StatusCode, String)> {
match key {
Some(k) if state.contains(k) => Ok(()),
Some(_) => Err((StatusCode::FORBIDDEN, "无效的 API Key".to_string())),
None => Err((StatusCode::UNAUTHORIZED, "缺少 API Key".to_string())),
}
}
async fn health_check() -> impl IntoResponse {
AxumJson(json!({
"status": "ok",
"server": "Time MCP Server (Rust)",
"version": "1.0.0"
}))
}
#[derive(Deserialize)]
struct TestQuery {
key: String,
}
async fn test_handler(Query(query): Query<TestQuery>, State(state): State<AppState>) -> impl IntoResponse {
if let Err((status, msg)) = check_auth(Some(&query.key), &state) {
return (status, AxumJson(json!({ "error": msg })));
}
(StatusCode::OK, AxumJson(json!({
"success": true,
"message": "API Key 验证成功",
"serverTime": Utc::now().to_rfc3339()
})))
}
#[derive(Deserialize)]
struct McpQuery {
key: String,
}
async fn mcp_handler(
Query(query): Query<McpQuery>,
State(state): State<AppState>,
AxumJson(body): AxumJson<JsonRpcRequest>,
) -> impl IntoResponse {
// 1. 验证 Key
if let Err((status, msg)) = check_auth(Some(&query.key), &state) {
return (status, AxumJson(json!({ "error": msg })));
}
// 2. 路由请求
let result = match body.method.as_str() {
// MCP 协议标准方法
"initialize" => Ok(json!({
"protocolVersion": "2024-11-05",
"serverInfo": {
"name": "Time MCP Server",
"version": "1.0.0"
},
"capabilities": {
"tools": {}
}
})),
"initialized" => Ok(json!({})),
"shutdown" => Ok(json!({})),
// 工具相关方法
"tools/list" => Ok(json!({
"tools": [
{
"name": "getCurrentTime",
"description": "获取当前时间",
"inputSchema": {
"type": "object",
"properties": {
"timezone": {
"type": "string",
"default": "UTC",
"description": "时区,例如 Asia/Shanghai"
},
"format": {
"type": "string",
"default": "iso",
"enum": ["iso", "datetime", "timestamp", "relative"],
"description": "时间格式"
}
}
}
},
{
"name": "timeDiff",
"description": "计算时间差异",
"inputSchema": {
"type": "object",
"properties": {
"from": {
"type": "string",
"description": "起始时间(ISO格式或时间戳)"
},
"to": {
"type": "string",
"description": "结束时间(ISO格式或时间戳)"
}
},
"required": ["from", "to"]
}
},
{
"name": "timestampConvert",
"description": "时间戳转换",
"inputSchema": {
"type": "object",
"properties": {
"value": {
"type": "string",
"description": "时间值(ISO格式或时间戳)"
},
"target_format": {
"type": "string",
"default": "datetime",
"enum": ["iso", "datetime", "timestamp", "human"],
"description": "目标格式"
}
},
"required": ["value"]
}
},
{
"name": "relativeTime",
"description": "相对时间",
"inputSchema": {
"type": "object",
"properties": {
"time": {
"type": "string",
"description": "目标时间(ISO格式或时间戳)"
},
"reference": {
"type": "string",
"description": "参考时间(可选,默认为当前时间)"
}
},
"required": ["time"]
}
},
{
"name": "getWorldTime",
"description": "世界时间",
"inputSchema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,例如 beijing, london, newyork"
}
},
"required": ["city"]
}
},
{
"name": "createReminder",
"description": "创建提醒",
"inputSchema": {
"type": "object",
"properties": {
"from": {
"type": "string",
"description": "起始时间(可选,默认为now)"
},
"delay": {
"type": "object",
"properties": {
"value": {
"type": "integer",
"description": "延迟数值"
},
"unit": {
"type": "string",
"enum": ["seconds", "minutes", "hours", "days"],
"description": "时间单位"
}
},
"required": ["value", "unit"]
}
},
"required": ["delay"]
}
}
]
})),
"tools/call" => {
// 解析工具调用
let params = body.params.unwrap_or(json!({}));
let tool_name = params["name"].as_str().unwrap_or("");
let tool_args = params.get("arguments").cloned().unwrap_or(json!({}));
let res = match tool_name {
"getCurrentTime" => handle_get_current_time(tool_args),
"timeDiff" => handle_time_diff(tool_args),
"timestampConvert" => handle_timestamp_convert(tool_args),
"relativeTime" => handle_relative_time(tool_args),
"getWorldTime" => handle_get_world_time(tool_args),
"createReminder" => handle_create_reminder(tool_args),
_ => Err(format!("未知工具: {}", tool_name)),
};
res.map(|content| json!({
"content": [{ "type": "text", "text": content.to_string() }]
}))
}
_ => Err(format!("未知方法: {}", body.method)),
};
// 3. 构造响应
let response = match result {
Ok(res) => JsonRpcResponse {
jsonrpc: "2.0".to_string(),
id: body.id,
result: Some(res),
error: None,
},
Err(msg) => JsonRpcResponse {
jsonrpc: "2.0".to_string(),
id: body.id,
result: None,
error: Some(JsonRpcError { code: -32000, message: msg }),
},
};
(StatusCode::OK, AxumJson(json!(response)))
}
mcpServers.json
{
"mcpServers": {
"time-streamable": {
"type": "streamable",
"name": "TimeServer",
"description": "Get the current time in a specified timezone",
"url": "http://127.0.0.1:8700/mcp?key=default_key_123456"
}
}
}
js 实现
package.json
{
"name": "time_mcp_server_with_js",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"packageManager": "pnpm@10.13.0",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.25.3",
"cors": "^2.8.5",
"express": "^5.2.1",
"zod": "^4.3.5"
}
}
main.mjs
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { z } from "zod";
import express from "express";
import cors from "cors";
// 配置
const CONFIG = {
port: 8700,
host: '0.0.0.0',
// 这里可以配置允许的 API Key,实际使用时可以从环境变量或数据库读取
allowedKeys: new Set([
process.env.TIME_SERVER_API_KEY || 'default_key_123456' // 默认 key,生产环境请修改
])
};
// 创建一个时间 MCP 服务器
const server = new McpServer({
name: "TimeServer",
version: "1.0.0"
});
// 工具 1: 获取当前时间
server.tool(
"getCurrentTime",
'获取当前时间,支持指定时区和格式化',
{
timezone: z.string().optional().default('UTC').describe('时区,例如: Asia/Shanghai, America/New_York, Europe/London'),
format: z.string().optional().default('iso').describe('时间格式: iso, datetime, timestamp, relative')
},
async ({ timezone, format }) => {
const date = new Date();
let formattedTime;
try {
const options = { timeZone: timezone };
const localDate = new Date(date.toLocaleString('en-US', options));
switch (format) {
case 'iso':
formattedTime = localDate.toISOString();
break;
case 'datetime':
formattedTime = localDate.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZone: timezone
});
break;
case 'timestamp':
formattedTime = localDate.getTime().toString();
break;
case 'relative':
const now = new Date();
const diff = now.getTime() - localDate.getTime();
if (diff < 60000) {
formattedTime = '刚刚';
} else if (diff < 3600000) {
formattedTime = `${Math.floor(diff / 60000)} 分钟前`;
} else if (diff < 86400000) {
formattedTime = `${Math.floor(diff / 3600000)} 小时前`;
} else {
formattedTime = `${Math.floor(diff / 86400000)} 天前`;
}
break;
default:
formattedTime = localDate.toISOString();
}
return {
content: [{
type: "text",
text: JSON.stringify({
success: true,
timezone: timezone,
format: format,
time: formattedTime,
timestamp: localDate.getTime(),
iso: localDate.toISOString()
}, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: JSON.stringify({
success: false,
error: `无效的时区: ${timezone}`
}, null, 2)
}]
};
}
}
);
// 工具 2: 获取时间差异
server.tool(
"timeDiff",
'计算两个时间点之间的差异',
{
from: z.string().describe('开始时间 (ISO 格式或时间戳)'),
to: z.string().describe('结束时间 (ISO 格式或时间戳)')
},
async ({ from, to }) => {
try {
let fromTime, toTime;
// 处理时间戳
if (/^\d+$/.test(from)) {
fromTime = new Date(parseInt(from));
} else {
fromTime = new Date(from);
}
if (/^\d+$/.test(to)) {
toTime = new Date(parseInt(to));
} else {
toTime = new Date(to);
}
const diffMs = Math.abs(toTime.getTime() - fromTime.getTime());
const diffSec = Math.floor(diffMs / 1000);
const diffMin = Math.floor(diffSec / 60);
const diffHour = Math.floor(diffMin / 60);
const diffDay = Math.floor(diffHour / 24);
const diffWeek = Math.floor(diffDay / 7);
return {
content: [{
type: "text",
text: JSON.stringify({
success: true,
from: fromTime.toISOString(),
to: toTime.toISOString(),
difference: {
milliseconds: diffMs,
seconds: diffSec,
minutes: diffMin,
hours: diffHour,
days: diffDay,
weeks: diffWeek,
description: `${diffDay} 天 ${diffHour % 24} 小时 ${diffMin % 60} 分钟 ${diffSec % 60} 秒`
}
}, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: JSON.stringify({
success: false,
error: '时间解析错误,请检查输入格式'
}, null, 2)
}]
};
}
}
);
// 工具 3: 时间戳转换
server.tool(
"timestampConvert",
'在时间戳、ISO 字符串和日期对象之间转换',
{
value: z.string().describe('要转换的值 (时间戳或 ISO 字符串)'),
targetFormat: z.enum(['iso', 'datetime', 'timestamp', 'human']).default('datetime').describe('目标格式')
},
async ({ value, targetFormat }) => {
try {
let date;
if (/^\d+$/.test(value)) {
date = new Date(parseInt(value));
} else {
date = new Date(value);
}
let result;
switch (targetFormat) {
case 'iso':
result = date.toISOString();
break;
case 'datetime':
result = date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
weekday: 'short'
});
break;
case 'timestamp':
result = date.getTime().toString();
break;
case 'human':
const now = new Date();
const diff = date.getTime() - now.getTime();
const absDiff = Math.abs(diff);
if (absDiff < 60000) {
result = diff >= 0 ? '不到 1 分钟后' : '不到 1 分钟前';
} else if (absDiff < 3600000) {
const mins = Math.floor(absDiff / 60000);
result = diff >= 0 ? `${mins} 分钟后` : `${mins} 分钟前`;
} else if (absDiff < 86400000) {
const hours = Math.floor(absDiff / 3600000);
result = diff >= 0 ? `${hours} 小时后` : `${hours} 小时前`;
} else if (absDiff < 604800000) {
const days = Math.floor(absDiff / 86400000);
result = diff >= 0 ? `${days} 天后` : `${days} 天前`;
} else {
result = date.toLocaleDateString('zh-CN');
}
break;
default:
result = date.toISOString();
}
return {
content: [{
type: "text",
text: JSON.stringify({
success: true,
input: value,
result: result,
parsed: date.toISOString(),
timestamp: date.getTime()
}, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: JSON.stringify({
success: false,
error: '转换错误,请检查输入格式'
}, null, 2)
}]
};
}
}
);
// 工具 4: 获取相对时间
server.tool(
"relativeTime",
'获取相对于当前时间的描述性时间',
{
time: z.string().describe('目标时间 (ISO 格式或时间戳)'),
reference: z.string().optional().describe('参考时间,默认为当前时间')
},
async ({ time, reference }) => {
try {
let targetTime, refTime;
if (/^\d+$/.test(time)) {
targetTime = new Date(parseInt(time));
} else {
targetTime = new Date(time);
}
if (reference) {
if (/^\d+$/.test(reference)) {
refTime = new Date(parseInt(reference));
} else {
refTime = new Date(reference);
}
} else {
refTime = new Date();
}
const diffMs = targetTime.getTime() - refTime.getTime();
const diffSec = Math.floor(Math.abs(diffMs) / 1000);
const diffMin = Math.floor(diffSec / 60);
const diffHour = Math.floor(diffMin / 60);
const diffDay = Math.floor(diffHour / 24);
const diffWeek = Math.floor(diffDay / 7);
const diffMonth = Math.floor(diffDay / 30);
const diffYear = Math.floor(diffDay / 365);
let description;
let isPast = diffMs < 0;
if (diffSec < 60) {
description = isPast ? '刚刚' : '现在';
} else if (diffMin < 60) {
description = isPast ? `${diffMin} 分钟前` : `${diffMin} 分钟后`;
} else if (diffHour < 24) {
description = isPast ? `${diffHour} 小时前` : `${diffHour} 小时后`;
} else if (diffDay < 7) {
description = isPast ? `${diffDay} 天前` : `${diffDay} 天后`;
} else if (diffWeek < 4) {
description = isPast ? `${diffWeek} 周前` : `${diffWeek} 周后`;
} else if (diffMonth < 12) {
description = isPast ? `${diffMonth} 个月前` : `${diffMonth} 个月后`;
} else {
description = isPast ? `${diffYear} 年前` : `${diffYear} 年后`;
}
return {
content: [{
type: "text",
text: JSON.stringify({
success: true,
targetTime: targetTime.toISOString(),
referenceTime: refTime.toISOString(),
differenceMs: diffMs,
description: description,
isFuture: diffMs > 0,
isPast: diffMs < 0,
isPresent: diffMs === 0
}, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: JSON.stringify({
success: false,
error: '时间解析错误'
}, null, 2)
}]
};
}
}
);
// 工具 5: 获取常用时区时间
server.tool(
"getWorldTime",
'获取世界各地主要城市当前时间',
{
city: z.enum([
'beijing', 'shanghai', 'hongkong', 'taipei',
'tokyo', 'seoul', 'singapore', 'mumbai',
'dubai', 'moscow', 'istanbul', 'cairo',
'london', 'paris', 'berlin', 'rome',
'newyork', 'losangeles', 'chicago', 'toronto',
'saopaulo', 'sydney', 'auckland', 'johannesburg'
]).describe('城市名称')
},
async ({ city }) => {
const timezoneMap = {
'beijing': 'Asia/Shanghai',
'shanghai': 'Asia/Shanghai',
'hongkong': 'Asia/Hong_Kong',
'taipei': 'Asia/Taipei',
'tokyo': 'Asia/Tokyo',
'seoul': 'Asia/Seoul',
'singapore': 'Asia/Singapore',
'mumbai': 'Asia/Kolkata',
'dubai': 'Asia/Dubai',
'moscow': 'Europe/Moscow',
'istanbul': 'Europe/Istanbul',
'cairo': 'Africa/Cairo',
'london': 'Europe/London',
'paris': 'Europe/Paris',
'berlin': 'Europe/Berlin',
'rome': 'Europe/Rome',
'newyork': 'America/New_York',
'losangeles': 'America/Los_Angeles',
'chicago': 'America/Chicago',
'toronto': 'America/Toronto',
'saopaulo': 'America/Sao_Paulo',
'sydney': 'Australia/Sydney',
'auckland': 'Pacific/Auckland',
'johannesburg': 'Africa/Johannesburg'
};
const cityNames = {
'beijing': '北京',
'shanghai': '上海',
'hongkong': '中国香港',
'taipei': '台北',
'tokyo': '东京',
'seoul': '首尔',
'singapore': '新加坡',
'mumbai': '孟买',
'dubai': '迪拜',
'moscow': '莫斯科',
'istanbul': '伊斯坦布尔',
'cairo': '开罗',
'london': '伦敦',
'paris': '巴黎',
'berlin': '柏林',
'rome': '罗马',
'newyork': '纽约',
'losangeles': '洛杉矶',
'chicago': '芝加哥',
'toronto': '多伦多',
'saopaulo': '圣保罗',
'sydney': '悉尼',
'auckland': '奥克兰',
'johannesburg': '约翰内斯堡'
};
const timezone = timezoneMap[city];
const date = new Date();
const localDate = new Date(date.toLocaleString('en-US', { timeZone: timezone }));
return {
content: [{
type: "text",
text: JSON.stringify({
success: true,
city: city,
cityName: cityNames[city],
timezone: timezone,
time: {
full: localDate.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
weekday: 'long',
timeZone: timezone
}),
iso: localDate.toISOString(),
timestamp: localDate.getTime()
}
}, null, 2)
}]
};
}
);
// 工具 6: 创建定时提醒
server.tool(
"createReminder",
'创建一个时间提醒,计算提醒时间',
{
from: z.string().optional().default('now').describe('开始时间,默认为当前时间'),
delay: z.object({
value: z.number().describe('延迟数值'),
unit: z.enum(['seconds', 'minutes', 'hours', 'days', 'weeks']).describe('延迟单位')
}).describe('延迟时间和单位')
},
async ({ from, delay }) => {
try {
let startTime;
if (from === 'now') {
startTime = new Date();
} else if (/^\d+$/.test(from)) {
startTime = new Date(parseInt(from));
} else {
startTime = new Date(from);
}
let delayMs;
switch (delay.unit) {
case 'seconds':
delayMs = delay.value * 1000;
break;
case 'minutes':
delayMs = delay.value * 60 * 1000;
break;
case 'hours':
delayMs = delay.value * 60 * 60 * 1000;
break;
case 'days':
delayMs = delay.value * 24 * 60 * 60 * 1000;
break;
case 'weeks':
delayMs = delay.value * 7 * 24 * 60 * 60 * 1000;
break;
}
const reminderTime = new Date(startTime.getTime() + delayMs);
return {
content: [{
type: "text",
text: JSON.stringify({
success: true,
startTime: startTime.toISOString(),
delay: delay,
reminderTime: reminderTime.toISOString(),
timestamp: reminderTime.getTime(),
humanReadable: reminderTime.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
})
}, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: JSON.stringify({
success: false,
error: '创建提醒失败'
}, null, 2)
}]
};
}
}
);
async function main() {
// 创建 Express 应用
const app = express();
app.use(cors());
app.use(express.json());
// API Key 验证中间件
const apiKeyAuth = (req, res, next) => {
// 从查询参数获取 key
const apiKey = req.query.key;
// 或者从请求头获取
// const apiKey = req.headers['x-api-key'];
if (!apiKey) {
return res.status(401).json({
success: false,
error: "缺少 API Key",
usage: "请使用 ?key=your_api_key 参数访问服务"
});
}
if (!CONFIG.allowedKeys.has(apiKey)) {
return res.status(403).json({
success: false,
error: "无效的 API Key",
usage: "请使用正确的 API Key 访问服务"
});
}
next();
};
// 创建 Streamable HTTP 传输
const transport = new StreamableHTTPServerTransport();
// 连接服务器到传输
await server.connect(transport);
// 设置路由,所有 MCP 请求都需要 API Key 验证
app.use('/mcp', apiKeyAuth);
app.post('/mcp', async (req, res, next) => {
try {
console.log(`收到 MCP 请求: ${req.method} ${req.url}`, {
key: req.query.key,
sessionId: transport.sessionId
});
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error('MCP 请求处理错误:', error);
next(error);
}
});
// 健康检查端点(不需要 API Key)
app.get('/health', (req, res) => {
res.json({
status: 'ok',
server: 'Time MCP Server',
version: '1.0.0',
transport: 'Streamable HTTP',
sessionId: transport.sessionId,
requiresKey: true,
endpoints: {
mcp: 'POST /mcp?key=your_api_key',
health: 'GET /health'
}
});
});
// 测试端点(需要 API Key)
app.get('/test', apiKeyAuth, (req, res) => {
res.json({
success: true,
message: 'API Key 验证成功',
serverTime: new Date().toISOString(),
key: req.query.key.substring(0, 8) + '...' // 部分显示 key
});
});
// 获取服务器信息(需要 API Key)
app.get('/info', apiKeyAuth, (req, res) => {
res.json({
success: true,
server: {
name: 'Time MCP Server',
version: '1.0.0',
description: '提供时间相关功能的 MCP 服务器',
tools: [
'getCurrentTime - 获取当前时间',
'timeDiff - 计算时间差异',
'timestampConvert - 时间戳转换',
'relativeTime - 获取相对时间',
'getWorldTime - 获取世界各地时间',
'createReminder - 创建定时提醒'
]
},
client: {
ip: req.ip,
key: req.query.key.substring(0, 8) + '...'
}
});
});
// 错误处理
app.use((err, req, res, next) => {
console.error('服务器错误:', err);
res.status(500).json({
success: false,
error: '服务器内部错误',
message: err.message
});
});
// 404 处理
app.use((req, res) => {
res.status(404).json({
success: false,
error: '未找到端点',
availableEndpoints: {
'GET /health': '健康检查(无需 key)',
'POST /mcp?key=your_key': 'MCP 服务端点',
'GET /test?key=your_key': '测试 API Key',
'GET /info?key=your_key': '获取服务器信息'
}
});
});
// 监听端口
const { port, host } = CONFIG;
app.listen(port, host, () => {
console.log(`=== 时间 MCP 服务器已启动 ===`);
console.log(`服务器地址: http://${host}:${port}`);
console.log(`健康检查: http://${host}:${port}/health`);
console.log(`MCP 端点: http://${host}:${port}/mcp?key=your_key`);
console.log(`测试端点: http://${host}:${port}/test?key=your_key`);
console.log(`服务器信息: http://${host}:${port}/info?key=your_key`);
console.log(`\n配置信息:`);
console.log(`- 端口: ${port}`);
console.log(`- 需要 API Key: 是`);
console.log(`- 允许的 Key 数量: ${CONFIG.allowedKeys.size}`);
if (process.env.TIME_SERVER_API_KEY) {
console.log(`- 环境变量 KEY: 已设置`);
} else {
console.log(`- 环境变量 KEY: 未设置,使用默认值`);
console.log(`- 默认 KEY: ${Array.from(CONFIG.allowedKeys)[0]}`);
}
console.log(`\n使用方法:`);
console.log(`1. 客户端连接 URL: http://${host}:${port}/mcp?key=your_key`);
console.log(`2. 或设置环境变量 TIME_SERVER_API_KEY=your_key`);
console.log(`\n可用工具:`);
console.log('1. getCurrentTime - 获取当前时间');
console.log('2. timeDiff - 计算时间差异');
console.log('3. timestampConvert - 时间戳转换');
console.log('4. relativeTime - 获取相对时间');
console.log('5. getWorldTime - 获取世界各地时间');
console.log('6. createReminder - 创建定时提醒');
console.log(`\n服务器已准备好接收请求...`);
});
}
main().catch(console.error);