UNPKG

precious-metal-mcp

Version:

An MCP tool to get precious metal prices.

169 lines (168 loc) 6.54 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 --- 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); } // --- Finnhub API Client --- // We export it to allow mocking in tests exports.finnhubClient = axios_1.default.create({ baseURL: "https://finnhub.io/api/v1", params: { token: apiKey, }, }); // --- Constants --- exports.TROY_OUNCE_TO_GRAMS = 31.1034768; // --- FastMCP Server Setup --- const server = new fastmcp_1.FastMCP({ name: "PreciousMetalPrice", version: "1.0.0", }); const getPreciousMetalPriceParams = zod_1.z.object({ metal: zod_1.z .enum(["gold", "silver"]) .describe("The metal to query (gold or silver)"), type: zod_1.z .enum(["spot", "futures"]) .describe("The price type (spot or futures)"), market: zod_1.z .enum(["new_york", "london", "shanghai"]) .describe("The trading market"), }); 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 "new_york": symbol = args.metal === "gold" ? "OANDA:XAU_USD" : "OANDA:XAG_USD"; currency = "USD"; break; 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 "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 = { metal: args.metal, type: args.type, market: args.market, original_price: { value: originalPrice, unit: `${currency}/oz (Assumed)`, }, converted_price: { value: parseFloat(priceInCnyPerGram.toFixed(2)), unit: "CNY/gram", }, exchange_rate: { from: currency, to: "CNY", rate: 1, }, timestamp: new Date().toISOString(), }; 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 = { metal: args.metal, type: args.type, market: args.market, original_price: { value: originalPrice, unit: `${currency}/oz`, }, converted_price: { value: parseFloat(priceInCnyPerGram.toFixed(2)), unit: "CNY/gram", }, exchange_rate: { from: currency, to: "CNY", rate: exchangeRate, }, timestamp: new Date().toISOString(), }; 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({ name: "getPreciousMetalPrice", description: "Fetches the daily price of gold or silver from a specified market and converts it to CNY per gram.", parameters: getPreciousMetalPriceParams, 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 }); } }, }); // This part will only run when the file is executed directly, not during tests if (process.env.NODE_ENV !== "test") { async function main() { await server.start({ transportType: "stdio", }); } main().catch(console.error); }