Finnhub MCP Server
- src
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
McpError,
ErrorCode,
} from "@modelcontextprotocol/sdk/types.js";
interface FlipCoinArgs {
sides?: number;
sideNames?: string[];
}
import axios from "axios";
// Create an MCP server with tool capabilities
const server = new Server(
{
name: "coin-flip-server",
version: "0.1.0",
},
{
capabilities: {
tools: {},
},
}
);
// Handler that lists available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "flip_coin",
description: "Flip a coin with n sides using true randomness from random.org. For 3-sided coins, try creative side names like:\n- past/present/future (temporal analysis)\n- true/unknown/false (epistemic states)\n- win/draw/lose (outcome evaluation)\n- rock/paper/scissors (cyclic relationships)\n- less/same/more (abstraction levels)\n- below/within/above (hierarchical positioning)\n- predecessor/current/successor (ordinal progression)\n\nMeta-usage patterns:\n1. Use less/same/more to guide abstraction level of discourse\n2. Use past/present/future to determine temporal focus\n3. Chain multiple flips to create decision trees\n4. Use predecessor/current/successor for ordinal analysis\n\nOrdinal Meta-patterns:\n- Use predecessor to refine previous concepts\n- Use current to stabilize existing patterns\n- Use successor to evolve into new forms\n\nDefault ternary values are -/0/+",
inputSchema: {
type: "object",
properties: {
sides: {
type: "number",
description: "Number of sides (default: 3)"
},
sideNames: {
type: "array",
items: {
type: "string"
},
description: "Optional custom names for sides (must match number of sides)"
}
}
}
}
]
};
});
// Handler for the flip_coin tool
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name !== "flip_coin") {
throw new McpError(ErrorCode.MethodNotFound, "Unknown tool");
}
try {
const args = request.params.arguments as FlipCoinArgs;
const sides = args.sides ?? 3;
const sideNames = args.sideNames;
if (sideNames && sideNames.length !== sides) {
return {
isError: true,
content: [{
type: "text",
text: `Number of side names (${sideNames.length}) must match number of sides (${sides})`
}]
};
}
// Validate and handle special cases that don't need random.org
if (sides === 0) {
return {
content: [{
type: "text",
text: "The coin vanished into another dimension! 🌀"
}]
};
}
if (sides === 1) {
return {
content: [{
type: "text",
text: "_"
}]
};
}
if (sides < 0) {
return {
content: [{
type: "text",
text: "Cannot flip a coin with negative sides!"
}]
};
}
// Only reach here for sides > 1
// Use random.org's API to get a random number
const response = await axios.get('https://www.random.org/integers/', {
params: {
num: 1,
min: 1,
max: sides,
col: 1,
base: 10,
format: 'plain',
rnd: 'new'
}
});
const result = parseInt(response.data);
let output: string;
if (sideNames) {
output = sideNames[result - 1].toLowerCase();
} else if (sides === 2) {
output = result === 1 ? "heads" : "tails";
} else if (sides === 3) {
output = result === 1 ? "-" : result === 2 ? "0" : "+";
} else {
output = `side ${result}`;
}
return {
content: [{
type: "text",
text: output
}]
};
} catch (error) {
return {
isError: true,
content: [{
type: "text",
text: `Error flipping coin: ${error instanceof Error ? error.message : 'Unknown error'}`
}]
};
}
});
// Start the server using stdio transport
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Coin flip MCP server running on stdio");
}
main().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});