One tool is a demo. Five tools is a platform. This part covers designing composable tool APIs, authentication patterns, npm packaging, cross-IDE deployment, and distributing your MCP server to the world.
When your server grows beyond 2-3 tools, design decisions matter. Poor naming, inconsistent parameter styles, and unclear boundaries between tools lead to AI confusion and unreliable behavior.
| Good Name | Bad Name | Why |
|---|---|---|
get_stock_quote | stockQuote | Missing verb; AI doesn't know if it reads or writes |
search_articles | articles | Too vague; is this list, search, or create? |
get_earnings_calendar | getEarningsCalendarForNextWeekByDate | Too long; parameters handle specifics |
create_alert | alert | No verb = unclear action |
list_watchlist_items | get_watchlist | list_ for collections, get_ for single items |
limit=10, format="json", sort="desc".z.enum(["1d","1w","1m","1y"]) > z.string().symbol, all tools use symbol (not ticker in some).Research shows AI models perform best with 7-15 tools. Fewer than 7 limits capability. More than 20 leads to tool confusion — the model starts picking wrong tools or hallucinating tool names. If you need more, consider splitting into multiple focused servers.
The real power of multi-tool servers is composability — tools whose outputs become inputs for other tools. The AI orchestrates the chain automatically.
// Layer 1: Data fetching (primitive tools) server.tool("get_quote", "Get stock price", ...); server.tool("get_financials", "Get financial statements", ...); server.tool("get_news", "Get recent news", ...); // Layer 2: Analysis (uses Layer 1 data internally) server.tool( "analyze_stock", "Complete stock analysis combining price, fundamentals, and news", { symbol: z.string() }, async ({ symbol }) => { // Internally calls the same APIs as Layer 1 tools const [quote, financials, news] = await Promise.all([ fetchQuote(symbol), fetchFinancials(symbol), fetchNews(symbol) ]); return { content: [{ type: "text", text: JSON.stringify({ quote, financials, news, // Pre-computed insights the AI can use directly signals: { pe_vs_sector: quote.pe / financials.sector_avg_pe, revenue_growth: financials.revenue_yoy_pct, sentiment: news.filter(n => n.sentiment > 0).length / news.length } }, null, 2) }] }; } ); // Layer 3: Workflow (orchestrates multiple analyses) server.tool( "compare_stocks", "Compare 2-5 stocks side by side on key metrics", { symbols: z.array(z.string()).min(2).max(5) .describe("List of stock tickers to compare") }, async ({ symbols }) => { const analyses = await Promise.all( symbols.map(s => analyzeStock(s)) ); return { content: [{ type: "text", text: JSON.stringify({ comparison: analyses }, null, 2) }] }; } );
The AI uses these layers naturally. For a simple question ("What's AAPL's price?"), it calls get_quote. For a complex one ("Compare AAPL, MSFT, and GOOGL"), it calls compare_stocks. The key: each layer's description clearly signals its scope.
MCP servers need authentication at two levels: authenticating to external APIs (the server's credentials) and authenticating users who connect to the server (for multi-user deployments).
// Validate required env vars at startup — fail fast const REQUIRED_KEYS = ["ALPHA_VANTAGE_KEY", "FINNHUB_KEY"]; const missing = REQUIRED_KEYS.filter(k => !process.env[k]); if (missing.length > 0) { console.error(`Missing required env vars: ${missing.join(", ")}`); console.error("Set them in .mcp.json under \"env\" or export them."); process.exit(1); }
For HTTP-transport servers that multiple users connect to, MCP supports OAuth 2.1 authorization. The flow:
Client requests /.well-known/oauth-authorization-server
User redirected to auth page, grants permission
Auth code exchanged for access token via PKCE
Token sent in headers for all subsequent MCP calls
For stdio servers (local), environment variables are sufficient. The server runs on the user's machine with their credentials. For HTTP servers (shared/cloud), OAuth 2.1 is the proper mechanism. Most MCP servers today use stdio + env vars. OAuth is mainly for enterprise and SaaS deployments.
The standard way to distribute MCP servers is via npm. Users install with npx and the host spawns the process automatically. Zero manual setup.
{
"name": "@yourorg/finance-mcp-server",
"version": "1.0.0",
"description": "MCP server for financial data — stocks, crypto, news",
"type": "module",
"bin": {
"finance-mcp": "./index.js"
},
"keywords": ["mcp", "mcp-server", "finance", "stocks", "claude"],
"files": ["index.js", "lib/", "tools/", "resources/"],
"engines": { "node": ">=18" },
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.0",
"zod": "^3.23.0"
}
}
# Publish to npm npm publish --access public # Users run with npx (no install needed) npx @yourorg/finance-mcp-server # Or in .mcp.json { "mcpServers": { "finance": { "command": "npx", "args": ["@yourorg/finance-mcp-server"], "env": { "ALPHA_VANTAGE_KEY": "..." } } } }
The whole point of MCP is "build once, use anywhere." Let's set up your server for every major AI tool simultaneously.
{
"mcpServers": {
"finance": {
"command": "npx",
"args": ["@yourorg/finance-mcp-server"],
"env": { "ALPHA_VANTAGE_KEY": "${ALPHA_VANTAGE_KEY}" }
}
}
}
{
"mcpServers": {
"finance": {
"command": "npx",
"args": ["@yourorg/finance-mcp-server"],
"env": { "ALPHA_VANTAGE_KEY": "${ALPHA_VANTAGE_KEY}" }
}
}
}
{
"servers": {
"finance": {
"type": "stdio",
"command": "npx",
"args": ["@yourorg/finance-mcp-server"],
"env": { "ALPHA_VANTAGE_KEY": "${ALPHA_VANTAGE_KEY}" }
}
}
}
#!/bin/bash # Auto-setup MCP config for all supported IDEs CONFIG='{ "mcpServers": { "finance": { "command": "npx", "args": ["@yourorg/finance-mcp-server"], "env": { "ALPHA_VANTAGE_KEY": "'$ALPHA_VANTAGE_KEY'" } } } }' # Claude Code echo "$CONFIG" > .mcp.json # Cursor mkdir -p .cursor && echo "$CONFIG" > .cursor/mcp.json # Windsurf mkdir -p .windsurf && echo "$CONFIG" > .windsurf/mcp.json # VS Code mkdir -p .vscode && echo "$CONFIG" > .vscode/mcp.json echo "MCP configured for Claude Code, Cursor, Windsurf, and VS Code!"
| IDE / Tool | Config Path | Format | Auto-discovery |
|---|---|---|---|
| Claude Code | .mcp.json | Standard MCP | Yes (project root) |
| Claude Desktop | ~/Library/.../claude_desktop_config.json | Standard MCP | Yes (global) |
| Cursor | .cursor/mcp.json | Standard MCP | Yes (project root) |
| Windsurf | .windsurf/mcp.json | Standard MCP | Yes (project root) |
| VS Code | .vscode/mcp.json | Extended | Yes (project root) |
| Gemini CLI | .gemini/settings.json | Extended | Yes (project root) |
#!/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 { TTLCache } from "./lib/cache.js"; import { fetchAPI, safeTool } from "./lib/utils.js"; const cache = new TTLCache(); const server = new McpServer({ name: "finance-platform", version: "2.0.0", description: "Financial data platform — stocks, crypto, news, earnings, screener" }); // ─── Tool 1: Stock Quote ──────────────────────────────────── server.tool("get_stock_quote", "Get real-time stock price and key metrics", { symbol: z.string().describe("Ticker (AAPL, MSFT)") }, safeTool(async ({ symbol }) => { /* ... */ }) ); // ─── Tool 2: Crypto Price ─────────────────────────────────── server.tool("get_crypto_price", "Get cryptocurrency price and market data", { coin: z.string().describe("Coin ID (bitcoin, ethereum)") }, safeTool(async ({ coin }) => { /* ... */ }) ); // ─── Tool 3: Search News ──────────────────────────────────── server.tool("search_news", "Search financial news by keyword or ticker", { query: z.string(), limit: z.number().optional().default(5) }, safeTool(async ({ query, limit }) => { /* ... */ }) ); // ─── Tool 4: Earnings Calendar ────────────────────────────── server.tool("get_earnings_calendar", "Get upcoming earnings dates", { days: z.number().optional().default(7).describe("Lookahead days") }, safeTool(async ({ days }) => { /* ... */ }) ); // ─── Tool 5: Market Regime ────────────────────────────────── server.tool("get_market_regime", "Detect current market regime (risk-on/off/neutral)", {}, safeTool(async () => { /* VIX + breadth + momentum → regime */ }) ); // ─── Tool 6: Screen Stocks ────────────────────────────────── server.tool("screen_stocks", "Screen stocks by technical and fundamental criteria", { strategy: z.enum(["oversold_bounce", "momentum", "breakout", "value"]), min_market_cap: z.number().optional().default(1e9) }, safeTool(async ({ strategy, min_market_cap }) => { /* ... */ }) ); // ─── Tool 7: Compare Stocks ───────────────────────────────── server.tool("compare_stocks", "Compare 2-5 stocks side by side", { symbols: z.array(z.string()).min(2).max(5) }, safeTool(async ({ symbols }) => { /* ... */ }) ); // ─── Resource: Watchlist ──────────────────────────────────── server.resource("watchlist", "finance://watchlist", { description: "User's stock watchlist" }, async () => ({ contents: [{ uri:"finance://watchlist", mimeType:"application/json", text: JSON.stringify(["AAPL","MSFT","GOOGL","NVDA","AMZN","BTC","ETH"]) }] }) ); // ─── Start ────────────────────────────────────────────────── await server.connect(new StdioServerTransport());
verb_noun pattern (like get_stock_quote) immediately tells the model whether the tool reads or writes data, and what entity it operates on. Vague names like stocks force the model to rely solely on descriptions, increasing misuse.stocks-mcp and crypto-mcp)."bin" entry in package.json. Users run npx @yourorg/server-name — no install needed. In their .mcp.json, they set "command": "npx" with the package name in "args". Environment variables for API keys go in "env"..mcp.json at project root with "mcpServers". VS Code uses .vscode/mcp.json with "servers" and adds a "type": "stdio" field. The server definition (command, args, env) is identical across all hosts.verb_noun[_qualifier]. Consistent naming prevents AI confusion."bin" entry. Users run with npx — zero install.MCP from A to Z — Supercharging AI with Live Data
Complete series in 5 parts — From protocol theory to production deployment
Back to Home