UNPKG

@pinkpixel/prysm-mcp

Version:

MCP server for the Prysm web scraper - enabling AI assistants to scrape web content

994 lines (982 loc) 32.9 kB
#!/usr/bin/env node var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); // node_modules/tsup/assets/esm_shims.js var init_esm_shims = __esm({ "node_modules/tsup/assets/esm_shims.js"() { "use strict"; } }); // node_modules/json2md/lib/converters.js var require_converters = __commonJS({ "node_modules/json2md/lib/converters.js"(exports, module) { "use strict"; init_esm_shims(); var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function(obj) { return typeof obj; } : function(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var converters = module.exports = {}; var generateHeader = function generateHeader2(repeat) { return function(input, json2md2) { return "#".repeat(repeat) + " " + json2md2(input); }; }; var indent = function indent2(content, spaces, ignoreFirst) { var lines = content; if (typeof content === "string") { lines = content.split("\n"); } if (ignoreFirst) { if (lines.length <= 1) { return lines.join("\n"); } return lines[0] + "\n" + indent2(lines.slice(1), spaces, false); } return lines.map(function(c) { return " ".repeat(spaces) + c; }).join("\n"); }; var parseTextFormat = function parseTextFormat2(text) { var formats = { strong: "**", italic: "*", underline: "_", strikethrough: "~~" }; return text.replace(/<\/?strong\>/gi, formats.strong).replace(/<\/?bold\>/gi, formats.strong).replace(/<\/?em\>/gi, formats.italic).replace(/<\/?italic\>/gi, formats.italic).replace(/<\/?u\>/gi, formats.underline).replace(/<\/?strike\>/gi, formats.strikethrough); }; converters.h1 = generateHeader(1); converters.h2 = generateHeader(2); converters.h3 = generateHeader(3); converters.h4 = generateHeader(4); converters.h5 = generateHeader(5); converters.h6 = generateHeader(6); converters.blockquote = function(input, json2md2) { return json2md2(input, "> "); }; converters.img = function(input, json2md2) { debugger; if (Array.isArray(input)) { return json2md2(input, "", "img"); } if (typeof input === "string") { return converters.img({ source: input, title: "", alt: "" }); } input.title = input.title || ""; input.alt = input.alt || ""; return "![" + input.alt + "](" + input.source + ' "' + input.title + '")'; }; converters.ul = function(input, json2md2) { var c = ""; for (var i = 0; i < input.length; ++i) { var marker = ""; var type2 = Object.keys(input[i])[0]; if (type2 !== "ul" && type2 !== "ol" && type2 !== "taskLists") { marker += "\n - "; } c += marker + parseTextFormat(indent(json2md2(input[i]), 4, true)); } return c; }; converters.ol = function(input, json2md2) { var c = ""; var jumpCount = 0; for (var i = 0; i < input.length; ++i) { var marker = ""; var type2 = Object.keys(input[i])[0]; if (type2 !== "ul" && type2 !== "ol" && type2 !== "taskLists") { marker = "\n " + (i + 1 - jumpCount) + ". "; } else { jumpCount++; } c += marker + parseTextFormat(indent(json2md2(input[i]), 4, true)); } return c; }; converters.taskLists = function(input, json2md2) { var c = ""; for (var i = 0; i < input.length; ++i) { var marker = ""; var type2 = Object.keys(input[i])[0]; if (type2 !== "ul" && type2 !== "ol" && type2 !== "taskLists") { marker += input[i].isDone ? "\n - [x] " : "\n - [ ] "; } c += marker + parseTextFormat(indent(json2md2(input[i].title || input[i]), 4, true)); } return c; }; converters.code = function(input, json2md2) { var c = "```" + (input.language || "") + "\n"; if (Array.isArray(input.content)) { c += input.content.join("\n"); } else { c += input.content; } c += "\n```"; return c; }; converters.p = function(input, json2md2) { return parseTextFormat(json2md2(input, "\n")); }; converters.table = function(input, json2md2) { var _PREFERRED_LENGTH_PER; var ALIGNMENT = { CENTER: "center", RIGHT: "right", LEFT: "left", NONE: "none" }; var PREFERRED_LENGTH_PER_ALIGNMENT = (_PREFERRED_LENGTH_PER = {}, _defineProperty(_PREFERRED_LENGTH_PER, ALIGNMENT.CENTER, 3), _defineProperty(_PREFERRED_LENGTH_PER, ALIGNMENT.RIGHT, 2), _defineProperty(_PREFERRED_LENGTH_PER, ALIGNMENT.LEFT, 2), _defineProperty(_PREFERRED_LENGTH_PER, ALIGNMENT.NONE, 1), _PREFERRED_LENGTH_PER); if ((typeof input === "undefined" ? "undefined" : _typeof(input)) !== "object" || !input.hasOwnProperty("headers") || !input.hasOwnProperty("rows")) { return ""; } var alignment = input.headers.map(function(_, index) { return input.aligns && input.aligns[index] ? input.aligns[index] : ALIGNMENT.NONE; }); var preferred_lengths = input.headers.map(function(header2, index) { return Math.max(PREFERRED_LENGTH_PER_ALIGNMENT[alignment[index]], header2.length - 2); }); if (input.pretty === true) { input.rows.forEach(function(row) { (Array.isArray(row) ? row : input.headers.map(function(col_id) { return row[col_id]; })).forEach(function(cell, index) { preferred_lengths[index] = Math.max(preferred_lengths[index], cell.length - 2); }); }); } var fill_right = function fill_right2(diff, header2) { return " ".repeat(diff) + header2; }; var fill_left = function fill_left2(diff, header2) { return header2 + " ".repeat(diff); }; var fill_center = function fill_center2(diff, header2) { return " ".repeat(Math.floor(diff / 2)) + header2 + " ".repeat(Math.ceil(diff / 2)); }; var fill_th = function fill_th2(header2, index) { var diff = preferred_lengths[index] + 2 - header2.length; switch (alignment[index]) { case ALIGNMENT.RIGHT: return fill_right(diff, header2); case ALIGNMENT.LEFT: return fill_left(diff, header2); case ALIGNMENT.CENTER: case ALIGNMENT.NONE: default: return fill_center(diff, header2); } }; var fill_td = function fill_td2(header2, index) { var diff = preferred_lengths[index] + 2 - header2.length; switch (alignment[index]) { case ALIGNMENT.RIGHT: return fill_right(diff, header2); case ALIGNMENT.NONE: case ALIGNMENT.LEFT: return fill_left(diff, header2); case ALIGNMENT.CENTER: default: return fill_center(diff, header2); } }; var column_names = input.headers.map(fill_th); var header = "| " + column_names.join(" | ") + " |"; var spaces = "| " + input.headers.map(function(_, index) { var inner = "-".repeat(preferred_lengths[index]); switch (alignment[index]) { case ALIGNMENT.CENTER: return ":" + inner + ":"; case ALIGNMENT.RIGHT: return "-" + inner + ":"; case ALIGNMENT.LEFT: return ":" + inner + "-"; case ALIGNMENT.NONE: default: return "-" + inner + "-"; } }).join(" | ") + " |"; var fill_tbody_cell = function fill_tbody_cell2(cell, index) { if (input.pretty !== true) return cell; return fill_td(cell, index); }; var data = input.rows.map(function(row) { return "| " + (Array.isArray(row) ? row : input.headers.map(function(col_id) { return row[col_id]; })).map(function(cell) { return json2md2(cell); }).map(function(cell) { return parseTextFormat(cell); }).map(function(cell) { return cell.replace(/([^\\])\|/, "$1\\|"); }).map(function(cell) { return cell.trim(); }).map(fill_tbody_cell).join(" | ") + " |"; }).join("\n"); return [header, spaces, data].join("\n"); }; converters.link = function(input, json2md2) { if (Array.isArray(input)) { return json2md2(input, "", "link"); } if (typeof input === "string") { return converters.link({ source: input, title: "" }); } return "[" + input.title + "](" + input.source + ")"; }; converters.hr = function(input, json2md2) { return "---"; }; } }); // node_modules/indento/lib/index.js var require_lib = __commonJS({ "node_modules/indento/lib/index.js"(exports, module) { "use strict"; init_esm_shims(); function indento(input, width, char) { char = typeof char !== "string" ? " " : char; return String(input).replace(/^/gm, char.repeat(width)); } module.exports = indento; } }); // node_modules/json2md/lib/index.js var require_lib2 = __commonJS({ "node_modules/json2md/lib/index.js"(exports, module) { "use strict"; init_esm_shims(); var converters = require_converters(); var indento = require_lib(); function json2md2(data, prefix, _type) { prefix = prefix || ""; if (typeof data === "string" || typeof data === "number") { return indento(data, 1, prefix); } var content = []; if (Array.isArray(data)) { for (var i = 0; i < data.length; ++i) { content.push(indento(json2md2(data[i], "", _type), 1, prefix)); } return content.join("\n"); } else if (_type) { var mdText = ""; var func = converters[_type || type]; if (typeof func === "function") { mdText += indento(func(_type ? data : data[type], json2md2), 1, prefix) + "\n"; } else { throw new Error("There is no such converter: " + type); } return mdText; } else { var _mdText = ""; Object.keys(data).forEach(function(type2, index, array) { var func2 = converters[_type || type2]; if (typeof func2 === "function") { _mdText += indento(func2(_type ? data : data[type2], json2md2), 1, prefix) + "\n"; } else { throw new Error("There is no such converter: " + type2); } }); return _mdText; } } json2md2.async = function(data, prefix, _type) { return Promise.resolve().then(function() { prefix = prefix || ""; if (typeof data === "string" || typeof data === "number") { return indento(data, 1, prefix); } var content = []; if (Array.isArray(data)) { var promises = data.map(function(d, index) { return Promise.resolve().then(function() { return json2md2.async(d, "", _type); }).then(function(result) { return indento(result, 1, prefix); }).then(function(result) { content[index] = result; }); }); return Promise.all(promises).then(function() { return content.join("\n"); }); } else { var _type2 = Object.keys(data)[0], func = converters[_type || _type2]; if (typeof func === "function") { return Promise.resolve().then(function() { return func(_type ? data : data[_type2], json2md2); }).then(function(result) { return indento(result, 1, prefix) + "\n"; }); } throw new Error("There is no such converter: " + _type2); } }); }; json2md2.converters = converters; module.exports = json2md2; } }); // src/index.ts init_esm_shims(); import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js"; // src/config.ts init_esm_shims(); // src/tools/scrapeFocused.ts init_esm_shims(); import * as prysm from "@pinkpixel/prysm-llm"; var scrapeFocused = { name: "scrapeFocused", description: "Fast web scraping optimized for speed (fewer scrolls, main content only)", parameters: { type: "object", properties: { url: { type: "string", description: "URL of the webpage to scrape" }, maxScrolls: { type: "number", description: "Maximum number of scroll attempts (default: 5)" }, scrollDelay: { type: "number", description: "Delay between scrolls in ms (default: 1000)" }, pages: { type: "number", description: "Number of pages to scrape (if pagination is present)" }, scrapeImages: { type: "boolean", description: "Whether to include images in the scrape result" }, downloadImages: { type: "boolean", description: "Whether to download images locally" }, maxImages: { type: "number", description: "Maximum number of images to extract" }, minImageSize: { type: "number", description: "Minimum width/height for images in pixels" }, output: { type: "string", description: "Output directory for general results" }, imageOutput: { type: "string", description: "Output directory for downloaded images" } }, required: ["url"] }, handler: async (params) => { const { url, maxScrolls = 5, scrollDelay = 1e3, pages = 1, scrapeImages = false, downloadImages = false, maxImages = 20, minImageSize = 100, output, imageOutput } = params; try { const options = { maxScrolls, scrollDelay, pages, focused: true, // Use focused mode for faster extraction standard: false, deep: false, scrapeImages: scrapeImages || downloadImages, downloadImages, maxImages, minImageSize, output: output || config.serverOptions.defaultOutputDir, // Use configured default if not provided imageOutput: imageOutput || config.serverOptions.defaultImageOutputDir // Use configured default if not provided }; const result = await prysm.scrape(url, options); if (result.content && result.content.length > 0) { if (result.content.length > 10) { result.content = result.content.slice(0, 10); result.content.push("(Content truncated due to size limitations)"); } result.content = result.content.map((section) => { if (section.length > 3e3) { return section.substring(0, 3e3) + "... (truncated)"; } return section; }); } if (result.images && result.images.length > 10) { result.images = result.images.slice(0, 10); } return result; } catch (error) { console.error(`Error scraping ${url}:`, error); return { title: "Scraping Error", content: [`Failed to scrape ${url}: ${error instanceof Error ? error.message : String(error)}`], images: [], metadata: { error: true }, url, structureType: "error", paginationType: "none", extractionMethod: "none" }; } } }; // src/tools/scrapeBalanced.ts init_esm_shims(); import * as prysm2 from "@pinkpixel/prysm-llm"; var scrapeBalanced = { name: "scrapeBalanced", description: "Balanced web scraping approach with good coverage and reasonable speed", parameters: { type: "object", properties: { url: { type: "string", description: "URL of the webpage to scrape" }, maxScrolls: { type: "number", description: "Maximum number of scroll attempts (default: 10)" }, scrollDelay: { type: "number", description: "Delay between scrolls in ms (default: 2000)" }, pages: { type: "number", description: "Number of pages to scrape (if pagination is present)" }, scrapeImages: { type: "boolean", description: "Whether to include images in the scrape result" }, downloadImages: { type: "boolean", description: "Whether to download images locally" }, maxImages: { type: "number", description: "Maximum number of images to extract" }, minImageSize: { type: "number", description: "Minimum width/height for images in pixels" }, timeout: { type: "number", description: "Maximum time in ms for the scrape operation (default: 30000)" }, output: { type: "string", description: "Output directory for general results" }, imageOutput: { type: "string", description: "Output directory for downloaded images" } }, required: ["url"] }, handler: async (params) => { const { url, maxScrolls = 10, scrollDelay = 2e3, pages = 1, scrapeImages = false, downloadImages = false, maxImages = 50, minImageSize = 100, timeout = 3e4, output, imageOutput } = params; try { const options = { maxScrolls, scrollDelay, pages, focused: false, standard: true, // Use standard mode for balanced extraction deep: false, scrapeImages: scrapeImages || downloadImages, downloadImages, maxImages, minImageSize, timeout, // Add timeout option output: output || config.serverOptions.defaultOutputDir, // Use configured default if not provided imageOutput: imageOutput || config.serverOptions.defaultImageOutputDir // Use configured default if not provided }; const scrapePromise = prysm2.scrape(url, options); const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error(`Scraping timed out after ${timeout}ms`)), timeout); }); const result = await Promise.race([scrapePromise, timeoutPromise]); if (result.content && result.content.length > 0) { if (result.content.length > 20) { result.content = result.content.slice(0, 20); result.content.push("(Content truncated due to size limitations)"); } result.content = result.content.map((section) => { if (section.length > 5e3) { return section.substring(0, 5e3) + "... (truncated)"; } return section; }); } if (result.images && result.images.length > 20) { result.images = result.images.slice(0, 20); } return result; } catch (error) { console.error(`Error scraping ${url}:`, error); return { title: "Scraping Error", content: [`Failed to scrape ${url}: ${error instanceof Error ? error.message : String(error)}`], images: [], metadata: { error: true }, url, structureType: "error", paginationType: "none", extractionMethod: "none" }; } } }; // src/tools/scrapeDeep.ts init_esm_shims(); import * as prysm3 from "@pinkpixel/prysm-llm"; var scrapeDeep = { name: "scrapeDeep", description: "Maximum extraction web scraping (slower but thorough)", parameters: { type: "object", properties: { url: { type: "string", description: "URL of the webpage to scrape" }, maxScrolls: { type: "number", description: "Maximum number of scroll attempts (default: 20)" }, scrollDelay: { type: "number", description: "Delay between scrolls in ms (default: 3000)" }, pages: { type: "number", description: "Number of pages to scrape (if pagination is present)" }, scrapeImages: { type: "boolean", description: "Whether to include images in the scrape result" }, downloadImages: { type: "boolean", description: "Whether to download images locally" }, maxImages: { type: "number", description: "Maximum number of images to extract" }, minImageSize: { type: "number", description: "Minimum width/height for images in pixels" }, output: { type: "string", description: "Output directory for general results" }, imageOutput: { type: "string", description: "Output directory for downloaded images" } }, required: ["url"] }, handler: async (params) => { const { url, maxScrolls = 20, scrollDelay = 3e3, pages = 1, scrapeImages = false, downloadImages = false, maxImages = 100, minImageSize = 100, output, imageOutput } = params; try { const options = { maxScrolls, scrollDelay, pages, focused: false, standard: false, deep: true, // Use deep mode for thorough extraction scrapeImages: scrapeImages || downloadImages, downloadImages, maxImages, minImageSize, output: output || config.serverOptions.defaultOutputDir, // Use configured default if not provided imageOutput: imageOutput || config.serverOptions.defaultImageOutputDir // Use configured default if not provided }; const result = await prysm3.scrape(url, options); if (result.content && result.content.length > 0) { if (result.content.length > 30) { result.content = result.content.slice(0, 30); result.content.push("(Content truncated due to size limitations)"); } result.content = result.content.map((section) => { if (section.length > 1e4) { return section.substring(0, 1e4) + "... (truncated)"; } return section; }); } if (result.images && result.images.length > 30) { result.images = result.images.slice(0, 30); } return result; } catch (error) { console.error(`Error scraping ${url}:`, error); return { title: "Scraping Error", content: [`Failed to scrape ${url}: ${error instanceof Error ? error.message : String(error)}`], images: [], metadata: { error: true }, url, structureType: "error", paginationType: "none", extractionMethod: "none" }; } } }; // src/tools/formatResult.ts init_esm_shims(); import fs from "node:fs/promises"; import path from "node:path"; var import_json2md = __toESM(require_lib2(), 1); function formatAsMarkdown(data, includeImages) { const mdData = []; mdData.push({ h1: data.title }); if (data.metadata && Object.keys(data.metadata).length > 0) { mdData.push({ h2: "Metadata" }); const metadataItems = []; for (const [key, value] of Object.entries(data.metadata)) { if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { metadataItems.push(`**${key}**: ${value}`); } } if (metadataItems.length > 0) { mdData.push({ ul: metadataItems }); } } mdData.push({ h2: "Content" }); if (data.content && data.content.length > 0) { data.content.forEach((section) => { mdData.push({ p: section }); }); } if (includeImages && data.images && data.images.length > 0) { mdData.push({ h2: "Images" }); data.images.forEach((image) => { const alt = image.alt || "Image"; mdData.push({ img: { title: alt, source: image.url } }); }); } return (0, import_json2md.default)(mdData); } function formatAsHTML(data, includeImages) { let html = `<h1>${data.title}</h1>`; if (data.metadata && Object.keys(data.metadata).length > 0) { html += "<h2>Metadata</h2><ul>"; for (const [key, value] of Object.entries(data.metadata)) { if (typeof value === "string" || typeof value === "number") { html += `<li><strong>${key}</strong>: ${value}</li>`; } } html += "</ul>"; } html += "<h2>Content</h2>"; for (const section of data.content) { html += `<p>${section}</p>`; } if (includeImages && data.images && data.images.length > 0) { html += "<h2>Images</h2>"; for (const image of data.images) { const alt = image.alt || "Image"; html += `<img src="${image.url}" alt="${alt}" />`; } } return html; } function generateDefaultFilename(data, format) { const safeTitle = data.title ? data.title.replace(/[^a-z0-9]/gi, "_").toLowerCase().substring(0, 50) : "scrape_result"; const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.-]/g, "_"); const extension = format === "markdown" ? ".md" : format === "html" ? ".html" : ".json"; return `${safeTitle}_${timestamp}${extension}`; } async function saveToFile(content, outputPath, format, data) { try { let resolvedPath = outputPath; if (!path.isAbsolute(outputPath)) { resolvedPath = path.join(config.serverOptions.defaultOutputDir, outputPath); } const stats = await fs.stat(resolvedPath).catch(() => null); if (stats && stats.isDirectory()) { resolvedPath = path.join(resolvedPath, generateDefaultFilename(data, format)); } else if (!path.extname(resolvedPath)) { const extension = format === "markdown" ? ".md" : format === "html" ? ".html" : ".json"; resolvedPath = `${resolvedPath}${extension}`; } const dir = path.dirname(resolvedPath); await fs.mkdir(dir, { recursive: true }); await fs.writeFile(resolvedPath, content, "utf-8"); return resolvedPath; } catch (error) { throw new Error(`Failed to save formatted result: ${error instanceof Error ? error.message : String(error)}`); } } var formatResult = { name: "formatResult", description: "Format scraped data into different structured formats (markdown, HTML, JSON)", parameters: { type: "object", properties: { data: { type: "object", description: "The scraped data to format" }, format: { type: "string", enum: ["markdown", "html", "json"], description: "The format to convert the data to" }, includeImages: { type: "boolean", description: "Whether to include images in the formatted output (default: true)" }, output: { type: "string", description: "File path to save the formatted result. If not provided, will use the default directory." } }, required: ["data", "format"] }, handler: async (params) => { const { data, format, includeImages = true, output } = params; try { let formatted; switch (format) { case "markdown": formatted = formatAsMarkdown(data, includeImages); break; case "html": formatted = formatAsHTML(data, includeImages); break; case "json": formatted = JSON.stringify(data, null, 2); break; default: throw new Error(`Unsupported format: ${format}`); } let savedTo; if (output) { savedTo = await saveToFile(formatted, output, format, data); } else { const defaultFilename = generateDefaultFilename(data, format); savedTo = await saveToFile(formatted, defaultFilename, format, data); } if (format === "json" && formatted.length > 1e4) { formatted = formatted.substring(0, 1e4) + "... (truncated for display, full content saved to file)"; } return { formatted, format, ...savedTo && { savedTo } }; } catch (error) { throw new Error(`Failed to format result: ${error instanceof Error ? error.message : String(error)}`); } } }; // src/config.ts import path2 from "node:path"; import os from "node:os"; var defaultOutputDir = process.env.PRYSM_OUTPUT_DIR || path2.join(os.homedir(), "prysm-mcp", "output"); var defaultImageOutputDir = process.env.PRYSM_IMAGE_OUTPUT_DIR || path2.join(defaultOutputDir, "images"); console.debug(`Setting default output directory to: ${defaultOutputDir}`); console.debug(`Setting default image output directory to: ${defaultImageOutputDir}`); var config = { name: "prysm-mcp-server", description: "Prysm web scraper MCP server for AI assistants", version: "1.1.2", model: { provider: "Pink Pixel", name: "Prysm", version: "1.1.2" }, tools: [ scrapeFocused, scrapeBalanced, scrapeDeep, // analyzeUrl, formatResult ], serverOptions: { defaultOutputDir, defaultImageOutputDir } }; // src/index.ts var server = new Server( { name: config.name, version: config.version }, { capabilities: { tools: {} } } ); server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: config.tools.map((tool) => ({ name: tool.name, description: tool.description, inputSchema: tool.parameters })) }; }); server.setRequestHandler( CallToolRequestSchema, async (request) => { try { if (!request.params.arguments) { throw new Error("Arguments are required"); } const tool = config.tools.find((t) => t.name === request.params.name); if (!tool) { throw new Error(`Unknown tool: ${request.params.name}`); } const result = await tool.handler(request.params.arguments); return { content: [{ type: "text", text: JSON.stringify(result) }] }; } catch (error) { console.error("Error executing tool:", error); return { content: [ { type: "text", text: JSON.stringify({ error: error instanceof Error ? error.message : String(error) }) } ] }; } } ); async function startServer() { try { const transport = new StdioServerTransport(); await server.connect(transport); console.error(`Prysm MCP Server initialized and ready`); console.error(`Available tools: ${config.tools.map((t) => t.name).join(", ")}`); const signals = ["SIGINT", "SIGTERM"]; for (const signal of signals) { process.on(signal, async () => { console.error(` ${signal} received, shutting down server...`); process.exit(0); }); } } catch (error) { console.error("Failed to start MCP server:", error); if (error instanceof Error) { console.error("Error details:", error.message); console.error("Stack trace:", error.stack); } console.error("Configuration:", JSON.stringify({ toolCount: config.tools.length, toolNames: config.tools.map((t) => t.name) }, null, 2)); process.exit(1); } } startServer(); //# sourceMappingURL=index.js.map