#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import fs from "fs/promises";
import path from "path";
import { fileURLToPath } from "url";
import { dirname } from "path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Директория для сохранения отчетов
const REPORTS_DIR = path.join(__dirname, "bitcoin_reports");
class FileServer {
constructor() {
// Создаем директорию для отчетов если её нет
this.initReportsDir();
this.server = new Server(
{
name: "file-mcp-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error("[MCP Error]", error);
process.on("SIGINT", async () => {
await this.server.close();
process.exit(0);
});
}
async initReportsDir() {
try {
await fs.mkdir(REPORTS_DIR, { recursive: true });
console.error(`Reports directory: ${REPORTS_DIR}`);
} catch (error) {
console.error("Failed to create reports directory:", error);
}
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "save_bitcoin_report",
description: "Сохраняет отчет об анализе курса биткоина в текстовый файл",
inputSchema: {
type: "object",
properties: {
content: {
type: "string",
description: "Содержимое отчета для сохранения",
},
price: {
type: "number",
description: "Текущий курс биткоина",
},
previous_price: {
type: "number",
description: "Предыдущий курс биткоина (опционально)",
},
},
required: ["content", "price"],
},
},
{
name: "list_reports",
description: "Получает список всех сохраненных отчетов",
inputSchema: {
type: "object",
properties: {
limit: {
type: "number",
description: "Максимальное количество отчетов (default: 10)",
},
},
},
},
{
name: "read_report",
description: "Читает содержимое конкретного отчета",
inputSchema: {
type: "object",
properties: {
filename: {
type: "string",
description: "Имя файла отчета",
},
},
required: ["filename"],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case "save_bitcoin_report":
return await this.saveBitcoinReport(args);
case "list_reports":
return await this.listReports(args);
case "read_report":
return await this.readReport(args);
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
return {
content: [
{
type: "text",
text: `Error: ${error.message}`,
},
],
isError: true,
};
}
});
}
async saveBitcoinReport(args) {
const { content, price, previous_price } = args;
if (!content) {
throw new Error("Content is required");
}
// Генерируем имя файла с датой и временем
const timestamp = new Date();
const filename = `report_${timestamp.getTime()}.txt`;
const filepath = path.join(REPORTS_DIR, filename);
// Форматируем отчет
const report = `
========================================
BITCOIN ANALYSIS REPORT
========================================
Date: ${timestamp.toLocaleString("ru-RU")}
Current Price: $${price}
${previous_price ? `Previous Price: $${previous_price}` : ""}
${previous_price ? `Change: $${(price - previous_price).toFixed(2)} (${(((price - previous_price) / previous_price) * 100).toFixed(2)}%)` : ""}
ANALYSIS:
${content}
========================================
Generated by AI Bitcoin Monitor
========================================
`.trim();
// Сохраняем в файл
await fs.writeFile(filepath, report, "utf-8");
return {
content: [
{
type: "text",
text: JSON.stringify({
success: true,
filename: filename,
filepath: filepath,
message: "Report saved successfully",
}),
},
],
};
}
async listReports(args) {
const limit = args.limit || 10;
try {
const files = await fs.readdir(REPORTS_DIR);
const reportFiles = files
.filter((f) => f.startsWith("report_") && f.endsWith(".txt"))
.sort()
.reverse()
.slice(0, limit);
const reports = [];
for (const file of reportFiles) {
const filepath = path.join(REPORTS_DIR, file);
const stats = await fs.stat(filepath);
reports.push({
filename: file,
created: stats.mtime.toISOString(),
size: stats.size,
});
}
return {
content: [
{
type: "text",
text: JSON.stringify({
reports: reports,
total: reports.length,
}),
},
],
};
} catch (error) {
throw new Error(`Failed to list reports: ${error.message}`);
}
}
async readReport(args) {
const { filename } = args;
if (!filename) {
throw new Error("Filename is required");
}
const filepath = path.join(REPORTS_DIR, filename);
try {
const content = await fs.readFile(filepath, "utf-8");
return {
content: [
{
type: "text",
text: content,
},
],
};
} catch (error) {
throw new Error(`Failed to read report: ${error.message}`);
}
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error("File MCP server running on stdio");
}
}
const server = new FileServer();
server.run();