#!/usr/bin/env node
import express from 'express';
import cors from 'cors';
// 抽签工具实现
// 定义工具列表(与 stdio 版本相同)
const tools = [
{
name: 'draw_lottery',
description: '从给定的选项列表中随机抽取一个或多个结果',
inputSchema: {
type: 'object',
properties: {
options: {
type: 'array',
items: { type: 'string' },
description: '抽签选项列表',
},
count: {
type: 'number',
description: '抽取数量,默认为1',
default: 1,
},
allow_duplicate: {
type: 'boolean',
description: '是否允许重复抽取,默认为false',
default: false,
},
},
required: ['options'],
},
},
{
name: 'roll_dice',
description: '投掷骰子,支持自定义面数和数量',
inputSchema: {
type: 'object',
properties: {
sides: {
type: 'number',
description: '骰子面数,默认为6',
default: 6,
},
count: {
type: 'number',
description: '骰子数量,默认为1',
default: 1,
},
},
},
},
{
name: 'flip_coin',
description: '抛硬币,返回正面或反面',
inputSchema: {
type: 'object',
properties: {
count: {
type: 'number',
description: '抛硬币次数,默认为1',
default: 1,
},
},
},
},
];
// 工具调用处理函数
async function handleToolCall(name, args) {
try {
switch (name) {
case 'draw_lottery': {
const { options, count = 1, allow_duplicate = false } = args;
if (!Array.isArray(options) || options.length === 0) {
throw new Error('选项列表不能为空');
}
if (count > options.length && !allow_duplicate) {
throw new Error('抽取数量不能超过选项数量(不允许重复时)');
}
const results = [];
const availableOptions = [...options];
for (let i = 0; i < count; i++) {
const randomIndex = Math.floor(Math.random() * availableOptions.length);
const selected = availableOptions[randomIndex];
results.push(selected);
if (!allow_duplicate) {
availableOptions.splice(randomIndex, 1);
}
}
return {
content: [
{
type: 'text',
text: `🎲 抽签结果:\n${results.map((result, index) => `${index + 1}. ${result}`).join('\n')}`,
},
],
};
}
case 'roll_dice': {
const { sides = 6, count = 1 } = args;
if (sides < 2) {
throw new Error('骰子面数至少为2');
}
if (count < 1) {
throw new Error('骰子数量至少为1');
}
const results = [];
let total = 0;
for (let i = 0; i < count; i++) {
const roll = Math.floor(Math.random() * sides) + 1;
results.push(roll);
total += roll;
}
const resultText = count === 1
? `🎲 投掷结果:${results[0]}`
: `🎲 投掷结果:${results.join(', ')}\n📊 总计:${total}`;
return {
content: [
{
type: 'text',
text: resultText,
},
],
};
}
case 'flip_coin': {
const { count = 1 } = args;
if (count < 1) {
throw new Error('抛硬币次数至少为1');
}
const results = [];
let heads = 0;
let tails = 0;
for (let i = 0; i < count; i++) {
const result = Math.random() < 0.5 ? '正面' : '反面';
results.push(result);
if (result === '正面') heads++;
else tails++;
}
const resultText = count === 1
? `🪙 抛硬币结果:${results[0]}`
: `🪙 抛硬币结果:${results.join(', ')}\n📊 统计:正面 ${heads} 次,反面 ${tails} 次`;
return {
content: [
{
type: 'text',
text: resultText,
},
],
};
}
default:
throw new Error(`未知工具:${name}`);
}
} catch (error) {
return {
content: [
{
type: 'text',
text: `❌ 错误:${error.message}`,
},
],
isError: true,
};
}
}
// 创建 Express 应用
const app = express();
app.use(cors());
app.use(express.json());
// 获取端口号,默认 3000
const PORT = process.env.PORT || 3000;
// 健康检查端点
app.get('/health', (req, res) => {
res.json({
status: 'ok',
service: 'lottery-tool-http',
version: '1.0.0',
timestamp: new Date().toISOString()
});
});
// 工具列表端点
app.get('/tools', (req, res) => {
try {
res.json({
jsonrpc: '2.0',
id: 1,
result: { tools }
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 工具调用端点
app.post('/tools/call', async (req, res) => {
try {
const { name, arguments: args } = req.body;
if (!name) {
return res.status(400).json({ error: '工具名称是必需的' });
}
const result = await handleToolCall(name, args);
res.json({
jsonrpc: '2.0',
id: Date.now(),
result
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 简单的 Web 界面
app.get('/', (req, res) => {
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>MCP 抽签工具</title>
<meta charset="utf-8">
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
.tool { margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }
button { padding: 10px 15px; margin: 5px; background: #007bff; color: white; border: none; border-radius: 3px; cursor: pointer; }
button:hover { background: #0056b3; }
.result { margin-top: 10px; padding: 10px; background: #f8f9fa; border-radius: 3px; }
input, select { padding: 8px; margin: 5px; border: 1px solid #ddd; border-radius: 3px; }
</style>
</head>
<body>
<h1>🎲 MCP 抽签工具</h1>
<p>这是一个基于 MCP (Model Context Protocol) 的抽签工具演示。</p>
<div class="tool">
<h3>🎯 抽签工具</h3>
<input type="text" id="lotteryOptions" placeholder="输入选项,用逗号分隔" value="苹果,香蕉,橙子,葡萄,草莓" style="width: 300px;">
<br>
<label>抽取数量: <input type="number" id="lotteryCount" value="1" min="1"></label>
<label><input type="checkbox" id="allowDuplicate"> 允许重复</label>
<br>
<button onclick="callLottery()">开始抽签</button>
<div id="lotteryResult" class="result"></div>
</div>
<div class="tool">
<h3>🎲 投骰子</h3>
<label>面数: <input type="number" id="diceSides" value="6" min="2"></label>
<label>数量: <input type="number" id="diceCount" value="1" min="1"></label>
<br>
<button onclick="callDice()">投掷骰子</button>
<div id="diceResult" class="result"></div>
</div>
<div class="tool">
<h3>🪙 抛硬币</h3>
<label>次数: <input type="number" id="coinCount" value="1" min="1"></label>
<br>
<button onclick="callCoin()">抛硬币</button>
<div id="coinResult" class="result"></div>
</div>
<script>
async function callLottery() {
const options = document.getElementById('lotteryOptions').value.split(',').map(s => s.trim()).filter(s => s);
const count = parseInt(document.getElementById('lotteryCount').value);
const allowDuplicate = document.getElementById('allowDuplicate').checked;
if (options.length === 0) {
alert('请输入至少一个选项');
return;
}
try {
const response = await fetch('/tools/call', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'draw_lottery',
arguments: { options, count, allow_duplicate: allowDuplicate }
})
});
const result = await response.json();
document.getElementById('lotteryResult').innerHTML = result.result.content[0].text.replace(/\\n/g, '<br>');
} catch (error) {
document.getElementById('lotteryResult').innerHTML = '错误: ' + error.message;
}
}
async function callDice() {
const sides = parseInt(document.getElementById('diceSides').value);
const count = parseInt(document.getElementById('diceCount').value);
try {
const response = await fetch('/tools/call', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'roll_dice',
arguments: { sides, count }
})
});
const result = await response.json();
document.getElementById('diceResult').innerHTML = result.result.content[0].text.replace(/\\n/g, '<br>');
} catch (error) {
document.getElementById('diceResult').innerHTML = '错误: ' + error.message;
}
}
async function callCoin() {
const count = parseInt(document.getElementById('coinCount').value);
try {
const response = await fetch('/tools/call', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'flip_coin',
arguments: { count }
})
});
const result = await response.json();
document.getElementById('coinResult').innerHTML = result.result.content[0].text.replace(/\\n/g, '<br>');
} catch (error) {
document.getElementById('coinResult').innerHTML = '错误: ' + error.message;
}
}
</script>
</body>
</html>
`);
});
// 启动服务器
function main() {
try {
// 启动 HTTP 服务器
app.listen(PORT, () => {
console.log(`🎲 抽签工具 HTTP 服务器已启动`);
console.log(`📡 HTTP 端口: http://localhost:${PORT}`);
console.log(`🌐 Web 界面: http://localhost:${PORT}`);
console.log(`🔧 工具列表: http://localhost:${PORT}/tools`);
console.log(`⚡ 工具调用: http://localhost:${PORT}/tools/call`);
console.log(`❤️ 健康检查: http://localhost:${PORT}/health`);
});
} catch (error) {
console.error('服务器启动失败:', error);
process.exit(1);
}
}
main();