#!/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 axios from "axios";
interface PriceParams {
fromSym: string;
timestampSEC?: string;
service?: string;
resolution?: string;
toFiat?: string;
timezone?: string;
}
class PriceServiceMCP {
private server: Server;
constructor() {
this.server = new Server(
{
name: "price-service-mcp",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
this.setupToolHandlers();
// Error handling
this.server.onerror = (error) => console.error("[MCP Error]", error);
process.on("SIGINT", async () => {
await this.server.close();
process.exit(0);
});
}
private setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "get_price",
description: "Get cryptocurrency price data from the price service",
inputSchema: {
type: "object",
properties: {
fromSym: {
type: "string",
description: "The cryptocurrency symbol to get price for (e.g., BTC, ETH)",
},
timestampSEC: {
type: "string",
description: "Unix timestamp in seconds for historical price (optional)",
},
service: {
type: "string",
description: "Price service to use (default: coinbase)",
enum: ["coinbase", "binance", "kraken"],
default: "coinbase",
},
resolution: {
type: "string",
description: "Time resolution for price data (default: 1d)",
enum: ["1m", "5m", "15m", "1h", "4h", "1d"],
default: "1d",
},
toFiat: {
type: "string",
description: "Target fiat currency (default: USD)",
default: "USD",
},
timezone: {
type: "string",
description: "Timezone for timestamp (default: UTC)",
default: "UTC",
},
},
required: ["fromSym"],
},
},
{
name: "health_check",
description: "Check the health status of the price service",
inputSchema: {
type: "object",
properties: {},
},
},
],
};
});
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
if (name === "get_price") {
return await this.getPriceData(args as unknown as PriceParams);
} else if (name === "health_check") {
return await this.healthCheck();
} else {
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: "text",
text: `Error: ${errorMessage}`,
},
],
};
}
});
}
private async getPriceData(params: PriceParams) {
const {
fromSym,
timestampSEC,
service = "coinbase",
resolution = "1d",
toFiat = "USD",
timezone = "UTC",
} = params;
if (!fromSym) {
throw new Error("fromSym parameter is required");
}
const url = "https://price-svc-utyjy373hq-uc.a.run.app/price";
const queryParams = {
fromSym,
service,
resolution,
toFiat,
timezone,
...(timestampSEC && { timestampSEC }),
};
const response = await axios.get(url, { params: queryParams });
return {
content: [
{
type: "text",
text: JSON.stringify(response.data, null, 2),
},
],
};
}
private async healthCheck() {
try {
const url = "https://price-svc-utyjy373hq-uc.a.run.app/health";
const response = await axios.get(url);
return {
content: [
{
type: "text",
text: JSON.stringify({
status: "healthy",
priceService: response.data,
timestamp: new Date().toISOString(),
}, null, 2),
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: JSON.stringify({
status: "unhealthy",
error: error instanceof Error ? error.message : String(error),
timestamp: new Date().toISOString(),
}, null, 2),
},
],
};
}
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error("Price Service MCP server running on stdio");
}
}
const server = new PriceServiceMCP();
server.run().catch(console.error);