import { FastMCP } from "fastmcp";
import { z } from "zod";
import * as fs from "fs";
import * as path from "path";
import * as yaml from "js-yaml";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Load configuration
const configPath = path.join(__dirname, "..", "config.yaml");
const configContent = fs.readFileSync(configPath, "utf-8");
const config = yaml.load(configContent) as any;
const server = new FastMCP({
name: config.mcp.name,
version: config.mcp.version,
});
server.addTool({
name: "get_riddle",
description: "Get a classic Sphinx riddle by index (1-3). Returns title, puzzle description, and a resource URL (x402 payment required for answer)",
annotations:{readOnlyHint: true},
parameters: z.object({
index: z.number().min(1).max(3).describe("Index of the riddle (1-3)"),
}),
execute: async (args: { index: number }) => {
const riddle = config.riddles[args.index - 1];
if (!riddle) {
return JSON.stringify({
error: "Riddle not found",
message: `Index ${args.index} is out of range. Please provide a number between 1 and 3.`
});
}
const timestamp = Date.now();
// Generate dynamic answer URL with payment ID
const answerUrl = `https://${config.x402Server.domain}/riddle/${args.index}/answer?payid=${timestamp}`;
return JSON.stringify({
title: riddle.title,
question: riddle.question,
answerUrl: answerUrl,
payid: timestamp.toString(),
}, null, 2);
},
});
server.addTool({
name: "get_answer",
description: "Get the answer for a paid riddle using a payid.",
annotations:{readOnlyHint: true},
parameters: z.object({
payid: z.string().describe("The payment ID received from get_riddle"),
}),
execute: async (args: { payid: string }) => {
try {
const verifyUrl = `http://${config.x402Server.host}:${config.x402Server.port}/verify-payment`;
const response = await fetch(verifyUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ payid: args.payid }),
});
if (!response.ok) {
return JSON.stringify({ error: "Failed to verify payment with server" });
}
const result = await response.json() as any;
if (result.verified) {
const riddle = config.riddles[result.riddleId - 1];
return JSON.stringify({
status: "success",
riddle: riddle.title,
question: riddle.question,
answer: riddle.answer
}, null, 2);
} else {
return JSON.stringify({
error: "Payment not found or already used",
message: "Please ensure you have paid via the provided link and the payid is correct."
});
}
} catch (error: any) {
return JSON.stringify({
error: "Internal server error",
details: error.message
});
}
},
});
server.start({
transportType: "httpStream",
httpStream: {
host: config.mcp.host,
port: config.mcp.port,
endpoint: config.mcp.endpoint,
},
});
console.log(`🎭 ${config.mcp.name} started on http://${config.mcp.host}:${config.mcp.port}`);
console.log(`📚 Loaded ${config.riddles.length} riddles`);