#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { SERVER_CONFIG } from './config/index.js';
import {
handleGetPrice,
handleGetMarketAnalysis,
handleGetHistoricalAnalysis,
GetPriceArgumentsSchema,
GetMarketAnalysisSchema,
GetHistoricalAnalysisSchema,
} from './tools/index.js';
export const configSchema = z.object({
coincapApiKey: z
.string()
.optional()
.describe("Optional API key for CoinCap to increase rate limits"),
});
export function createServer({
config,
}: {
config: z.infer<typeof configSchema>;
}) {
if (config?.coincapApiKey && !process.env.COINCAP_API_KEY) {
process.env.COINCAP_API_KEY = config.coincapApiKey;
}
const server = new McpServer({
name: SERVER_CONFIG.name,
version: SERVER_CONFIG.version,
});
server.registerTool(
"get-crypto-price",
{
title: "Get Crypto Price",
description: "Get current price and 24h stats for a cryptocurrency",
inputSchema: GetPriceArgumentsSchema.shape,
},
async (args, _extra) => {
const result = await handleGetPrice(args);
return result as any;
}
);
server.registerTool(
"get-market-analysis",
{
title: "Get Market Analysis",
description:
"Get detailed market analysis including top exchanges and volume distribution",
inputSchema: GetMarketAnalysisSchema.shape,
},
async (args, _extra) => {
const result = await handleGetMarketAnalysis(args);
return result as any;
}
);
server.registerTool(
"get-historical-analysis",
{
title: "Get Historical Analysis",
description:
"Get historical price analysis with customizable timeframe",
inputSchema: GetHistoricalAnalysisSchema.shape,
},
async (args, _extra) => {
const result = await handleGetHistoricalAnalysis(args);
return result as any;
}
);
return server.server;
}
export default createServer;
export function createSandboxServer() {
return createServer({
config: { coincapApiKey: undefined },
});
}
async function main() {
const server = createServer({
config: { coincapApiKey: process.env.COINCAP_API_KEY },
});
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Crypto Price MCP Server running on stdio");
}
// Start stdio transport when:
// 1. Explicitly requested via MCP_TRANSPORT=stdio, OR
// 2. Run directly from CLI (not imported as a module)
let thisFilePath: string | undefined;
try {
const importMetaUrl = (import.meta as any)?.url;
if (typeof importMetaUrl === "string") {
thisFilePath = fileURLToPath(importMetaUrl);
}
} catch {
// no-op
}
const isEntrypoint =
typeof thisFilePath === "string" &&
typeof process.argv[1] === "string" &&
path.resolve(process.argv[1]) === path.resolve(thisFilePath);
const isDirectRun = isEntrypoint || process.argv[1]?.includes('mcp-crypto-price');
if (process.env.MCP_TRANSPORT === "stdio" || isDirectRun) {
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});
}