UNPKG

precious-metal-mcp

Version:

An MCP tool to get precious metal prices.

168 lines (167 loc) 6.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TROY_OUNCE_TO_GRAMS = exports.finnhubClient = void 0; exports.getPreciousMetalPriceLogic = getPreciousMetalPriceLogic; require("dotenv/config"); const axios_1 = __importDefault(require("axios")); const fastmcp_1 = require("fastmcp"); const zod_1 = require("zod"); // --- Environment Variable and API Key --- // We export it to allow mocking in tests exports.finnhubClient = axios_1.default.create({ baseURL: "https://finnhub.io/api/v1", // Token will be set in the main function after validation }); // --- Constants --- exports.TROY_OUNCE_TO_GRAMS = 31.1034768; // --- FastMCP Server Setup --- const server = new fastmcp_1.FastMCP({ name: "PreciousMetalPrice", version: "1.0.2", }); const getPreciousMetalPriceParams = zod_1.z.object({ market: zod_1.z .enum(["london", "new_york", "shanghai"]) .describe("The trading market"), metal: zod_1.z .enum(["gold", "silver"]) .describe("The metal to query (gold or silver)"), type: zod_1.z .enum(["futures", "spot"]) .describe("The price type (spot or futures)"), }); async function getPreciousMetalPriceLogic(args) { console.log("Executing with arguments:", args); try { // 1. Determine the symbol and currency based on market let symbol = ""; let currency = ""; switch (args.market) { case "london": // Finnhub provides limited GBP pairs for precious metals. // We'll get the USD price and convert using GBP/USD rate. symbol = args.metal === "gold" ? "OANDA:XAU_USD" : "OANDA:XAG_USD"; currency = "GBP"; break; case "new_york": symbol = args.metal === "gold" ? "OANDA:XAU_USD" : "OANDA:XAG_USD"; currency = "USD"; break; case "shanghai": // For Shanghai Gold Exchange, prices are often in CNY/gram already. // This requires a different symbol and conversion logic. // Placeholder: Using a common proxy symbol. symbol = "XAUCNY"; currency = "CNY"; break; } // 2. Fetch original price const priceResponse = await exports.finnhubClient.get("/quote", { params: { symbol }, }); const originalPrice = priceResponse.data.c; if (typeof originalPrice !== "number" || originalPrice === 0) { throw new Error(`Could not fetch a valid price for symbol ${symbol}.`); } // If the market is Shanghai, we might already have CNY/gram. // For this example, we'll assume the provided symbol gives price in CNY/ounce // and still needs weight conversion. if (args.market === "shanghai") { const priceInCnyPerGram = originalPrice / exports.TROY_OUNCE_TO_GRAMS; const response = { converted_price: { unit: "CNY/gram", value: parseFloat(priceInCnyPerGram.toFixed(2)), }, exchange_rate: { from: currency, rate: 1, to: "CNY", }, market: args.market, metal: args.metal, original_price: { unit: `${currency}/oz (Assumed)`, value: originalPrice, }, timestamp: new Date().toISOString(), type: args.type, }; return response; } // 3. Fetch exchange rate for non-Shanghai markets let exchangeRate = 1; const exchangeSymbol = `${currency}/CNY`; const rateResponse = await exports.finnhubClient.get("/quote", { params: { symbol: `OANDA:${currency}_CNY` }, }); exchangeRate = rateResponse.data.c; if (typeof exchangeRate !== "number" || exchangeRate === 0) { throw new Error(`Could not fetch a valid exchange rate for ${exchangeSymbol}.`); } // 4. Perform conversions const priceInCnyPerOunce = originalPrice * exchangeRate; const priceInCnyPerGram = priceInCnyPerOunce / exports.TROY_OUNCE_TO_GRAMS; const response = { converted_price: { unit: "CNY/gram", value: parseFloat(priceInCnyPerGram.toFixed(2)), }, exchange_rate: { from: currency, rate: exchangeRate, to: "CNY", }, market: args.market, metal: args.metal, original_price: { unit: `${currency}/oz`, value: originalPrice, }, timestamp: new Date().toISOString(), type: args.type, }; return response; } catch (error) { const errorMessage = error instanceof Error ? error.message : "An unknown error occurred."; console.error("Error executing tool logic:", error); // Re-throw the error to be caught by the calling function in tests throw new Error(errorMessage); } } server.addTool({ description: "Queries spot and futures prices for gold and silver from specified markets (New York, London, or Shanghai), returning the value in CNY per gram.", execute: async (args) => { try { const result = await getPreciousMetalPriceLogic(args); return JSON.stringify(result, null, 2); } catch (error) { const errorMessage = error instanceof Error ? error.message : "An unknown error occurred."; return JSON.stringify({ error: true, message: errorMessage }); } }, name: "getPreciousMetalPrice", parameters: getPreciousMetalPriceParams, }); async function main() { const apiKey = process.env.FINNHUB_API_KEY; if (!apiKey) { console.error("Error: FINNHUB_API_KEY environment variable not set."); console.error("Please create a .env file and add FINNHUB_API_KEY=<your_key>"); process.exit(1); } // Set the token for all subsequent requests exports.finnhubClient.defaults.params.token = apiKey; await server.start({ transportType: "stdio", }); } // This check ensures that the main function is only called when running the script directly if (process.env.NODE_ENV !== "test") { main().catch(console.error); }