precious-metal-mcp
Version:
An MCP tool to get precious metal prices.
169 lines (168 loc) • 6.54 kB
JavaScript
;
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);
}