├── .github ├── CODEOWNERS ├── workflows │ └── ci.yml └── RELEASE.md ├── .npmrc ├── src ├── server │ └── index.ts ├── mcp-server-factory │ └── index.ts ├── client-storage │ ├── index.ts │ ├── ClientStorage.ts │ └── ClientStorage.test.ts ├── dynamic-toolset-manager │ └── index.ts ├── api │ ├── cot │ │ ├── index.ts │ │ ├── COTClient.ts │ │ └── types.ts │ ├── dcf │ │ ├── index.ts │ │ ├── types.ts │ │ └── DCFClient.ts │ ├── esg │ │ ├── index.ts │ │ ├── types.ts │ │ └── ESGClient.ts │ ├── bulk │ │ └── index.ts │ ├── chart │ │ ├── index.ts │ │ ├── types.ts │ │ └── ChartClient.ts │ ├── forex │ │ ├── index.ts │ │ └── types.ts │ ├── fund │ │ ├── index.ts │ │ └── types.ts │ ├── news │ │ ├── index.ts │ │ ├── types.ts │ │ └── NewsClient.ts │ ├── analyst │ │ ├── index.ts │ │ ├── types.ts │ │ └── AnalystClient.ts │ ├── calendar │ │ ├── index.ts │ │ ├── types.ts │ │ └── CalendarClient.ts │ ├── company │ │ ├── index.ts │ │ └── types.ts │ ├── crypto │ │ ├── index.ts │ │ └── types.ts │ ├── form-13f │ │ ├── index.ts │ │ └── types.ts │ ├── indexes │ │ ├── index.ts │ │ └── types.ts │ ├── quotes │ │ ├── index.ts │ │ └── types.ts │ ├── search │ │ ├── index.ts │ │ ├── types.ts │ │ └── SearchClient.ts │ ├── commodity │ │ ├── index.ts │ │ ├── types.ts │ │ ├── CommodityClient.ts │ │ └── CommodityClient.test.ts │ ├── directory │ │ ├── index.ts │ │ ├── types.ts │ │ └── DirectoryClient.ts │ ├── economics │ │ ├── index.ts │ │ ├── types.ts │ │ └── EconomicsClient.ts │ ├── sec-filings │ │ ├── index.ts │ │ └── types.ts │ ├── statements │ │ └── index.ts │ ├── fundraisers │ │ ├── index.ts │ │ ├── types.ts │ │ └── FundraisersClient.ts │ ├── market-hours │ │ ├── index.ts │ │ ├── types.ts │ │ └── MarketHoursClient.ts │ ├── insider-trades │ │ ├── index.ts │ │ ├── types.ts │ │ └── InsiderTradesClient.ts │ ├── government-trading │ │ ├── index.ts │ │ ├── types.ts │ │ └── GovernmentTradingClient.ts │ ├── market-performance │ │ ├── index.ts │ │ └── types.ts │ ├── earnings-transcript │ │ ├── index.ts │ │ ├── types.ts │ │ └── EarningsTranscriptClient.ts │ ├── technical-indicators │ │ ├── index.ts │ │ ├── types.ts │ │ └── TechnicalIndicatorsClient.ts │ └── FMPClient.ts ├── server-mode-enforcer │ ├── index.ts │ └── ServerModeEnforcer.ts ├── constants │ └── index.ts ├── utils │ ├── getServerVersion.ts │ ├── resolveAccessToken.ts │ ├── computeClientId.ts │ ├── index.ts │ ├── isValidJsonRpc.ts │ ├── loadModuleWithTimeout.ts │ └── showHelp.ts ├── prompts │ ├── index.ts │ └── registerPrompts.test.ts ├── schemas │ └── session │ │ ├── SessionConfigSchema.ts │ │ └── SessionConfigSchema.test.ts ├── tools │ ├── commodity.ts │ ├── esg.ts │ ├── market-hours.ts │ ├── cot.ts │ ├── economics.ts │ ├── earnings-transcript.ts │ └── index.ts ├── index.ts └── types │ └── index.ts ├── smithery.yaml ├── glama.json ├── vitest.config.ts ├── tsconfig.json ├── .gitignore ├── Dockerfile ├── server.json ├── scripts ├── verify-npm-ready.ts └── version-sync.ts └── package.json /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @imbenrabi 2 | * @NitayRabi -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /src/server/index.ts: -------------------------------------------------------------------------------- 1 | export * from './FmpMcpServer.js'; 2 | -------------------------------------------------------------------------------- /src/mcp-server-factory/index.ts: -------------------------------------------------------------------------------- 1 | export * from './McpServerFactory.js'; -------------------------------------------------------------------------------- /src/client-storage/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ClientStorage.js'; 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/dynamic-toolset-manager/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./DynamicToolsetManager.js"; -------------------------------------------------------------------------------- /src/api/cot/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./COTClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/dcf/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./DCFClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/esg/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./ESGClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/bulk/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./BulkClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/chart/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./ChartClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/forex/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./ForexClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/fund/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./FundClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/news/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./NewsClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/analyst/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./AnalystClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/calendar/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./CalendarClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/company/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./CompanyClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/crypto/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./CryptoClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/form-13f/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Form13FClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/indexes/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./IndexesClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/quotes/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./QuotesClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/search/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./SearchClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/commodity/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./CommodityClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/directory/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./DirectoryClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/economics/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./EconomicsClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/sec-filings/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./SECFilingsClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/statements/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./StatementsClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/server-mode-enforcer/index.ts: -------------------------------------------------------------------------------- 1 | export { ServerModeEnforcer } from './ServerModeEnforcer.js'; 2 | -------------------------------------------------------------------------------- /src/api/fundraisers/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./FundraisersClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/market-hours/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./MarketHoursClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/insider-trades/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./InsiderTradesClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/government-trading/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./GovernmentTradingClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/market-performance/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./MarketPerformanceClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/earnings-transcript/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./EarningsTranscriptClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /src/api/technical-indicators/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./TechnicalIndicatorsClient.js"; 2 | export * from "./types.js"; 3 | -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- 1 | version: 1 2 | runtime: "container" 3 | startCommand: 4 | type: http 5 | build: 6 | dockerfile: "Dockerfile" 7 | dockerBuildPath: "." 8 | -------------------------------------------------------------------------------- /glama.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": "https://glama.ai/mcp/schemas/server.json", 3 | "maintainers": [ 4 | "imbenrabi", 5 | "NitayRabi" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_API_KEY = "PLACEHOLDER_TOKEN_FOR_TOOL_LISTING"; 2 | export const DEFAULT_PORT = 8080; 3 | 4 | export * from "./toolSets.js"; 5 | -------------------------------------------------------------------------------- /src/api/commodity/types.ts: -------------------------------------------------------------------------------- 1 | export interface Commodity { 2 | symbol: string; 3 | name: string; 4 | exchange: string; 5 | tradeMonth: string; 6 | currency: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/getServerVersion.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import path from "node:path"; 3 | import { fileURLToPath } from "node:url"; 4 | 5 | export function getServerVersion(): string { 6 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 7 | const packageJsonPath = path.resolve(__dirname, "../../package.json"); 8 | const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); 9 | return packageJson.version; 10 | } 11 | -------------------------------------------------------------------------------- /src/api/market-hours/types.ts: -------------------------------------------------------------------------------- 1 | // Global Exchange Market Hours API 2 | export interface ExchangeMarketHours { 3 | exchange: string; 4 | name: string; 5 | openingHour: string; 6 | closingHour: string; 7 | timezone: string; 8 | isMarketOpen: boolean; 9 | } 10 | 11 | export interface HolidayByExchange { 12 | exchange: string; 13 | date: string; 14 | name: string; 15 | isClosed: boolean; 16 | adjOpenTime: string | null; 17 | adjCloseTime: string | null; 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/resolveAccessToken.ts: -------------------------------------------------------------------------------- 1 | import type { SessionConfig } from "../mcp-server-factory/McpServerFactory.js"; 2 | 3 | /** 4 | * Resolves access token with precedence: server-level token overrides session config. 5 | * Returns undefined if neither is present. 6 | */ 7 | export function resolveAccessToken( 8 | serverToken?: string, 9 | sessionConfig?: SessionConfig 10 | ): string | undefined { 11 | return serverToken || sessionConfig?.FMP_ACCESS_TOKEN; 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | environment: 'node', 7 | coverage: { 8 | provider: 'v8', 9 | reporter: ['text', 'json', 'html'], 10 | exclude: [ 11 | 'node_modules/', 12 | 'dist/', 13 | '**/*.d.ts', 14 | '**/*.test.ts', 15 | '**/*.spec.ts', 16 | 'coverage/', 17 | 'vitest.config.ts' 18 | ], 19 | }, 20 | }, 21 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "skipLibCheck": true, 10 | "outDir": "dist", 11 | "declaration": true, 12 | "sourceMap": true, 13 | "resolveJsonModule": true 14 | }, 15 | "include": ["src/**/*"], 16 | "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.test.js"] 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/computeClientId.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'node:crypto'; 2 | 3 | /** 4 | * Computes a stable client identifier from an access token. 5 | * - If a token is provided, returns `client:`. 6 | * - If no token is provided, returns a per-request unique `anon:`. 7 | */ 8 | export function computeClientId(accessToken?: string): string { 9 | if (typeof accessToken === 'string' && accessToken.length > 0) { 10 | const hash = crypto.createHash('sha256').update(accessToken).digest('hex'); 11 | return `client:${hash}`; 12 | } 13 | return `anon:${crypto.randomUUID()}`; 14 | } 15 | 16 | 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | npm-debug.log* 4 | 5 | # Build output 6 | dist/ 7 | build/ 8 | *.tsbuildinfo 9 | 10 | # Environment variables 11 | .env 12 | .env.local 13 | .env.development.local 14 | .env.test.local 15 | .env.production.local 16 | 17 | # Editor directories 18 | .idea/ 19 | .vscode/ 20 | .kiro/ 21 | *.swp 22 | *.swo 23 | 24 | # OS files 25 | .DS_Store 26 | Thumbs.db 27 | 28 | # Coverage 29 | coverage/ 30 | 31 | # Logs 32 | logs/ 33 | *.log 34 | 35 | # Local test scripts 36 | manual-server-tests 37 | 38 | # MCP registry token files (do not commit) 39 | .mcpregistry_github_token 40 | .mcpregistry_registry_token 41 | -------------------------------------------------------------------------------- /src/prompts/index.ts: -------------------------------------------------------------------------------- 1 | import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 2 | import type { ServerMode, ToolSet } from "../types/index.js"; 3 | import { registerListMcpAssets, type PromptContext } from "./list-mcp-assets.js"; 4 | 5 | export function registerPrompts( 6 | server: McpServer, 7 | context: { mode: ServerMode; version: string; listChanged: boolean; staticToolSets?: ToolSet[] } 8 | ): void { 9 | const ctx: PromptContext = { 10 | mode: context.mode, 11 | version: context.version, 12 | listChanged: context.listChanged, 13 | staticToolSets: context.staticToolSets, 14 | }; 15 | 16 | registerListMcpAssets(server, ctx); 17 | } 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/api/government-trading/types.ts: -------------------------------------------------------------------------------- 1 | export interface FinancialDisclosure { 2 | symbol: string; 3 | disclosureDate: string; 4 | transactionDate: string; 5 | firstName: string; 6 | lastName: string; 7 | office: string; 8 | district: string; 9 | owner: string; 10 | assetDescription: string; 11 | assetType: string; 12 | type: string; 13 | amount: string; 14 | capitalGainsOver200USD?: string; 15 | comment: string; 16 | link: string; 17 | } 18 | 19 | export interface PaginationParams { 20 | page?: number; 21 | limit?: number; 22 | } 23 | 24 | export interface SymbolParams { 25 | symbol: string; 26 | } 27 | 28 | export interface NameParams { 29 | name: string; 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { computeClientId } from './computeClientId.js'; 2 | export { getServerVersion } from './getServerVersion.js'; 3 | export { isValidJsonRpc } from './isValidJsonRpc.js'; 4 | export { loadModuleWithTimeout } from './loadModuleWithTimeout.js'; 5 | export { resolveAccessToken } from './resolveAccessToken.js'; 6 | export { showHelp } from './showHelp.js'; 7 | export { parseCommaSeparatedToolSets, validateToolSets } from './validation.js'; 8 | export { 9 | isValidSemVer, 10 | getVersionInfo, 11 | validateVersionConsistency, 12 | validateServerJsonSchema, 13 | synchronizeVersion, 14 | getCurrentVersion, 15 | type VersionInfo, 16 | type ValidationResult, 17 | type SyncOptions 18 | } from './versionSync.js'; -------------------------------------------------------------------------------- /src/api/commodity/CommodityClient.ts: -------------------------------------------------------------------------------- 1 | import { FMPClient } from "../FMPClient.js"; 2 | import type { FMPContext } from "../../types/index.js"; 3 | import type { 4 | Commodity 5 | } from "./types.js"; 6 | 7 | export class CommodityClient extends FMPClient { 8 | constructor(apiKey?: string) { 9 | super(apiKey); 10 | } 11 | 12 | /** 13 | * Get list of commodities 14 | * @param options Optional parameters including abort signal and context 15 | * @returns Array of commodities 16 | */ 17 | async listCommodities( 18 | options?: { 19 | signal?: AbortSignal; 20 | context?: FMPContext; 21 | } 22 | ): Promise { 23 | return super.get("/commodity-list", {}, options); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # ---- Build stage ---- 2 | FROM node:lts-alpine AS builder 3 | WORKDIR /app 4 | 5 | COPY package.json package-lock.json ./ 6 | RUN npm ci 7 | 8 | # Copy source and build 9 | COPY . . 10 | RUN npm run build 11 | 12 | # ---- Runtime stage ---- 13 | FROM node:lts-alpine AS runner 14 | WORKDIR /app 15 | 16 | # Copy manifests and install prod deps as root 17 | COPY package.json package-lock.json ./ 18 | RUN npm ci --omit=dev 19 | 20 | # Copy built app; make sure files are owned by the non-root user 21 | COPY --from=builder /app/dist ./dist 22 | # If base image has the 'node' user (it does in node:lts-alpine): 23 | RUN chown -R node:node /app 24 | USER node 25 | 26 | ENV NODE_ENV=production 27 | ENV PORT=8080 28 | 29 | EXPOSE 8080 30 | CMD ["node", "dist/index.js"] 31 | -------------------------------------------------------------------------------- /src/api/esg/types.ts: -------------------------------------------------------------------------------- 1 | export interface ESGDisclosure { 2 | date: string; 3 | acceptedDate: string; 4 | symbol: string; 5 | cik: string; 6 | companyName: string; 7 | formType: string; 8 | environmentalScore: number; 9 | socialScore: number; 10 | governanceScore: number; 11 | ESGScore: number; 12 | url: string; 13 | } 14 | 15 | export interface ESGRating { 16 | symbol: string; 17 | cik: string; 18 | companyName: string; 19 | industry: string; 20 | fiscalYear: number; 21 | ESGRiskRating: string; 22 | industryRank: string; 23 | } 24 | 25 | export interface ESGBenchmark { 26 | fiscalYear: number; 27 | sector: string; 28 | environmentalScore: number; 29 | socialScore: number; 30 | governanceScore: number; 31 | ESGScore: number; 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | pull_request: 8 | branches: 9 | - '**' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | 18 | - name: Use Node.js 20 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: 20 22 | cache: "npm" 23 | registry-url: https://registry.npmjs.org/ 24 | 25 | - name: Install dependencies 26 | run: npm ci 27 | 28 | - name: Security audit (production dependencies) 29 | run: npm audit --omit=dev --audit-level=high 30 | 31 | - name: Build 32 | run: npm run build 33 | 34 | - name: Run tests 35 | run: npm run test:run -------------------------------------------------------------------------------- /src/api/chart/types.ts: -------------------------------------------------------------------------------- 1 | export interface ChartData { 2 | symbol: string; 3 | date: string; 4 | open: number; 5 | high: number; 6 | low: number; 7 | close: number; 8 | volume: number; 9 | change?: number; 10 | changePercent?: number; 11 | vwap: number; 12 | } 13 | 14 | export interface LightChartData { 15 | symbol: string; 16 | date: string; 17 | close: number; 18 | volume: number; 19 | } 20 | 21 | 22 | 23 | export interface UnadjustedChartData { 24 | symbol: string; 25 | date: string; 26 | adjOpen: number; 27 | adjHigh: number; 28 | adjLow: number; 29 | adjClose: number 30 | volume: number; 31 | } 32 | 33 | export interface IntradayChartData extends Omit {} 34 | 35 | export type Interval = "1min" | "5min" | "15min" | "30min" | "1hour" | "4hour"; 36 | -------------------------------------------------------------------------------- /src/api/news/types.ts: -------------------------------------------------------------------------------- 1 | // FMP Articles API 2 | export interface FMPArticle { 3 | title: string; 4 | date: string; 5 | content: string; 6 | tickers: string; 7 | image: string; 8 | link: string; 9 | author: string; 10 | site: string; 11 | } 12 | 13 | // General News, Stock News, Press Releases, Crypto News, Forex News APIs 14 | export interface NewsArticle { 15 | symbol: string | null; 16 | publishedDate: string; 17 | publisher: string; 18 | title: string; 19 | image: string; 20 | site: string; 21 | text: string; 22 | url: string; 23 | } 24 | 25 | // Common parameters for news API requests 26 | export interface NewsParams { 27 | from?: string; 28 | to?: string; 29 | page?: number; 30 | limit?: number; 31 | } 32 | 33 | // Parameters for searching news by symbols 34 | export interface NewsSearchParams extends NewsParams { 35 | symbols: string; 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/isValidJsonRpc.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Validates if a request follows JSON-RPC 2.0 specification 3 | * @param request The parsed JSON-RPC request 4 | * @returns true if valid, false otherwise 5 | */ 6 | export function isValidJsonRpc(request: any): boolean { 7 | // Must be an object 8 | if (!request || typeof request !== "object") return false; 9 | 10 | // Must have jsonrpc property with value "2.0" 11 | if (request.jsonrpc !== "2.0") return false; 12 | 13 | // Must have method property that is a string 14 | if (typeof request.method !== "string") return false; 15 | 16 | // Must have id (can be string, number, null, but not undefined) 17 | if (request.id === undefined) return false; 18 | 19 | // If params exists, it must be an object or array 20 | if (request.params !== undefined && !(typeof request.params === "object")) 21 | return false; 22 | 23 | return true; 24 | } 25 | -------------------------------------------------------------------------------- /src/api/earnings-transcript/types.ts: -------------------------------------------------------------------------------- 1 | export interface LatestEarningTranscript { 2 | symbol: string; 3 | period: string; 4 | fiscalYear: number; 5 | date: string; 6 | } 7 | 8 | export interface EarningTranscript { 9 | symbol: string; 10 | period: string; 11 | year: number; 12 | date: string; 13 | content: string; 14 | } 15 | 16 | export interface TranscriptDate { 17 | quarter: number; 18 | fiscalYear: number; 19 | date: string; 20 | } 21 | 22 | export interface AvailableTranscriptSymbol { 23 | symbol: string; 24 | companyName: string; 25 | noOfTranscripts: string; 26 | } 27 | 28 | export interface LatestTranscriptsParams { 29 | limit?: number; 30 | page?: number; 31 | } 32 | 33 | export interface TranscriptParams { 34 | symbol: string; 35 | year: string; 36 | quarter: string; 37 | limit?: number; 38 | } 39 | 40 | export interface TranscriptDatesParams { 41 | symbol: string; 42 | } 43 | -------------------------------------------------------------------------------- /src/api/economics/types.ts: -------------------------------------------------------------------------------- 1 | export interface TreasuryRate { 2 | date: string; 3 | month1: number; 4 | month2: number; 5 | month3: number; 6 | month6: number; 7 | year1: number; 8 | year2: number; 9 | year3: number; 10 | year5: number; 11 | year7: number; 12 | year10: number; 13 | year20: number; 14 | year30: number; 15 | } 16 | 17 | export interface EconomicIndicator { 18 | date: string; 19 | name: string; 20 | value: number; 21 | } 22 | 23 | export interface EconomicCalendar { 24 | date: string; 25 | country: string; 26 | event: string; 27 | currency: string; 28 | previous: number; 29 | estimate: number | null; 30 | actual: number; 31 | change: number; 32 | impact: string; 33 | changePercentage: number; 34 | } 35 | 36 | export interface MarketRiskPremium { 37 | country: string; 38 | continent: string; 39 | countryRiskPremium: number; 40 | totalEquityRiskPremium: number; 41 | } 42 | -------------------------------------------------------------------------------- /src/api/market-performance/types.ts: -------------------------------------------------------------------------------- 1 | // Market Sector Performance Snapshot API 2 | export interface SectorPerformance { 3 | date: string; 4 | sector: string; 5 | exchange: string; 6 | averageChange: number; 7 | } 8 | 9 | // Industry Performance Snapshot API 10 | export interface IndustryPerformance { 11 | date: string; 12 | industry: string; 13 | exchange: string; 14 | averageChange: number; 15 | } 16 | 17 | // Sector PE Snapshot API 18 | export interface SectorPE { 19 | date: string; 20 | sector: string; 21 | exchange: string; 22 | pe: number; 23 | } 24 | 25 | // Industry PE Snapshot API 26 | export interface IndustryPE { 27 | date: string; 28 | industry: string; 29 | exchange: string; 30 | pe: number; 31 | } 32 | 33 | // Biggest Stock Gainers/Losers/Most Active APIs 34 | export interface StockMovement { 35 | symbol: string; 36 | price: number; 37 | name: string; 38 | change: number; 39 | changesPercentage: number; 40 | exchange: string; 41 | } 42 | -------------------------------------------------------------------------------- /src/schemas/session/SessionConfigSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | /** 4 | * Zod schema for session configuration used by the stateful server. 5 | * Note: Token optionality and descriptions may be adapted in a subsequent step. 6 | */ 7 | export const SessionConfigSchema = z.object({ 8 | FMP_ACCESS_TOKEN: z 9 | .string() 10 | .optional() 11 | .describe( 12 | "Financial Modeling Prep API access token. Optional for server initialization; required to successfully call FMP-backed tools." 13 | ), 14 | FMP_TOOL_SETS: z 15 | .string() 16 | .optional() 17 | .describe( 18 | "Comma-separated list of tool sets to load (e.g., 'search,company,quotes'). If not specified, all tools will be loaded." 19 | ), 20 | DYNAMIC_TOOL_DISCOVERY: z 21 | .string() 22 | .optional() 23 | .describe( 24 | "Enable dynamic toolset management. Set to 'true' to use meta-tools for runtime toolset loading. Default is 'false'." 25 | ), 26 | }); 27 | 28 | export type SessionConfigInput = z.infer; 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/api/directory/types.ts: -------------------------------------------------------------------------------- 1 | export interface CompanySymbol { 2 | symbol: string; 3 | companyName: string; 4 | } 5 | 6 | export interface FinancialStatementSymbol extends CompanySymbol { 7 | tradingCurrency: string; 8 | reportingCurrency: string; 9 | } 10 | 11 | export interface CIKEntry { 12 | cik: string; 13 | companyName: string; 14 | } 15 | 16 | export interface SymbolChange { 17 | date: string; 18 | companyName: string; 19 | oldSymbol: string; 20 | newSymbol: string; 21 | } 22 | 23 | export interface ETFEntry { 24 | symbol: string; 25 | name: string; 26 | } 27 | 28 | export interface ActivelyTradingEntry { 29 | symbol: string; 30 | name: string; 31 | } 32 | 33 | export interface EarningsTranscriptEntry { 34 | symbol: string; 35 | companyName: string; 36 | noOfTranscripts: string; 37 | } 38 | 39 | export interface ExchangeEntry { 40 | exchange: string; 41 | name: string; 42 | countryName: string; 43 | countryCode: string; 44 | symbolSuffix: string; 45 | delay: string 46 | } 47 | 48 | export interface SectorEntry { 49 | sector: string; 50 | } 51 | 52 | export interface IndustryEntry { 53 | industry: string; 54 | } 55 | 56 | export interface CountryEntry { 57 | country: string; 58 | } 59 | -------------------------------------------------------------------------------- /src/utils/loadModuleWithTimeout.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleLoader, ToolRegistrationFunction } from "../types/index.js"; 2 | 3 | /** 4 | * Helper function to create a timeout promise that rejects after a specified time 5 | * @param timeoutMs - Timeout in milliseconds 6 | * @param moduleName - Name of the module (for error message) 7 | * @returns Promise that rejects with timeout error 8 | */ 9 | function createTimeoutPromise(timeoutMs: number, moduleName: string): Promise { 10 | return new Promise((_, reject) => 11 | setTimeout(() => reject(new Error(`Module loading timeout for ${moduleName}`)), timeoutMs) 12 | ); 13 | } 14 | 15 | /** 16 | * Loads a module with timeout protection 17 | * @param moduleLoader - The module loader function 18 | * @param moduleName - Name of the module being loaded 19 | * @param timeoutMs - Timeout in milliseconds (default: 10000) 20 | * @returns Promise that resolves to the registration function 21 | */ 22 | export async function loadModuleWithTimeout( 23 | moduleLoader: ModuleLoader, 24 | moduleName: string, 25 | timeoutMs: number = 10000 26 | ): Promise { 27 | return await Promise.race([ 28 | moduleLoader(), 29 | createTimeoutPromise(timeoutMs, moduleName) 30 | ]); 31 | } -------------------------------------------------------------------------------- /src/schemas/session/SessionConfigSchema.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { SessionConfigSchema } from './SessionConfigSchema.js'; 3 | import { QuotesClient } from '../../api/quotes/QuotesClient.js'; 4 | import type { FMPContext } from '../../types/index.js'; 5 | 6 | describe('SessionConfigSchema', () => { 7 | it('accepts configuration without FMP_ACCESS_TOKEN', () => { 8 | const parsed = SessionConfigSchema.parse({}); 9 | expect(parsed).toBeDefined(); 10 | expect(parsed.FMP_ACCESS_TOKEN).toBeUndefined(); 11 | }); 12 | 13 | it('accepts configuration with optional fields', () => { 14 | const parsed = SessionConfigSchema.parse({ 15 | FMP_TOOL_SETS: 'search,quotes', 16 | DYNAMIC_TOOL_DISCOVERY: 'true', 17 | }); 18 | expect(parsed.FMP_TOOL_SETS).toBe('search,quotes'); 19 | expect(parsed.DYNAMIC_TOOL_DISCOVERY).toBe('true'); 20 | }); 21 | }); 22 | 23 | describe('FMP token requirement at call time', () => { 24 | it('throws when calling a client operation without a token', async () => { 25 | const client = new QuotesClient(); 26 | const context: FMPContext = { config: {} }; 27 | await expect( 28 | client.getQuote({ symbol: 'AAPL' }, { context }) 29 | ).rejects.toThrow(/FMP_ACCESS_TOKEN is required/); 30 | }); 31 | }); 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/api/forex/types.ts: -------------------------------------------------------------------------------- 1 | export interface ForexPair { 2 | symbol: string; 3 | fromCurrency: string; 4 | toCurrency: string; 5 | fromName: string; 6 | toName: string; 7 | } 8 | 9 | export interface ForexQuote { 10 | symbol: string; 11 | name: string; 12 | price: number; 13 | changePercentage: number; 14 | change: number; 15 | volume: number; 16 | dayLow: number; 17 | dayHigh: number; 18 | yearHigh: number; 19 | yearLow: number; 20 | marketCap: number | null; 21 | priceAvg50: number; 22 | priceAvg200: number; 23 | exchange: string; 24 | open: number; 25 | previousClose: number; 26 | timestamp: number; 27 | } 28 | 29 | export interface ForexShortQuote { 30 | symbol: string; 31 | price: number; 32 | change: number; 33 | volume: number; 34 | } 35 | 36 | export interface ForexLightChart { 37 | symbol: string; 38 | date: string; 39 | price: number; 40 | volume: number; 41 | } 42 | 43 | export interface ForexHistoricalChart { 44 | symbol: string; 45 | date: string; 46 | open: number; 47 | high: number; 48 | low: number; 49 | close: number; 50 | volume: number; 51 | change: number; 52 | changePercent: number; 53 | vwap: number; 54 | } 55 | 56 | export interface ForexIntradayChart { 57 | date: string; 58 | open: number; 59 | high: number; 60 | low: number; 61 | close: number; 62 | volume: number; 63 | } 64 | -------------------------------------------------------------------------------- /src/api/crypto/types.ts: -------------------------------------------------------------------------------- 1 | export interface Cryptocurrency { 2 | symbol: string; 3 | name: string; 4 | exchange: string; 5 | icoDate: string; 6 | circulatingSupply: number; 7 | totalSupply: number | null; 8 | } 9 | 10 | export interface CryptocurrencyQuote { 11 | symbol: string; 12 | name: string; 13 | price: number; 14 | changePercentage: number; 15 | change: number; 16 | volume: number; 17 | dayLow: number; 18 | dayHigh: number; 19 | yearHigh: number; 20 | yearLow: number; 21 | marketCap: number; 22 | priceAvg50: number; 23 | priceAvg200: number; 24 | exchange: string; 25 | open: number; 26 | previousClose: number; 27 | timestamp: number; 28 | } 29 | 30 | export interface CryptocurrencyShortQuote { 31 | symbol: string; 32 | price: number; 33 | change: number; 34 | volume: number; 35 | } 36 | 37 | export interface CryptocurrencyLightChart { 38 | symbol: string; 39 | date: string; 40 | price: number; 41 | volume: number; 42 | } 43 | 44 | export interface CryptocurrencyHistoricalChart { 45 | symbol: string; 46 | date: string; 47 | open: number; 48 | high: number; 49 | low: number; 50 | close: number; 51 | volume: number; 52 | change: number; 53 | changePercent: number; 54 | vwap: number; 55 | } 56 | 57 | export interface CryptocurrencyIntradayPrice { 58 | date: string; 59 | open: number; 60 | high: number; 61 | low: number; 62 | close: number; 63 | volume: number; 64 | } 65 | -------------------------------------------------------------------------------- /src/tools/commodity.ts: -------------------------------------------------------------------------------- 1 | import { CommodityClient } from "../api/commodity/CommodityClient.js"; 2 | import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 3 | 4 | /** 5 | * Register all commodity-related tools with the MCP server 6 | * @param server The MCP server instance 7 | * @param accessToken The Financial Modeling Prep API access token (optional when using lazy loading) 8 | */ 9 | export function registerCommodityTools( 10 | server: McpServer, 11 | accessToken?: string 12 | ): void { 13 | const commodityClient = new CommodityClient(accessToken); 14 | 15 | server.tool( 16 | "listCommodities", 17 | "Access an extensive list of tracked commodities across various sectors, including energy, metals, and agricultural products. The FMP Commodities List API provides essential data on tradable commodities, giving investors the ability to explore market options in real-time.", 18 | {}, 19 | async () => { 20 | try { 21 | const results = await commodityClient.listCommodities(); 22 | return { 23 | content: [{ type: "text", text: JSON.stringify(results, null, 2) }], 24 | }; 25 | } catch (error) { 26 | return { 27 | content: [ 28 | { 29 | type: "text", 30 | text: `Error: ${ 31 | error instanceof Error ? error.message : String(error) 32 | }`, 33 | }, 34 | ], 35 | isError: true, 36 | }; 37 | } 38 | } 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import minimist from "minimist"; 4 | import { getAvailableToolSets, DEFAULT_PORT } from "./constants/index.js"; 5 | import { showHelp } from "./utils/showHelp.js"; 6 | import { FmpMcpServer } from "./server/index.js"; 7 | import { ServerModeEnforcer } from "./server-mode-enforcer/index.js"; 8 | 9 | // Parse command line arguments 10 | const argv = minimist(process.argv.slice(2)); 11 | 12 | // Show help if requested 13 | if (argv.help || argv.h) { 14 | const availableToolSets = getAvailableToolSets(); 15 | showHelp(availableToolSets); 16 | process.exit(0); 17 | } 18 | 19 | function main() { 20 | // Initialize the ServerModeEnforcer with env vars and CLI args 21 | // This will also validate tool sets and exit if invalid ones are found 22 | ServerModeEnforcer.initialize(process.env, argv); 23 | 24 | const PORT = 25 | argv.port || (process.env.PORT ? parseInt(process.env.PORT) : DEFAULT_PORT); 26 | const fmpToken = argv["fmp-token"] || process.env.FMP_ACCESS_TOKEN; 27 | 28 | const mcpServer = new FmpMcpServer({ 29 | accessToken: fmpToken, 30 | cacheOptions: { 31 | maxSize: 25, 32 | ttl: 1000 * 60 * 60 * 2, // 2 hours 33 | }, 34 | }); 35 | 36 | mcpServer.start(PORT); 37 | 38 | const handleShutdown = () => { 39 | console.log("\n🔌 Shutting down server..."); 40 | mcpServer.stop(); 41 | process.exit(0); 42 | }; 43 | 44 | process.on("SIGINT", handleShutdown); // Catches Ctrl+C 45 | process.on("SIGTERM", handleShutdown); // Catches kill signals 46 | } 47 | 48 | main(); 49 | -------------------------------------------------------------------------------- /src/api/quotes/types.ts: -------------------------------------------------------------------------------- 1 | export interface StockQuote { 2 | symbol: string; 3 | name: string; 4 | price: number; 5 | changePercentage: number; 6 | change: number; 7 | volume: number; 8 | dayLow: number; 9 | dayHigh: number; 10 | yearHigh: number; 11 | yearLow: number; 12 | marketCap: number; 13 | priceAvg50: number; 14 | priceAvg200: number; 15 | exchange: string; 16 | open: number; 17 | previousClose: number; 18 | timestamp: number; 19 | } 20 | 21 | export interface StockQuoteShort { 22 | symbol: string; 23 | price: number; 24 | change: number; 25 | volume: number; 26 | } 27 | 28 | export interface AftermarketTrade { 29 | symbol: string; 30 | price: number; 31 | tradeSize: number; 32 | timestamp: number; 33 | } 34 | 35 | export interface AftermarketQuote { 36 | symbol: string; 37 | bidSize: number; 38 | bidPrice: number; 39 | askSize: number; 40 | askPrice: number; 41 | volume: number; 42 | timestamp: number; 43 | } 44 | 45 | export interface StockPriceChange { 46 | symbol: string; 47 | "1D": number; 48 | "5D": number; 49 | "1M": number; 50 | "3M": number; 51 | "6M": number; 52 | ytd: number; 53 | "1Y": number; 54 | "3Y": number; 55 | "5Y": number; 56 | "10Y": number; 57 | max: number; 58 | } 59 | 60 | export interface QuoteParams { 61 | symbol: string; 62 | } 63 | 64 | export interface BatchQuoteParams { 65 | symbols: string; 66 | } 67 | 68 | export interface ExchangeQuoteParams { 69 | exchange: string; 70 | short?: boolean; 71 | } 72 | 73 | export interface ShortParams { 74 | short?: boolean; 75 | } 76 | -------------------------------------------------------------------------------- /src/api/calendar/types.ts: -------------------------------------------------------------------------------- 1 | export interface Dividend { 2 | symbol: string; 3 | date: string; 4 | recordDate: string; 5 | paymentDate: string; 6 | declarationDate: string; 7 | adjDividend: number; 8 | dividend: number; 9 | yield: number; 10 | frequency: string; 11 | } 12 | 13 | export interface EarningsReport { 14 | symbol: string; 15 | date: string; 16 | epsActual: number | null; 17 | epsEstimated: number | null; 18 | revenueActual: number | null; 19 | revenueEstimated: number | null; 20 | lastUpdated: string; 21 | } 22 | 23 | export interface IPO { 24 | symbol: string; 25 | date: string; 26 | daa: string; 27 | company: string; 28 | exchange: string; 29 | actions: string; 30 | shares: number | null; 31 | priceRange: string | null; 32 | marketCap: number | null; 33 | } 34 | 35 | export interface IPODisclosure { 36 | symbol: string; 37 | filingDate: string; 38 | acceptedDate: string; 39 | effectivenessDate: string; 40 | cik: string; 41 | form: string; 42 | url: string; 43 | } 44 | 45 | export interface IPOProspectus { 46 | symbol: string; 47 | acceptedDate: string; 48 | filingDate: string; 49 | ipoDate: string; 50 | cik: string; 51 | pricePublicPerShare: number; 52 | pricePublicTotal: number; 53 | discountsAndCommissionsPerShare: number; 54 | discountsAndCommissionsTotal: number; 55 | proceedsBeforeExpensesPerShare: number; 56 | proceedsBeforeExpensesTotal: number; 57 | form: string; 58 | url: string; 59 | } 60 | 61 | export interface StockSplit { 62 | symbol: string; 63 | date: string; 64 | numerator: number; 65 | denominator: number; 66 | } 67 | -------------------------------------------------------------------------------- /src/api/technical-indicators/types.ts: -------------------------------------------------------------------------------- 1 | // Base interface for all technical indicators 2 | export interface TechnicalIndicatorBase { 3 | date: string; 4 | open: number; 5 | high: number; 6 | low: number; 7 | close: number; 8 | volume: number; 9 | } 10 | 11 | // Simple Moving Average (SMA) 12 | export interface SMAIndicator extends TechnicalIndicatorBase { 13 | sma: number; 14 | } 15 | 16 | // Exponential Moving Average (EMA) 17 | export interface EMAIndicator extends TechnicalIndicatorBase { 18 | ema: number; 19 | } 20 | 21 | // Weighted Moving Average (WMA) 22 | export interface WMAIndicator extends TechnicalIndicatorBase { 23 | wma: number; 24 | } 25 | 26 | // Double Exponential Moving Average (DEMA) 27 | export interface DEMAIndicator extends TechnicalIndicatorBase { 28 | dema: number; 29 | } 30 | 31 | // Triple Exponential Moving Average (TEMA) 32 | export interface TEMAIndicator extends TechnicalIndicatorBase { 33 | tema: number; 34 | } 35 | 36 | // Relative Strength Index (RSI) 37 | export interface RSIIndicator extends TechnicalIndicatorBase { 38 | rsi: number; 39 | } 40 | 41 | // Standard Deviation 42 | export interface StandardDeviationIndicator extends TechnicalIndicatorBase { 43 | standardDeviation: number; 44 | } 45 | 46 | // Williams %R 47 | export interface WilliamsIndicator extends TechnicalIndicatorBase { 48 | williams: number; 49 | } 50 | 51 | // Average Directional Index (ADX) 52 | export interface ADXIndicator extends TechnicalIndicatorBase { 53 | adx: number; 54 | } 55 | 56 | // Common parameters for technical indicator requests 57 | export interface TechnicalIndicatorParams { 58 | symbol: string; 59 | periodLength: number; 60 | timeframe: string; 61 | from?: string; 62 | to?: string; 63 | } 64 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 2 | 3 | /** 4 | * FMP Context type 5 | */ 6 | export type FMPContext = { 7 | config?: { 8 | FMP_ACCESS_TOKEN?: string; 9 | }; 10 | }; 11 | 12 | /** 13 | * Type definition for tool registration functions 14 | * All registration functions follow this pattern: (server, accessToken?) => void 15 | */ 16 | export type ToolRegistrationFunction = (server: McpServer, accessToken?: string) => void; 17 | 18 | 19 | /** 20 | * Type definition for module loader functions 21 | * Each module loader returns a Promise that resolves to a registration function 22 | */ 23 | export type ModuleLoader = () => Promise; 24 | 25 | /** 26 | * Tool sets configuration based on Financial Modeling Prep API categories 27 | * Each set contains related functionality that users might want to access together 28 | */ 29 | 30 | export type ToolSet = 31 | | "search" 32 | | "company" 33 | | "quotes" 34 | | "statements" 35 | | "calendar" 36 | | "charts" 37 | | "news" 38 | | "analyst" 39 | | "market-performance" 40 | | "insider-trades" 41 | | "institutional" 42 | | "indexes" 43 | | "economics" 44 | | "crypto" 45 | | "forex" 46 | | "commodities" 47 | | "etf-funds" 48 | | "esg" 49 | | "technical-indicators" 50 | | "senate" 51 | | "sec-filings" 52 | | "earnings" 53 | | "dcf" 54 | | "bulk"; 55 | 56 | /** 57 | * Tool set definition 58 | */ 59 | export interface ToolSetDefinition { 60 | name: string; 61 | description: string; 62 | decisionCriteria: string; 63 | modules: string[]; 64 | } 65 | 66 | /** 67 | * Server mode enumeration 68 | */ 69 | export type ServerMode = 'DYNAMIC_TOOL_DISCOVERY' | 'STATIC_TOOL_SETS' | 'ALL_TOOLS'; 70 | -------------------------------------------------------------------------------- /src/api/insider-trades/types.ts: -------------------------------------------------------------------------------- 1 | // Latest Insider Trading API 2 | export interface InsiderTrading { 3 | symbol: string; 4 | filingDate: string; 5 | transactionDate: string; 6 | reportingCik: string; 7 | companyCik: string; 8 | transactionType: string; 9 | securitiesOwned: number; 10 | reportingName: string; 11 | typeOfOwner: string; 12 | acquisitionOrDisposition: string; 13 | directOrIndirect: string; 14 | formType: string; 15 | securitiesTransacted: number; 16 | price: number; 17 | securityName: string; 18 | url: string; 19 | } 20 | 21 | // Search Insider Trades by Reporting Name API 22 | export interface InsiderReportingName { 23 | reportingCik: string; 24 | reportingName: string; 25 | } 26 | 27 | // All Insider Transaction Types API 28 | export interface InsiderTransactionType { 29 | transactionType: string; 30 | } 31 | 32 | // Insider Trade Statistics API 33 | export interface InsiderTradeStatistics { 34 | symbol: string; 35 | cik: string; 36 | year: number; 37 | quarter: number; 38 | acquiredTransactions: number; 39 | disposedTransactions: number; 40 | acquiredDisposedRatio: number; 41 | totalAcquired: number; 42 | totalDisposed: number; 43 | averageAcquired: number; 44 | averageDisposed: number; 45 | totalPurchases: number; 46 | totalSales: number; 47 | } 48 | 49 | // Acquisition Ownership API 50 | export interface AcquisitionOwnership { 51 | cik: string; 52 | symbol: string; 53 | filingDate: string; 54 | acceptedDate: string; 55 | cusip: string; 56 | nameOfReportingPerson: string; 57 | citizenshipOrPlaceOfOrganization: string; 58 | soleVotingPower: string; 59 | sharedVotingPower: string; 60 | soleDispositivePower: string; 61 | sharedDispositivePower: string; 62 | amountBeneficiallyOwned: string; 63 | percentOfClass: string; 64 | typeOfReportingPerson: string; 65 | url: string; 66 | } 67 | -------------------------------------------------------------------------------- /src/api/esg/ESGClient.ts: -------------------------------------------------------------------------------- 1 | import { FMPClient } from "../FMPClient.js"; 2 | import type { FMPContext } from "../../types/index.js"; 3 | import type { ESGDisclosure, ESGRating, ESGBenchmark } from "./types.js"; 4 | 5 | export class ESGClient extends FMPClient { 6 | constructor(apiKey?: string) { 7 | super(apiKey); 8 | } 9 | 10 | /** 11 | * Get ESG disclosures for a symbol 12 | * @param symbol The stock symbol 13 | * @param options Optional parameters including abort signal and context 14 | * @returns Array of ESG disclosures 15 | */ 16 | async getDisclosures( 17 | symbol: string, 18 | options?: { 19 | signal?: AbortSignal; 20 | context?: FMPContext; 21 | } 22 | ): Promise { 23 | return super.get( 24 | "/esg-disclosure", 25 | { symbol }, 26 | options 27 | ); 28 | } 29 | 30 | /** 31 | * Get ESG ratings for a symbol 32 | * @param symbol The stock symbol 33 | * @param options Optional parameters including abort signal and context 34 | * @returns Array of ESG ratings 35 | */ 36 | async getRatings( 37 | symbol: string, 38 | options?: { 39 | signal?: AbortSignal; 40 | context?: FMPContext; 41 | } 42 | ): Promise { 43 | return super.get("/esg-ratings", { symbol }, options); 44 | } 45 | 46 | /** 47 | * Get ESG benchmarks 48 | * @param year Optional year to get benchmarks for 49 | * @param options Optional parameters including abort signal and context 50 | * @returns Array of ESG benchmarks 51 | */ 52 | async getBenchmarks( 53 | year?: string, 54 | options?: { 55 | signal?: AbortSignal; 56 | context?: FMPContext; 57 | } 58 | ): Promise { 59 | return super.get( 60 | "/esg-benchmark", 61 | { year }, 62 | options 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/api/market-hours/MarketHoursClient.ts: -------------------------------------------------------------------------------- 1 | import { FMPClient } from "../FMPClient.js"; 2 | import type { FMPContext } from "../../types/index.js"; 3 | import type { ExchangeMarketHours, HolidayByExchange } from "./types.js"; 4 | 5 | export class MarketHoursClient extends FMPClient { 6 | constructor(apiKey?: string) { 7 | super(apiKey); 8 | } 9 | 10 | /** 11 | * Get market hours for a specific exchange 12 | * @param exchange Exchange name/code 13 | * @param options Optional parameters including abort signal and context 14 | */ 15 | async getExchangeMarketHours( 16 | exchange: string, 17 | options?: { 18 | signal?: AbortSignal; 19 | context?: FMPContext; 20 | } 21 | ): Promise { 22 | return super.get( 23 | "/exchange-market-hours", 24 | { exchange }, 25 | options 26 | ); 27 | } 28 | 29 | /** 30 | * Get holidays for a specific exchange 31 | * @param exchange Exchange name/code 32 | * @param from Optional Start date for the holidays 33 | * @param to Optional End date for the holidays 34 | * @param options Optional parameters including abort signal and context 35 | */ 36 | async getHolidaysByExchange( 37 | exchange: string, 38 | from?: string, 39 | to?: string, 40 | options?: { 41 | signal?: AbortSignal; 42 | context?: FMPContext; 43 | } 44 | ): Promise { 45 | return super.get( 46 | "/holidays-by-exchange", 47 | { exchange, from, to }, 48 | options 49 | ); 50 | } 51 | 52 | /** 53 | * Get market hours for all exchanges 54 | * @param options Optional parameters including abort signal and context 55 | */ 56 | async getAllExchangeMarketHours(options?: { 57 | signal?: AbortSignal; 58 | context?: FMPContext; 59 | }): Promise { 60 | return super.get( 61 | "/all-exchange-market-hours", 62 | {}, 63 | options 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/prompts/registerPrompts.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi } from 'vitest'; 2 | import { registerPrompts } from './index.js'; 3 | 4 | describe('registerPrompts', () => { 5 | function createMockServer(hasPrompt = true) { 6 | const calls: any = {}; 7 | const server: any = {}; 8 | if (hasPrompt) { 9 | server.prompt = vi.fn((_name: string, _title: string, _schema: any, handler: any) => { 10 | calls.prompt = { handler }; 11 | }); 12 | } 13 | // ensure no accidental tool alias usage 14 | server.tool = vi.fn(); 15 | return { server, calls }; 16 | } 17 | 18 | const baseCtx = { 19 | mode: 'ALL_TOOLS' as const, 20 | version: '1.0.0', 21 | listChanged: false, 22 | }; 23 | 24 | it('registers native prompt when supported', async () => { 25 | const { server, calls } = createMockServer(true); 26 | registerPrompts(server, baseCtx); 27 | 28 | expect(server.prompt).toHaveBeenCalled(); 29 | expect(server.tool).not.toHaveBeenCalled(); 30 | 31 | // Execute handlers to ensure they return structured content 32 | const promptResult = await calls.prompt.handler(); 33 | expect(promptResult.messages[0].content[0].text).toContain('# Server Capabilities'); 34 | }); 35 | 36 | it('does nothing when prompt API is not available (no alias)', async () => { 37 | const { server } = createMockServer(false); 38 | registerPrompts(server, { ...baseCtx, mode: 'STATIC_TOOL_SETS', staticToolSets: ['search'] as any }); 39 | expect(server.prompt).toBeUndefined(); 40 | expect(server.tool).not.toHaveBeenCalled(); 41 | }); 42 | 43 | it('renders dynamic mode content with meta-tools note', async () => { 44 | const { server, calls } = createMockServer(true); 45 | registerPrompts(server, { ...baseCtx, mode: 'DYNAMIC_TOOL_DISCOVERY', listChanged: true }); 46 | const promptResult = await calls.prompt.handler(); 47 | expect(promptResult.messages[0].content[0].text).toContain('enable_toolset'); 48 | expect(promptResult.messages[0].content[0].text).toContain('get_toolset_status'); 49 | }); 50 | }); 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/api/cot/COTClient.ts: -------------------------------------------------------------------------------- 1 | import { FMPClient } from "../FMPClient.js"; 2 | import type { FMPContext } from "../../types/index.js"; 3 | import { COTReport, COTAnalysis, COTList } from "./types.js"; 4 | 5 | export class COTClient extends FMPClient { 6 | constructor(apiKey?: string) { 7 | super(apiKey); 8 | } 9 | 10 | /** 11 | * Get COT(Commitment Of Traders) reports for a symbol 12 | * @param symbol Optional the commodity symbol 13 | * @param from Optional start date 14 | * @param to Optional end date 15 | * @param options Optional parameters including abort signal and context 16 | * @returns Array of COT reports 17 | */ 18 | async getReports( 19 | symbol: string, 20 | from?: string, 21 | to?: string, 22 | options?: { 23 | signal?: AbortSignal; 24 | context?: FMPContext; 25 | } 26 | ): Promise { 27 | return super.get("/commitment-of-traders-report", { symbol, from, to }, options); 28 | } 29 | 30 | /** 31 | * Get COT(Commitment Of Traders) analysis for a symbol 32 | * @param symbol The commodity symbol 33 | * @param from Optional start date 34 | * @param to Optional end date 35 | * @param options Optional parameters including abort signal and context 36 | * @returns Array of COT analysis 37 | */ 38 | async getAnalysis( 39 | symbol: string, 40 | from?: string, 41 | to?: string, 42 | options?: { 43 | signal?: AbortSignal; 44 | context?: FMPContext; 45 | } 46 | ): Promise { 47 | return super.get( 48 | "/commitment-of-traders-analysis", 49 | { symbol, from, to }, 50 | options 51 | ); 52 | } 53 | 54 | /** 55 | * Get list of available COT(Commitment Of Traders) reports 56 | * @param options Optional parameters including abort signal and context 57 | * @returns Array of available COT reports 58 | */ 59 | async getList(options?: { 60 | signal?: AbortSignal; 61 | context?: FMPContext; 62 | }): Promise { 63 | return super.get("/commitment-of-traders-list", {}, options); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/api/indexes/types.ts: -------------------------------------------------------------------------------- 1 | // Stock Market Indexes List API 2 | export interface IndexItem { 3 | symbol: string; 4 | name: string; 5 | exchange: string; 6 | currency: string; 7 | } 8 | 9 | // Index Quote API 10 | export interface IndexQuote { 11 | symbol: string; 12 | name: string; 13 | price: number; 14 | changePercentage: number; 15 | change: number; 16 | volume: number; 17 | dayLow: number; 18 | dayHigh: number; 19 | yearHigh: number; 20 | yearLow: number; 21 | marketCap: number | null; 22 | priceAvg50: number; 23 | priceAvg200: number; 24 | exchange: string; 25 | open: number; 26 | previousClose: number; 27 | timestamp: number; 28 | } 29 | 30 | // Index Short Quote API 31 | export interface IndexShortQuote { 32 | symbol: string; 33 | price: number; 34 | change: number; 35 | volume: number; 36 | } 37 | 38 | // Historical Index Light Chart API 39 | export interface IndexLightChart { 40 | symbol: string; 41 | date: string; 42 | price: number; 43 | volume: number; 44 | } 45 | 46 | // Historical Index Full Chart API 47 | export interface IndexFullChart { 48 | symbol: string; 49 | date: string; 50 | open: number; 51 | high: number; 52 | low: number; 53 | close: number; 54 | volume: number; 55 | change: number; 56 | changePercent: number; 57 | vwap: number; 58 | } 59 | 60 | // Intraday Index Chart APIs (1min, 5min, 1hour) 61 | export interface IndexIntradayData { 62 | date: string; 63 | open: number; 64 | low: number; 65 | high: number; 66 | close: number; 67 | volume: number; 68 | } 69 | 70 | // Index Constituent APIs (S&P 500, Nasdaq, Dow Jones) 71 | export interface IndexConstituent { 72 | symbol: string; 73 | name: string; 74 | sector: string; 75 | subSector: string; 76 | headQuarter: string; 77 | dateFirstAdded: string | null; 78 | cik: string; 79 | founded: string; 80 | } 81 | 82 | // Historical Index Changes APIs 83 | export interface HistoricalIndexChange { 84 | dateAdded: string; 85 | addedSecurity: string; 86 | removedTicker: string; 87 | removedSecurity: string; 88 | date: string; 89 | symbol: string; 90 | reason: string; 91 | } 92 | -------------------------------------------------------------------------------- /src/api/search/types.ts: -------------------------------------------------------------------------------- 1 | export interface SymbolSearchResult { 2 | symbol: string; 3 | name: string; 4 | currency: string; 5 | exchangeFullName: string; 6 | exchange: string; 7 | } 8 | 9 | export interface NameSearchResult { 10 | symbol: string; 11 | name: string; 12 | currency: string; 13 | exchangeFullName: string; 14 | exchange: string; 15 | } 16 | 17 | export interface CIKSearchResult { 18 | symbol: string; 19 | companyName: string; 20 | cik: string; 21 | exchangeFullName: string; 22 | exchange: string; 23 | currency: string; 24 | } 25 | 26 | export interface CUSIPSearchResult { 27 | symbol: string; 28 | companyName: string; 29 | cusip: string; 30 | marketCap: number; 31 | } 32 | 33 | export interface ISINSearchResult { 34 | symbol: string; 35 | name: string; 36 | isin: string; 37 | marketCap: number; 38 | } 39 | 40 | export interface StockScreenerResult { 41 | symbol: string; 42 | companyName: string; 43 | marketCap: number; 44 | sector: string; 45 | industry: string; 46 | beta: number; 47 | price: number; 48 | lastAnnualDividend: number; 49 | volume: number; 50 | exchange: string; 51 | exchangeShortName: string; 52 | country: string; 53 | isEtf: boolean; 54 | isFund: boolean; 55 | isActivelyTrading: boolean; 56 | } 57 | 58 | export interface ExchangeVariantResult { 59 | symbol: string; 60 | price: number; 61 | beta: number; 62 | volAvg: number; 63 | mktCap: number; 64 | lastDiv: number; 65 | range: string; 66 | changes: number; 67 | companyName: string; 68 | currency: string; 69 | cik: string; 70 | isin: string; 71 | cusip: string; 72 | exchange: string; 73 | exchangeShortName: string; 74 | industry: string; 75 | website: string; 76 | description: string; 77 | ceo: string; 78 | sector: string; 79 | country: string; 80 | fullTimeEmployees: string; 81 | phone: string; 82 | address: string; 83 | city: string; 84 | state: string; 85 | zip: string; 86 | dcfDiff: number; 87 | dcf: number; 88 | image: string; 89 | ipoDate: string; 90 | defaultImage: boolean; 91 | isEtf: boolean; 92 | isActivelyTrading: boolean; 93 | isAdr: boolean; 94 | isFund: boolean; 95 | } 96 | -------------------------------------------------------------------------------- /src/api/dcf/types.ts: -------------------------------------------------------------------------------- 1 | export interface DCFValuation { 2 | symbol: string; 3 | date: string; 4 | ["Stock Price"]: number; 5 | dcf: number; 6 | } 7 | 8 | export interface CustomDCFInput { 9 | symbol: string; 10 | revenueGrowthPct?: number; 11 | ebitdaPct?: number; 12 | depreciationAndAmortizationPct?: number; 13 | cashAndShortTermInvestmentsPct?: number; 14 | receivablesPct?: number; 15 | inventoriesPct?: number; 16 | payablePct?: number; 17 | ebitPct?: number; 18 | capitalExpenditurePct?: number; 19 | operatingCashFlowPct?: number; 20 | sellingGeneralAndAdministrativeExpensesPct?: number; 21 | taxRate?: number; 22 | longTermGrowthRate?: number; 23 | costOfDebt?: number; 24 | costOfEquity?: number; 25 | marketRiskPremium?: number; 26 | beta?: number; 27 | riskFreeRate?: number; 28 | } 29 | 30 | export interface CustomDCFOutput { 31 | symbol: string; 32 | revenue?: number; 33 | revenuePercentage?: number; 34 | ebitda?: number; 35 | ebitdaPercentage?: number; 36 | ebit?: number; 37 | ebitPercentage?: number; 38 | depreciation?: number; 39 | depreciationPercentage?: number; 40 | totalCash?: number; 41 | totalCashPercentage?: number; 42 | receivables?: number; 43 | receivablesPercentage?: number; 44 | inventories?: number; 45 | inventoriesPercentage?: number; 46 | payable?: number; 47 | payablePercentage?: number; 48 | capitalExpenditure?: number; 49 | capitalExpenditurePercentage?: number; 50 | price?: number; 51 | beta?: number; 52 | dilutedSharesOutstanding?: number; 53 | costofDebt?: number; 54 | taxRate?: number; 55 | afterTaxCostOfDebt?: number; 56 | riskFreeRate?: number; 57 | marketRiskPremium?: number; 58 | costOfEquity?: number; 59 | totalDebt?: number; 60 | totalEquity?: number; 61 | totalCapital?: number; 62 | debtWeighting?: number; 63 | equityWeighting?: number; 64 | wacc?: number; 65 | taxRateCash?: number; 66 | ebiat?: number; 67 | ufcf?: number; 68 | sumPvUfcf?: number; 69 | longTermGrowthRate?: number; 70 | terminalValue?: number; 71 | presentTerminalValue?: number; 72 | enterpriseValue?: number; 73 | netDebt?: number; 74 | equityValue?: number; 75 | equityValuePerShare?: number; 76 | freeCashFlowT1?: number; 77 | } 78 | -------------------------------------------------------------------------------- /src/api/dcf/DCFClient.ts: -------------------------------------------------------------------------------- 1 | import { FMPClient } from "../FMPClient.js"; 2 | import type { FMPContext } from "../../types/index.js"; 3 | import type { 4 | DCFValuation, 5 | CustomDCFInput, 6 | CustomDCFOutput, 7 | } from "./types.js"; 8 | 9 | 10 | 11 | export class DCFClient extends FMPClient { 12 | constructor(apiKey?: string) { 13 | super(apiKey); 14 | } 15 | 16 | /** 17 | * Get DCF(Discounted Cash Flow) valuation for a symbol 18 | * @param symbol The stock symbol 19 | * @param options Optional parameters including abort signal and context 20 | * @returns DCF valuation data 21 | */ 22 | async getValuation( 23 | symbol: string, 24 | options?: { 25 | signal?: AbortSignal; 26 | context?: FMPContext; 27 | } 28 | ): Promise { 29 | return super.get("/discounted-cash-flow", { symbol }, options); 30 | } 31 | 32 | /** 33 | * Get levered DCF(Discounted Cash Flow) valuation for a symbol 34 | * @param symbol The stock symbol 35 | * @param options Optional parameters including abort signal and context 36 | * @returns Levered DCF valuation data 37 | */ 38 | async getLeveredValuation( 39 | symbol: string, 40 | options?: { 41 | signal?: AbortSignal; 42 | context?: FMPContext; 43 | } 44 | ): Promise { 45 | return super.get("/levered-discounted-cash-flow", { symbol }, options); 46 | } 47 | 48 | /** 49 | * Calculate custom levered DCF valuation 50 | * @param input Custom DCF input parameters 51 | * @param options Optional parameters including abort signal and context 52 | * @returns Custom DCF output data 53 | */ 54 | async calculateCustomLeveredDCF( 55 | input: CustomDCFInput, 56 | options?: { 57 | signal?: AbortSignal; 58 | context?: FMPContext; 59 | } 60 | ): Promise { 61 | return super.post("/custom-levered-discounted-cash-flow", { ...input }, options); 62 | } 63 | 64 | /** 65 | * Calculate custom DCF valuation 66 | * @param input Custom DCF input parameters 67 | * @param options Optional parameters including abort signal and context 68 | * @returns Custom DCF output data 69 | */ 70 | async calculateCustomDCF( 71 | input: CustomDCFInput, 72 | options?: { 73 | signal?: AbortSignal; 74 | context?: FMPContext; 75 | } 76 | ): Promise { 77 | return super.post("/custom-discounted-cash-flow", { ...input }, options); 78 | } 79 | } 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/api/fund/types.ts: -------------------------------------------------------------------------------- 1 | export interface FundHolding { 2 | symbol: string; 3 | asset: string; 4 | name: string; 5 | isin: string; 6 | securityCusip: string; 7 | sharesNumber: number; 8 | weightPercentage: number; 9 | marketValue: number; 10 | updatedAt: string; 11 | updated: string; 12 | } 13 | 14 | export interface FundSector { 15 | industry: string; 16 | exposure: number; 17 | } 18 | 19 | export interface FundInfo { 20 | symbol: string; 21 | name: string; 22 | description: string; 23 | isin: string; 24 | assetClass: string; 25 | securityCusip: string; 26 | domicile: string; 27 | website: string; 28 | etfCompany: string; 29 | expenseRatio: number; 30 | assetsUnderManagement: number; 31 | avgVolume: number; 32 | inceptionDate: string; 33 | nav: number; 34 | navCurrency: string; 35 | holdingsCount: number; 36 | updatedAt: string; 37 | sectorsList: FundSector[]; 38 | } 39 | 40 | export interface FundCountryAllocation { 41 | country: string; 42 | weightPercentage: string; 43 | } 44 | 45 | export interface FundAssetExposure { 46 | symbol: string; 47 | asset: string; 48 | sharesNumber: number; 49 | weightPercentage: number; 50 | marketValue: number; 51 | } 52 | 53 | export interface FundSectorWeighting { 54 | symbol: string; 55 | sector: string; 56 | weightPercentage: number; 57 | } 58 | 59 | export interface FundDisclosureHolder { 60 | cik: string; 61 | holder: string; 62 | shares: number; 63 | dateReported: string; 64 | change: number; 65 | weightPercent: number; 66 | } 67 | 68 | export interface FundDisclosureSearch { 69 | symbol: string; 70 | cik: string; 71 | classId: string; 72 | seriesId: string; 73 | entityName: string; 74 | entityOrgType: string; 75 | seriesName: string; 76 | className: string; 77 | reportingFileNumber: string; 78 | address: string; 79 | city: string; 80 | zipCode: string; 81 | state: string; 82 | } 83 | 84 | export interface FundDisclosureDate { 85 | date: string; 86 | year: number; 87 | quarter: number; 88 | } 89 | 90 | export interface FundDisclosure { 91 | cik: string; 92 | date: string; 93 | acceptedDate: string; 94 | symbol: string; 95 | name: string; 96 | lei: string; 97 | title: string; 98 | cusip: string; 99 | isin: string; 100 | balance: number; 101 | units: string; 102 | cur_cd: string; 103 | valUsd: number; 104 | pctVal: number; 105 | payoffProfile: string; 106 | assetCat: string; 107 | issuerCat: string; 108 | invCountry: string; 109 | isRestrictedSec: string; 110 | fairValLevel: string; 111 | isCashCollateral: string; 112 | isNonCashCollateral: string; 113 | isLoanByFund: string; 114 | } 115 | -------------------------------------------------------------------------------- /server.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json", 3 | "name": "io.github.imbenrabi/financial-modeling-prep-mcp-server", 4 | "title": "Financial Modeling Prep MCP Server", 5 | "description": "MCP server for Financial Modeling Prep API with 250+ financial data tools", 6 | "version": "2.5.3", 7 | "websiteUrl": "https://github.com/imbenrabi/Financial-Modeling-Prep-MCP-Server", 8 | "repository": { 9 | "url": "https://github.com/imbenrabi/Financial-Modeling-Prep-MCP-Server", 10 | "source": "github" 11 | }, 12 | "packages": [ 13 | { 14 | "registryType": "npm", 15 | "registryBaseUrl": "https://registry.npmjs.org", 16 | "identifier": "financial-modeling-prep-mcp-server", 17 | "version": "2.5.3", 18 | "runtimeHint": "npx", 19 | "transport": { 20 | "type": "streamable-http", 21 | "url": "https://financial-modeling-prep-mcp-server-production.up.railway.app/mcp" 22 | }, 23 | "packageArguments": [ 24 | { 25 | "type": "named", 26 | "name": "--fmp-token", 27 | "description": "Financial Modeling Prep API access token", 28 | "isRequired": false, 29 | "format": "string" 30 | }, 31 | { 32 | "type": "named", 33 | "name": "--port", 34 | "description": "Port number for HTTP server mode", 35 | "isRequired": false, 36 | "format": "number" 37 | }, 38 | { 39 | "type": "named", 40 | "name": "--dynamic-tool-discovery", 41 | "description": "Enable dynamic tool discovery mode", 42 | "isRequired": false, 43 | "format": "boolean" 44 | }, 45 | { 46 | "type": "named", 47 | "name": "--fmp-tool-sets", 48 | "description": "Comma-separated list of tool sets to load", 49 | "isRequired": false, 50 | "format": "string" 51 | } 52 | ], 53 | "environmentVariables": [ 54 | { 55 | "name": "FMP_ACCESS_TOKEN", 56 | "description": "Financial Modeling Prep API access token", 57 | "isRequired": false, 58 | "format": "string", 59 | "isSecret": true 60 | }, 61 | { 62 | "name": "PORT", 63 | "description": "Port number for HTTP server mode", 64 | "isRequired": false, 65 | "format": "number" 66 | }, 67 | { 68 | "name": "DYNAMIC_TOOL_DISCOVERY", 69 | "description": "Enable dynamic tool discovery mode", 70 | "isRequired": false, 71 | "format": "boolean" 72 | }, 73 | { 74 | "name": "FMP_TOOL_SETS", 75 | "description": "Comma-separated list of tool sets to load", 76 | "isRequired": false, 77 | "format": "string" 78 | } 79 | ] 80 | } 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /src/utils/showHelp.ts: -------------------------------------------------------------------------------- 1 | import type { ToolSet, ToolSetDefinition } from "../types/index.js"; 2 | import { DEFAULT_PORT } from "../constants/index.js"; 3 | 4 | /** 5 | * Display help information for the Financial Modeling Prep MCP Server 6 | * 7 | * Shows comprehensive usage information including: 8 | * - Command line options and their descriptions 9 | * - Available tool sets with names and descriptions 10 | * - Usage examples for different scenarios 11 | * - Environment variable configuration options 12 | * 13 | * @param availableToolSets - Array of available tool sets with their definitions 14 | * 15 | * @example 16 | * ```typescript 17 | * import { getAvailableToolSets } from "../constants/index.js"; 18 | * import { showHelp } from "./showHelp.js"; 19 | * 20 | * const toolSets = getAvailableToolSets(); 21 | * showHelp(toolSets); 22 | * ``` 23 | */ 24 | export function showHelp( 25 | availableToolSets: Array<{ key: ToolSet; definition: ToolSetDefinition }> 26 | ): void { 27 | console.log(` 28 | Financial Modeling Prep MCP Server 29 | 30 | Usage: npm start [options] 31 | 32 | Options: 33 | --port Server port (default: ${DEFAULT_PORT}) 34 | --fmp-token FMP API access token 35 | --dynamic-tool-discovery Enable dynamic toolset management mode 36 | --fmp-tool-sets Comma-separated list of toolsets to load in static mode 37 | --help, -h Show this help message 38 | 39 | Server Modes: 40 | Dynamic Mode Starts with 3 meta-tools, load toolsets on-demand 41 | Static Mode Pre-loads specific toolsets (use --fmp-tool-sets) 42 | Legacy Mode Loads all 253 tools (default) 43 | 44 | Available Tool Sets: 45 | `); 46 | 47 | availableToolSets.forEach(({ key, definition }) => { 48 | console.log(` ${key.padEnd(20)} ${definition.name}`); 49 | console.log(` ${" ".repeat(20)} ${definition.description}`); 50 | console.log(); 51 | }); 52 | 53 | console.log(` 54 | Examples: 55 | npm start # Legacy mode (all tools) 56 | npm start -- --port 4000 # Custom port 57 | npm start -- --dynamic-tool-discovery # Dynamic mode 58 | npm start -- --fmp-tool-sets search,company # Static mode with specific tools 59 | npm start -- --fmp-token YOUR_TOKEN # With API token 60 | 61 | Environment Variables: 62 | PORT Server port (default: ${DEFAULT_PORT}) 63 | FMP_ACCESS_TOKEN Financial Modeling Prep API access token 64 | DYNAMIC_TOOL_DISCOVERY Enable dynamic toolset management mode (true/false) 65 | FMP_TOOL_SETS Comma-separated list of toolsets for static mode 66 | 67 | Configuration Precedence: 68 | 1. CLI Arguments (highest priority) 69 | 2. Environment Variables 70 | 3. Session Configuration (lowest priority) 71 | 72 | Note: Server-level configurations override ALL session-level configurations. 73 | `); 74 | } 75 | -------------------------------------------------------------------------------- /src/api/economics/EconomicsClient.ts: -------------------------------------------------------------------------------- 1 | import { FMPClient } from "../FMPClient.js"; 2 | import type { FMPContext } from "../../types/index.js"; 3 | import type { 4 | TreasuryRate, 5 | EconomicIndicator, 6 | EconomicCalendar, 7 | MarketRiskPremium, 8 | } from "./types.js"; 9 | 10 | 11 | 12 | export class EconomicsClient extends FMPClient { 13 | constructor(apiKey?: string) { 14 | super(apiKey); 15 | } 16 | 17 | /** 18 | * Get treasury rates 19 | * @param from Optional start date (YYYY-MM-DD) 20 | * @param to Optional end date (YYYY-MM-DD) 21 | * @param options Optional parameters including abort signal and context 22 | * @returns Array of treasury rates 23 | */ 24 | async getTreasuryRates( 25 | from?: string, 26 | to?: string, 27 | options?: { 28 | signal?: AbortSignal; 29 | context?: FMPContext; 30 | } 31 | ): Promise { 32 | return super.get("/treasury-rates", { from, to }, options); 33 | } 34 | 35 | /** 36 | * Get economic indicators 37 | * @param name Name of the indicator 38 | * @param from Optional start date (YYYY-MM-DD) 39 | * @param to Optional end date (YYYY-MM-DD) 40 | * @param options Optional parameters including abort signal and context 41 | * @returns Array of economic indicators 42 | */ 43 | async getEconomicIndicators( 44 | name: string, 45 | from?: string, 46 | to?: string, 47 | options?: { 48 | signal?: AbortSignal; 49 | context?: FMPContext; 50 | } 51 | ): Promise { 52 | return super.get( 53 | "/economic-indicator", 54 | { 55 | name, 56 | from, 57 | to, 58 | }, 59 | options 60 | ); 61 | } 62 | 63 | /** 64 | * Get economic calendar 65 | * @param from Optional start date (YYYY-MM-DD) 66 | * @param to Optional end date (YYYY-MM-DD) 67 | * @param options Optional parameters including abort signal and context 68 | * @returns Array of economic calendar events 69 | */ 70 | async getEconomicCalendar( 71 | from?: string, 72 | to?: string, 73 | options?: { 74 | signal?: AbortSignal; 75 | context?: FMPContext; 76 | } 77 | ): Promise { 78 | return super.get( 79 | "/economic-calendar", 80 | { from, to }, 81 | options 82 | ); 83 | } 84 | 85 | /** 86 | * Get market risk premium 87 | * @param options Optional parameters including abort signal and context 88 | * @returns Array of market risk premiums 89 | */ 90 | async getMarketRiskPremium( 91 | options?: { 92 | signal?: AbortSignal; 93 | context?: FMPContext; 94 | } 95 | ): Promise { 96 | return super.get( 97 | "/market-risk-premium", 98 | {}, 99 | options 100 | ); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/api/earnings-transcript/EarningsTranscriptClient.ts: -------------------------------------------------------------------------------- 1 | import { FMPClient } from "../FMPClient.js"; 2 | import type { FMPContext } from "../../types/index.js"; 3 | import type { 4 | LatestEarningTranscript, 5 | EarningTranscript, 6 | TranscriptDate, 7 | AvailableTranscriptSymbol, 8 | LatestTranscriptsParams, 9 | TranscriptParams, 10 | TranscriptDatesParams, 11 | } from "./types.js"; 12 | 13 | export class EarningsTranscriptClient extends FMPClient { 14 | constructor(apiKey?: string) { 15 | super(apiKey); 16 | } 17 | 18 | /** 19 | * Get latest earning transcripts with pagination 20 | * @param params Parameters for latest transcripts request 21 | * @param options Optional parameters including abort signal and context 22 | */ 23 | async getLatestTranscripts( 24 | params: LatestTranscriptsParams = {}, 25 | options?: { 26 | signal?: AbortSignal; 27 | context?: FMPContext; 28 | } 29 | ): Promise { 30 | return this.get( 31 | `/earning-call-transcript-latest`, 32 | { 33 | limit: params.limit, 34 | page: params.page, 35 | }, 36 | options 37 | ); 38 | } 39 | 40 | /** 41 | * Get earning transcript for a specific company, year, and quarter 42 | * @param params Parameters for transcript request 43 | * @param options Optional parameters including abort signal and context 44 | */ 45 | async getTranscript( 46 | params: TranscriptParams, 47 | options?: { 48 | signal?: AbortSignal; 49 | context?: FMPContext; 50 | } 51 | ): Promise { 52 | return this.get( 53 | `/earning-call-transcript`, 54 | { 55 | symbol: params.symbol, 56 | year: params.year, 57 | quarter: params.quarter, 58 | limit: params.limit, 59 | }, 60 | options 61 | ); 62 | } 63 | 64 | /** 65 | * Get transcript dates for a specific company 66 | * @param params Parameters for transcript dates request 67 | * @param options Optional parameters including abort signal and context 68 | */ 69 | async getTranscriptDates( 70 | params: TranscriptDatesParams, 71 | options?: { 72 | signal?: AbortSignal; 73 | context?: FMPContext; 74 | } 75 | ): Promise { 76 | return this.get( 77 | `/earning-call-transcript-dates`, 78 | { 79 | symbol: params.symbol, 80 | }, 81 | options 82 | ); 83 | } 84 | 85 | /** 86 | * Get list of available transcript symbols 87 | * @param options Optional parameters including abort signal and context 88 | */ 89 | async getAvailableTranscriptSymbols(options?: { 90 | signal?: AbortSignal; 91 | context?: FMPContext; 92 | }): Promise { 93 | return this.get( 94 | `/earnings-transcript-list`, 95 | {}, 96 | options 97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/api/analyst/types.ts: -------------------------------------------------------------------------------- 1 | export interface AnalystEstimate { 2 | symbol: string; 3 | date: string; 4 | revenueLow: number; 5 | revenueHigh: number; 6 | revenueAvg: number; 7 | ebitdaLow: number; 8 | ebitdaHigh: number; 9 | ebitdaAvg: number; 10 | ebitLow: number; 11 | ebitHigh: number; 12 | ebitAvg: number; 13 | netIncomeLow: number; 14 | netIncomeHigh: number; 15 | netIncomeAvg: number; 16 | sgaExpenseLow: number; 17 | sgaExpenseHigh: number; 18 | sgaExpenseAvg: number; 19 | epsAvg: number; 20 | epsHigh: number; 21 | epsLow: number; 22 | numAnalystsRevenue: number; 23 | numAnalystsEps: number; 24 | } 25 | 26 | export interface RatingsSnapshot { 27 | symbol: string; 28 | rating: string; 29 | overallScore: number; 30 | discountedCashFlowScore: number; 31 | returnOnEquityScore: number; 32 | returnOnAssetsScore: number; 33 | debtToEquityScore: number; 34 | priceToEarningsScore: number; 35 | priceToBookScore: number; 36 | } 37 | 38 | export interface HistoricalRating extends RatingsSnapshot { 39 | date: string; 40 | } 41 | 42 | export interface PriceTargetSummary { 43 | symbol: string; 44 | lastMonthCount: number; 45 | lastMonthAvgPriceTarget: number; 46 | lastQuarterCount: number; 47 | lastQuarterAvgPriceTarget: number; 48 | lastYearCount: number; 49 | lastYearAvgPriceTarget: number; 50 | allTimeCount: number; 51 | allTimeAvgPriceTarget: number; 52 | publishers: string; 53 | } 54 | 55 | export interface PriceTargetConsensus { 56 | symbol: string; 57 | targetHigh: number; 58 | targetLow: number; 59 | targetConsensus: number; 60 | targetMedian: number; 61 | } 62 | 63 | export interface PriceTargetNews { 64 | symbol: string; 65 | publishedDate: string; 66 | newsURL: string; 67 | newsTitle: string; 68 | analystName: string; 69 | priceTarget: number; 70 | adjPriceTarget: number; 71 | priceWhenPosted: number; 72 | newsPublisher: string; 73 | newsBaseURL: string; 74 | analystCompany: string; 75 | } 76 | 77 | export interface StockGrade { 78 | symbol: string; 79 | date: string; 80 | gradingCompany: string; 81 | previousGrade: string; 82 | newGrade: string; 83 | action: string; 84 | } 85 | 86 | export interface HistoricalStockGrade { 87 | symbol: string; 88 | date: string; 89 | analystRatingsBuy: number; 90 | analystRatingsHold: number; 91 | analystRatingsSell: number; 92 | analystRatingsStrongSell: number; 93 | } 94 | 95 | export interface StockGradeSummary { 96 | symbol: string; 97 | strongBuy: number; 98 | buy: number; 99 | hold: number; 100 | sell: number; 101 | strongSell: number; 102 | consensus: string; 103 | } 104 | 105 | export interface StockGradeNews { 106 | symbol: string; 107 | publishedDate: string; 108 | newsURL: string; 109 | newsTitle: string; 110 | newsBaseURL: string; 111 | newsPublisher: string; 112 | newGrade: string; 113 | previousGrade: string; 114 | gradingCompany: string; 115 | action: string; 116 | priceWhenPosted: number; 117 | } 118 | -------------------------------------------------------------------------------- /src/api/sec-filings/types.ts: -------------------------------------------------------------------------------- 1 | export interface SECFiling { 2 | symbol: string; 3 | cik: string; 4 | filingDate: string; 5 | acceptedDate: string; 6 | formType: string; 7 | hasFinancials?: boolean; 8 | link: string; 9 | finalLink: string; 10 | } 11 | 12 | export interface SECFilingFormType extends Omit {} 13 | 14 | export interface CompanySearchResult { 15 | symbol: string; 16 | name: string; 17 | cik: string; 18 | sicCode: string; 19 | industryTitle: string; 20 | businessAddress: string; 21 | phoneNumber: string; 22 | } 23 | 24 | export interface CompanyProfile { 25 | symbol: string; 26 | cik: string; 27 | registrantName: string; 28 | sicCode: string; 29 | sicDescription: string; 30 | sicGroup: string; 31 | isin: string; 32 | businessAddress: string; 33 | mailingAddress: string; 34 | phoneNumber: string; 35 | postalCode: string; 36 | city: string; 37 | state: string; 38 | country: string; 39 | description: string; 40 | ceo: string; 41 | website: string; 42 | exchange: string; 43 | stateLocation: string; 44 | stateOfIncorporation: string; 45 | fiscalYearEnd: string; 46 | ipoDate: string; 47 | employees: string; 48 | secFilingsUrl: string; 49 | taxIdentificationNumber: string; 50 | fiftyTwoWeekRange: string; 51 | isActive: boolean; 52 | assetType: string; 53 | openFigiComposite: string; 54 | priceCurrency: string; 55 | marketSector: string; 56 | securityType: string | null; 57 | isEtf: boolean; 58 | isAdr: boolean; 59 | isFund: boolean; 60 | } 61 | 62 | export interface IndustryClassification { 63 | office: string; 64 | sicCode: string; 65 | industryTitle: string; 66 | } 67 | 68 | export interface DateRangeParams { 69 | from: string; 70 | to: string; 71 | page?: number; 72 | limit?: number; 73 | } 74 | 75 | export interface Form8KParams extends DateRangeParams { 76 | // Extends DateRangeParams with no additional fields 77 | } 78 | 79 | export interface FinancialsParams extends DateRangeParams { 80 | // Extends DateRangeParams with no additional fields 81 | } 82 | 83 | export interface FormTypeParams extends DateRangeParams { 84 | formType: string; 85 | } 86 | 87 | export interface SymbolParams extends DateRangeParams { 88 | symbol: string; 89 | } 90 | 91 | export interface CIKParams extends DateRangeParams { 92 | cik: string; 93 | } 94 | 95 | export interface CompanyNameSearchParams { 96 | company: string; 97 | } 98 | 99 | export interface CompanySymbolSearchParams { 100 | symbol: string; 101 | } 102 | 103 | export interface CompanyCIKSearchParams { 104 | cik: string; 105 | } 106 | 107 | export interface CompanyProfileParams { 108 | symbol?: string; 109 | cik?: string; 110 | } 111 | 112 | export interface IndustrySearchParams { 113 | industryTitle?: string; 114 | sicCode?: string; 115 | } 116 | 117 | export interface IndustryClassificationSearchParams { 118 | symbol?: string; 119 | cik?: string; 120 | sicCode?: string; 121 | } 122 | 123 | export interface AllIndustryClassificationParams { 124 | page?: number; 125 | limit?: number; 126 | } 127 | -------------------------------------------------------------------------------- /src/client-storage/ClientStorage.ts: -------------------------------------------------------------------------------- 1 | import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 2 | import type { DynamicToolsetManager } from "../dynamic-toolset-manager/DynamicToolsetManager.js"; 3 | import type { ServerMode, ToolSet } from "../types/index.js"; 4 | 5 | interface StorageEntry { 6 | mcpServer: McpServer; 7 | toolManager?: DynamicToolsetManager; 8 | mode: ServerMode; 9 | staticToolSets?: ToolSet[]; 10 | lastAccessed: number; 11 | } 12 | 13 | export interface StorageOptions { 14 | maxSize?: number; 15 | ttl?: number; // ms 16 | } 17 | 18 | export class ClientStorage { 19 | private storage = new Map(); 20 | private maxSize: number; 21 | private ttl: number; 22 | private pruneInterval: NodeJS.Timeout; 23 | 24 | constructor(options: StorageOptions = {}) { 25 | this.maxSize = options.maxSize ?? 1000; 26 | this.ttl = options.ttl ?? 1000 * 60 * 60; 27 | this.pruneInterval = setInterval(() => this.pruneExpired(), 1000 * 60 * 10); 28 | } 29 | 30 | public getEntryCount(): number { 31 | return this.storage.size; 32 | } 33 | 34 | public getMaxSize(): number { 35 | return this.maxSize; 36 | } 37 | 38 | public getTtl(): number { 39 | return this.ttl; 40 | } 41 | 42 | get(clientId: string): Omit | null { 43 | const entry = this.storage.get(clientId); 44 | if (!entry) return null; 45 | 46 | if (Date.now() - entry.lastAccessed > this.ttl) { 47 | console.log(`[ClientStorage] Client ${clientId} expired. Deleting.`); 48 | this.delete(clientId); 49 | return null; 50 | } 51 | 52 | entry.lastAccessed = Date.now(); 53 | this.storage.delete(clientId); 54 | this.storage.set(clientId, entry); 55 | return { mcpServer: entry.mcpServer, toolManager: entry.toolManager, mode: entry.mode, staticToolSets: entry.staticToolSets }; 56 | } 57 | 58 | set(clientId: string, data: Omit): void { 59 | if (this.storage.size >= this.maxSize) { 60 | this.evictLeastRecentlyUsed(); 61 | } 62 | const newEntry: StorageEntry = { ...data, lastAccessed: Date.now() }; 63 | this.storage.set(clientId, newEntry); 64 | } 65 | 66 | delete(clientId: string): void { 67 | this.storage.delete(clientId); 68 | } 69 | 70 | stop(): void { 71 | if (this.pruneInterval) { 72 | clearInterval(this.pruneInterval); 73 | this.pruneInterval = null as any; 74 | } 75 | } 76 | 77 | private evictLeastRecentlyUsed(): void { 78 | const lruClientId = this.storage.keys().next().value; 79 | if (lruClientId) { 80 | console.log(`[ClientStorage] Max size reached. Evicting least recently used client: ${lruClientId}`); 81 | this.delete(lruClientId); 82 | } 83 | } 84 | 85 | private pruneExpired(): void { 86 | const now = Date.now(); 87 | console.log(`[ClientStorage] Pruning expired clients...`); 88 | for (const [clientId, entry] of this.storage.entries()) { 89 | if (now - entry.lastAccessed > this.ttl) { 90 | console.log(`[ClientStorage] Pruning expired client: ${clientId}`); 91 | this.delete(clientId); 92 | } 93 | } 94 | } 95 | } 96 | 97 | 98 | -------------------------------------------------------------------------------- /src/api/company/types.ts: -------------------------------------------------------------------------------- 1 | export interface CompanyProfile { 2 | symbol: string; 3 | price: number; 4 | marketCap: number; 5 | beta: number; 6 | lastDividend: number; 7 | range: string; 8 | change: number; 9 | changePercentage: number; 10 | volume: number; 11 | averageVolume: number; 12 | companyName: string; 13 | currency: string; 14 | cik: string; 15 | isin: string; 16 | cusip: string; 17 | exchangeFullName: string; 18 | exchange: string; 19 | industry: string; 20 | website: string; 21 | description: string; 22 | ceo: string; 23 | sector: string; 24 | country: string; 25 | fullTimeEmployees: string; 26 | phone: string; 27 | address: string; 28 | city: string; 29 | state: string; 30 | zip: string; 31 | image: string; 32 | ipoDate: string; 33 | defaultImage: boolean; 34 | isEtf: boolean; 35 | isActivelyTrading: boolean; 36 | isAdr: boolean; 37 | isFund: boolean; 38 | } 39 | 40 | export interface CompanyNote { 41 | cik: string; 42 | symbol: string; 43 | title: string; 44 | exchange: string; 45 | } 46 | 47 | export interface StockPeer { 48 | symbol: string; 49 | companyName: string; 50 | price: number; 51 | mktCap: number; 52 | } 53 | 54 | export interface DelistedCompany { 55 | symbol: string; 56 | companyName: string; 57 | exchange: string; 58 | ipoDate: string; 59 | delistedDate: string; 60 | } 61 | 62 | export interface EmployeeCount { 63 | symbol: string; 64 | cik: string; 65 | acceptanceTime: string; 66 | periodOfReport: string; 67 | companyName: string; 68 | formType: string; 69 | filingDate: string; 70 | employeeCount: number; 71 | source: string; 72 | } 73 | 74 | export interface MarketCap { 75 | symbol: string; 76 | date: string; 77 | marketCap: number; 78 | } 79 | 80 | export interface ShareFloat { 81 | symbol: string; 82 | date: string; 83 | freeFloat: number; 84 | floatShares: number; 85 | outstandingShares: number; 86 | } 87 | 88 | export interface MergerAcquisition { 89 | symbol: string; 90 | companyName: string; 91 | cik: string; 92 | targetedCompanyName: string; 93 | targetedCik: string; 94 | targetedSymbol: string; 95 | transactionDate: string; 96 | acceptedDate: string; 97 | link: string; 98 | } 99 | 100 | export interface CompanyExecutive { 101 | title: string; 102 | name: string; 103 | pay: number | null; 104 | currencyPay: string; 105 | gender: string | null; 106 | yearBorn: number | null; 107 | active: boolean | null; 108 | } 109 | 110 | export interface ExecutiveCompensation { 111 | cik: string; 112 | symbol: string; 113 | companyName: string; 114 | filingDate: string; 115 | acceptedDate: string; 116 | nameAndPosition: string; 117 | year: number; 118 | salary: number; 119 | bonus: number; 120 | stockAward: number; 121 | optionAward: number; 122 | incentivePlanCompensation: number; 123 | allOtherCompensation: number; 124 | total: number; 125 | link: string; 126 | } 127 | 128 | export interface ExecutiveCompensationBenchmark { 129 | industryTitle: string; 130 | year: number; 131 | averageCompensation: number; 132 | } 133 | -------------------------------------------------------------------------------- /src/tools/esg.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { ESGClient } from "../api/esg/ESGClient.js"; 3 | import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 4 | 5 | /** 6 | * Register all ESG-related tools with the MCP server 7 | * @param server The MCP server instance 8 | * @param accessToken The Financial Modeling Prep API access token (optional when using lazy loading) 9 | */ 10 | export function registerESGTools(server: McpServer, accessToken?: string): void { 11 | const esgClient = new ESGClient(accessToken); 12 | 13 | server.tool( 14 | "getESGDisclosures", 15 | "Align your investments with your values using the FMP ESG Investment Search API. Discover companies and funds based on Environmental, Social, and Governance (ESG) scores, performance, controversies, and business involvement criteria.", 16 | { 17 | symbol: z.string().describe("Stock symbol"), 18 | }, 19 | async ({ symbol }) => { 20 | try { 21 | const results = await esgClient.getDisclosures(symbol); 22 | return { 23 | content: [{ type: "text", text: JSON.stringify(results, null, 2) }], 24 | }; 25 | } catch (error) { 26 | return { 27 | content: [ 28 | { 29 | type: "text", 30 | text: `Error: ${ 31 | error instanceof Error ? error.message : String(error) 32 | }`, 33 | }, 34 | ], 35 | isError: true, 36 | }; 37 | } 38 | } 39 | ); 40 | 41 | server.tool( 42 | "getESGRatings", 43 | "Access comprehensive ESG ratings for companies and funds with the FMP ESG Ratings API. Make informed investment decisions based on environmental, social, and governance (ESG) performance data.", 44 | { 45 | symbol: z.string().describe("Stock symbol"), 46 | }, 47 | async ({ symbol }) => { 48 | try { 49 | const results = await esgClient.getRatings(symbol); 50 | return { 51 | content: [{ type: "text", text: JSON.stringify(results, null, 2) }], 52 | }; 53 | } catch (error) { 54 | return { 55 | content: [ 56 | { 57 | type: "text", 58 | text: `Error: ${ 59 | error instanceof Error ? error.message : String(error) 60 | }`, 61 | }, 62 | ], 63 | isError: true, 64 | }; 65 | } 66 | } 67 | ); 68 | 69 | server.tool( 70 | "getESGBenchmarks", 71 | "Evaluate the ESG performance of companies and funds with the FMP ESG Benchmark Comparison API. Compare ESG leaders and laggards within industries to make informed and responsible investment decisions.", 72 | { 73 | year: z 74 | .string() 75 | .optional() 76 | .describe("Optional year to get benchmarks for"), 77 | }, 78 | async ({ year }) => { 79 | try { 80 | const results = await esgClient.getBenchmarks(year); 81 | return { 82 | content: [{ type: "text", text: JSON.stringify(results, null, 2) }], 83 | }; 84 | } catch (error) { 85 | return { 86 | content: [ 87 | { 88 | type: "text", 89 | text: `Error: ${ 90 | error instanceof Error ? error.message : String(error) 91 | }`, 92 | }, 93 | ], 94 | isError: true, 95 | }; 96 | } 97 | } 98 | ); 99 | } 100 | -------------------------------------------------------------------------------- /src/tools/market-hours.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { MarketHoursClient } from "../api/market-hours/MarketHoursClient.js"; 3 | import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 4 | 5 | /** 6 | * Register all market hours-related tools with the MCP server 7 | * @param server The MCP server instance 8 | * @param accessToken The Financial Modeling Prep API access token (optional when using lazy loading) 9 | */ 10 | export function registerMarketHoursTools( 11 | server: McpServer, 12 | accessToken?: string 13 | ): void { 14 | const marketHoursClient = new MarketHoursClient(accessToken); 15 | 16 | server.tool( 17 | "getExchangeMarketHours", 18 | "Retrieve trading hours for specific stock exchanges using the Global Exchange Market Hours API. Find out the opening and closing times of global exchanges to plan your trading strategies effectively.", 19 | { 20 | exchange: z.string().describe("Exchange code (e.g., NASDAQ, NYSE)"), 21 | }, 22 | async ({ exchange }) => { 23 | try { 24 | const results = await marketHoursClient.getExchangeMarketHours( 25 | exchange 26 | ); 27 | return { 28 | content: [{ type: "text", text: JSON.stringify(results, null, 2) }], 29 | }; 30 | } catch (error) { 31 | return { 32 | content: [ 33 | { 34 | type: "text", 35 | text: `Error: ${ 36 | error instanceof Error ? error.message : String(error) 37 | }`, 38 | }, 39 | ], 40 | isError: true, 41 | }; 42 | } 43 | } 44 | ); 45 | 46 | server.tool( 47 | "getHolidaysByExchange", 48 | "Access holiday schedules for specific stock exchanges using the Global Exchange Market Hours API. Find out the dates when global exchanges are closed for holidays and plan your trading activities accordingly.", 49 | { 50 | exchange: z.string().describe("Exchange code (e.g., NASDAQ, NYSE)"), 51 | from: z.string().optional().describe("Start date for the holidays (YYYY-MM-DD format)"), 52 | to: z.string().optional().describe("End date for the holidays (YYYY-MM-DD format)"), 53 | }, 54 | async ({ exchange, from, to }) => { 55 | try { 56 | const results = await marketHoursClient.getHolidaysByExchange( 57 | exchange, 58 | from, 59 | to 60 | ); 61 | return { 62 | content: [{ type: "text", text: JSON.stringify(results, null, 2) }], 63 | }; 64 | } catch (error) { 65 | return { 66 | content: [ 67 | { 68 | type: "text", 69 | text: `Error: ${ 70 | error instanceof Error ? error.message : String(error) 71 | }`, 72 | }, 73 | ], 74 | isError: true, 75 | }; 76 | } 77 | } 78 | ); 79 | 80 | server.tool( 81 | "getAllExchangeMarketHours", 82 | "View the market hours for all exchanges. Check when different markets are active.", 83 | {}, 84 | async () => { 85 | try { 86 | const results = await marketHoursClient.getAllExchangeMarketHours(); 87 | return { 88 | content: [{ type: "text", text: JSON.stringify(results, null, 2) }], 89 | }; 90 | } catch (error) { 91 | return { 92 | content: [ 93 | { 94 | type: "text", 95 | text: `Error: ${ 96 | error instanceof Error ? error.message : String(error) 97 | }`, 98 | }, 99 | ], 100 | isError: true, 101 | }; 102 | } 103 | }); 104 | } 105 | -------------------------------------------------------------------------------- /src/api/fundraisers/types.ts: -------------------------------------------------------------------------------- 1 | export interface CrowdfundingCampaign { 2 | cik: string; 3 | companyName: string; 4 | date: string | null; 5 | filingDate: string; 6 | acceptedDate: string; 7 | formType: string; 8 | formSignification: string; 9 | nameOfIssuer: string; 10 | legalStatusForm: string; 11 | jurisdictionOrganization: string; 12 | issuerStreet: string; 13 | issuerCity: string; 14 | issuerStateOrCountry: string; 15 | issuerZipCode: string; 16 | issuerWebsite: string; 17 | intermediaryCompanyName: string; 18 | intermediaryCommissionCik: string; 19 | intermediaryCommissionFileNumber: string; 20 | compensationAmount: string; 21 | financialInterest: string; 22 | securityOfferedType: string; 23 | securityOfferedOtherDescription: string; 24 | numberOfSecurityOffered: number; 25 | offeringPrice: number; 26 | offeringAmount: number; 27 | overSubscriptionAccepted: string; 28 | overSubscriptionAllocationType: string; 29 | maximumOfferingAmount: number; 30 | offeringDeadlineDate: string; 31 | currentNumberOfEmployees: number; 32 | totalAssetMostRecentFiscalYear: number; 33 | totalAssetPriorFiscalYear: number; 34 | cashAndCashEquiValentMostRecentFiscalYear: number; 35 | cashAndCashEquiValentPriorFiscalYear: number; 36 | accountsReceivableMostRecentFiscalYear: number; 37 | accountsReceivablePriorFiscalYear: number; 38 | shortTermDebtMostRecentFiscalYear: number; 39 | shortTermDebtPriorFiscalYear: number; 40 | longTermDebtMostRecentFiscalYear: number; 41 | longTermDebtPriorFiscalYear: number; 42 | revenueMostRecentFiscalYear: number; 43 | revenuePriorFiscalYear: number; 44 | costGoodsSoldMostRecentFiscalYear: number; 45 | costGoodsSoldPriorFiscalYear: number; 46 | taxesPaidMostRecentFiscalYear: number; 47 | taxesPaidPriorFiscalYear: number; 48 | netIncomeMostRecentFiscalYear: number; 49 | netIncomePriorFiscalYear: number; 50 | } 51 | 52 | export interface CrowdfundingSearchResult { 53 | cik: string; 54 | name: string; 55 | date: string | null; 56 | } 57 | 58 | export interface EquityOffering { 59 | cik: string; 60 | companyName: string; 61 | date: string; 62 | filingDate: string; 63 | acceptedDate: string; 64 | formType: string; 65 | formSignification: string; 66 | entityName: string; 67 | issuerStreet: string; 68 | issuerCity: string; 69 | issuerStateOrCountry: string; 70 | issuerStateOrCountryDescription: string; 71 | issuerZipCode: string; 72 | issuerPhoneNumber: string; 73 | jurisdictionOfIncorporation: string; 74 | entityType: string; 75 | incorporatedWithinFiveYears: boolean | null; 76 | yearOfIncorporation: string; 77 | relatedPersonFirstName: string; 78 | relatedPersonLastName: string; 79 | relatedPersonStreet: string; 80 | relatedPersonCity: string; 81 | relatedPersonStateOrCountry: string; 82 | relatedPersonStateOrCountryDescription: string; 83 | relatedPersonZipCode: string; 84 | relatedPersonRelationship: string; 85 | industryGroupType: string; 86 | revenueRange: string | null; 87 | federalExemptionsExclusions: string; 88 | isAmendment: boolean; 89 | dateOfFirstSale: string; 90 | durationOfOfferingIsMoreThanYear: boolean; 91 | securitiesOfferedAreOfEquityType: boolean; 92 | isBusinessCombinationTransaction: boolean; 93 | minimumInvestmentAccepted: number; 94 | totalOfferingAmount: number; 95 | totalAmountSold: number; 96 | totalAmountRemaining: number; 97 | hasNonAccreditedInvestors: boolean; 98 | totalNumberAlreadyInvested: number; 99 | salesCommissions: number; 100 | findersFees: number; 101 | grossProceedsUsed: number; 102 | } 103 | 104 | export interface EquityOfferingSearchResult { 105 | cik: string; 106 | name: string; 107 | date: string; 108 | } 109 | -------------------------------------------------------------------------------- /src/tools/cot.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { COTClient } from "../api/cot/COTClient.js"; 3 | import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 4 | 5 | /** 6 | * Register all COT-related tools with the MCP server 7 | * @param server The MCP server instance 8 | * @param accessToken The Financial Modeling Prep API access token (optional when using lazy loading) 9 | */ 10 | export function registerCOTTools(server: McpServer, accessToken?: string): void { 11 | const cotClient = new COTClient(accessToken); 12 | 13 | server.tool( 14 | "getCOTReports", 15 | "Access comprehensive Commitment of Traders (COT) reports with the FMP COT Report API. This API provides detailed information about long and short positions across various sectors, helping you assess market sentiment and track positions in commodities, indices, and financial instruments.", 16 | { 17 | symbol: z.string().describe("Commodity symbol"), 18 | from: z 19 | .string() 20 | .optional() 21 | .describe("Optional start date (YYYY-MM-DD)"), 22 | to: z 23 | .string() 24 | .optional() 25 | .describe("Optional end date (YYYY-MM-DD)"), 26 | }, 27 | async ({ symbol, from, to }) => { 28 | try { 29 | const results = await cotClient.getReports(symbol, from, to); 30 | return { 31 | content: [{ type: "text", text: JSON.stringify(results, null, 2) }], 32 | }; 33 | } catch (error) { 34 | return { 35 | content: [ 36 | { 37 | type: "text", 38 | text: `Error: ${ 39 | error instanceof Error ? error.message : String(error) 40 | }`, 41 | }, 42 | ], 43 | isError: true, 44 | }; 45 | } 46 | } 47 | ); 48 | 49 | server.tool( 50 | "getCOTAnalysis", 51 | "Gain in-depth insights into market sentiment with the FMP COT Report Analysis API. Analyze the Commitment of Traders (COT) reports for a specific date range to evaluate market dynamics, sentiment, and potential reversals across various sectors.", 52 | { 53 | symbol: z.string().describe("Commodity symbol"), 54 | from: z 55 | .string() 56 | .optional() 57 | .describe("Optional start date (YYYY-MM-DD)"), 58 | to: z 59 | .string() 60 | .optional() 61 | .describe("Optional end date (YYYY-MM-DD)"), 62 | }, 63 | async ({ symbol, from, to }) => { 64 | try { 65 | const results = await cotClient.getAnalysis(symbol, from, to); 66 | return { 67 | content: [{ type: "text", text: JSON.stringify(results, null, 2) }], 68 | }; 69 | } catch (error) { 70 | return { 71 | content: [ 72 | { 73 | type: "text", 74 | text: `Error: ${ 75 | error instanceof Error ? error.message : String(error) 76 | }`, 77 | }, 78 | ], 79 | isError: true, 80 | }; 81 | } 82 | } 83 | ); 84 | 85 | server.tool("getCOTList", 86 | "Access a comprehensive list of available Commitment of Traders (COT) reports by commodity or futures contract using the FMP COT Report List API. This API provides an overview of different market segments, allowing users to retrieve and explore COT reports for a wide variety of commodities and financial instruments.", 87 | {}, 88 | async () => { 89 | try { 90 | const results = await cotClient.getList(); 91 | return { 92 | content: [{ type: "text", text: JSON.stringify(results, null, 2) }], 93 | }; 94 | } catch (error) { 95 | return { 96 | content: [ 97 | { 98 | type: "text", 99 | text: `Error: ${ 100 | error instanceof Error ? error.message : String(error) 101 | }`, 102 | }, 103 | ], 104 | isError: true, 105 | }; 106 | } 107 | }); 108 | } 109 | -------------------------------------------------------------------------------- /src/api/government-trading/GovernmentTradingClient.ts: -------------------------------------------------------------------------------- 1 | import { FMPClient } from "../FMPClient.js"; 2 | import type { FMPContext } from "../../types/index.js"; 3 | import type { 4 | FinancialDisclosure, 5 | PaginationParams, 6 | SymbolParams, 7 | NameParams, 8 | } from "./types.js"; 9 | 10 | export class GovernmentTradingClient extends FMPClient { 11 | constructor(apiKey?: string) { 12 | super(apiKey); 13 | } 14 | 15 | /** 16 | * Get latest financial disclosures from U.S. Senate members 17 | * @param params Optional pagination parameters 18 | * @param options Optional parameters including abort signal and context 19 | */ 20 | async getLatestSenateDisclosures( 21 | params: PaginationParams = {}, 22 | options?: { 23 | signal?: AbortSignal; 24 | context?: FMPContext; 25 | } 26 | ): Promise { 27 | return this.get( 28 | `/senate-latest`, 29 | { 30 | page: params.page, 31 | limit: params.limit, 32 | }, 33 | options 34 | ); 35 | } 36 | 37 | /** 38 | * Get latest financial disclosures from U.S. House members 39 | * @param params Optional pagination parameters 40 | * @param options Optional parameters including abort signal and context 41 | */ 42 | async getLatestHouseDisclosures( 43 | params: PaginationParams = {}, 44 | options?: { 45 | signal?: AbortSignal; 46 | context?: FMPContext; 47 | } 48 | ): Promise { 49 | return this.get( 50 | `/house-latest`, 51 | { 52 | page: params.page, 53 | limit: params.limit, 54 | }, 55 | options 56 | ); 57 | } 58 | 59 | /** 60 | * Get Senate trading activity for a specific symbol 61 | * @param params Symbol parameters 62 | * @param options Optional parameters including abort signal and context 63 | */ 64 | async getSenateTrades( 65 | params: SymbolParams, 66 | options?: { 67 | signal?: AbortSignal; 68 | context?: FMPContext; 69 | } 70 | ): Promise { 71 | return this.get( 72 | `/senate-trades`, 73 | { 74 | symbol: params.symbol, 75 | }, 76 | options 77 | ); 78 | } 79 | 80 | /** 81 | * Get Senate trades by senator name 82 | * @param params Name parameters 83 | * @param options Optional parameters including abort signal and context 84 | */ 85 | async getSenateTradesByName( 86 | params: NameParams, 87 | options?: { 88 | signal?: AbortSignal; 89 | context?: FMPContext; 90 | } 91 | ): Promise { 92 | return this.get( 93 | `/senate-trades-by-name`, 94 | { 95 | name: params.name, 96 | }, 97 | options 98 | ); 99 | } 100 | 101 | /** 102 | * Get House trading activity for a specific symbol 103 | * @param params Symbol parameters 104 | * @param options Optional parameters including abort signal and context 105 | */ 106 | async getHouseTrades( 107 | params: SymbolParams, 108 | options?: { 109 | signal?: AbortSignal; 110 | context?: FMPContext; 111 | } 112 | ): Promise { 113 | return this.get( 114 | `/house-trades`, 115 | { 116 | symbol: params.symbol, 117 | }, 118 | options 119 | ); 120 | } 121 | 122 | /** 123 | * Get House trades by representative name 124 | * @param params Name parameters 125 | * @param options Optional parameters including abort signal and context 126 | */ 127 | async getHouseTradesByName( 128 | params: NameParams, 129 | options?: { 130 | signal?: AbortSignal; 131 | context?: FMPContext; 132 | } 133 | ): Promise { 134 | return this.get( 135 | `/house-trades-by-name`, 136 | { 137 | name: params.name, 138 | }, 139 | options 140 | ); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/api/insider-trades/InsiderTradesClient.ts: -------------------------------------------------------------------------------- 1 | import { FMPClient } from "../FMPClient.js"; 2 | import type { FMPContext } from "../../types/index.js"; 3 | import { 4 | InsiderTrading, 5 | InsiderReportingName, 6 | InsiderTransactionType, 7 | InsiderTradeStatistics, 8 | AcquisitionOwnership, 9 | } from "./types.js"; 10 | 11 | export class InsiderTradesClient extends FMPClient { 12 | constructor(apiKey?: string) { 13 | super(apiKey); 14 | } 15 | 16 | /** 17 | * Get latest insider trading activities 18 | * @param params Optional parameters for date, pagination 19 | * @param options Optional parameters including abort signal and context 20 | */ 21 | async getLatestInsiderTrading( 22 | params: { date?: string; page?: number; limit?: number } = {}, 23 | options?: { 24 | signal?: AbortSignal; 25 | context?: FMPContext; 26 | } 27 | ): Promise { 28 | return super.get( 29 | "/insider-trading/latest", 30 | params, 31 | options 32 | ); 33 | } 34 | 35 | /** 36 | * Search insider trades by various criteria 37 | * @param params Search parameters 38 | * @param options Optional parameters including abort signal and context 39 | */ 40 | async searchInsiderTrades( 41 | params: { 42 | symbol?: string; 43 | page?: number; 44 | limit?: number; 45 | reportingCik?: string; 46 | companyCik?: string; 47 | transactionType?: string; 48 | } = {}, 49 | options?: { 50 | signal?: AbortSignal; 51 | context?: FMPContext; 52 | } 53 | ): Promise { 54 | return super.get( 55 | "/insider-trading/search", 56 | params, 57 | options 58 | ); 59 | } 60 | 61 | /** 62 | * Search insider trades by reporting name 63 | * @param name Name to search for 64 | * @param options Optional parameters including abort signal and context 65 | */ 66 | async searchInsiderTradesByReportingName( 67 | name: string, 68 | options?: { 69 | signal?: AbortSignal; 70 | context?: FMPContext; 71 | } 72 | ): Promise { 73 | return super.get( 74 | "/insider-trading/reporting-name", 75 | { name }, 76 | options 77 | ); 78 | } 79 | 80 | /** 81 | * Get all insider transaction types 82 | * @param options Optional parameters including abort signal and context 83 | */ 84 | async getInsiderTransactionTypes(options?: { 85 | signal?: AbortSignal; 86 | context?: FMPContext; 87 | }): Promise { 88 | return super.get( 89 | "/insider-trading-transaction-type", 90 | {}, 91 | options 92 | ); 93 | } 94 | 95 | /** 96 | * Get insider trade statistics for a symbol 97 | * @param symbol Stock symbol 98 | * @param options Optional parameters including abort signal and context 99 | */ 100 | async getInsiderTradeStatistics( 101 | symbol: string, 102 | options?: { 103 | signal?: AbortSignal; 104 | context?: FMPContext; 105 | } 106 | ): Promise { 107 | return super.get( 108 | "/insider-trading/statistics", 109 | { symbol }, 110 | options 111 | ); 112 | } 113 | 114 | /** 115 | * Get acquisition ownership information for a symbol 116 | * @param symbol Stock symbol 117 | * @param limit Optional limit on number of results 118 | * @param options Optional parameters including abort signal and context 119 | */ 120 | async getAcquisitionOwnership( 121 | symbol: string, 122 | limit?: number, 123 | options?: { 124 | signal?: AbortSignal; 125 | context?: FMPContext; 126 | } 127 | ): Promise { 128 | const params: Record = { symbol }; 129 | if (limit !== undefined) { 130 | params.limit = limit; 131 | } 132 | return super.get( 133 | "/acquisition-of-beneficial-ownership", 134 | params, 135 | options 136 | ); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/api/chart/ChartClient.ts: -------------------------------------------------------------------------------- 1 | import { FMPClient } from "../FMPClient.js"; 2 | import type { FMPContext } from "../../types/index.js"; 3 | import type { 4 | ChartData, 5 | LightChartData, 6 | UnadjustedChartData, 7 | Interval, 8 | IntradayChartData, 9 | } from "./types.js"; 10 | 11 | 12 | 13 | export class ChartClient extends FMPClient { 14 | constructor(apiKey?: string) { 15 | super(apiKey); 16 | } 17 | 18 | /** 19 | * Get light chart data for a stock symbol 20 | * @param symbol Stock symbol 21 | * @param from Optional start date (YYYY-MM-DD) 22 | * @param to Optional end date (YYYY-MM-DD) 23 | * @param options Optional parameters including abort signal and context 24 | * @returns Array of light chart data 25 | */ 26 | async getLightChart( 27 | symbol: string, 28 | from?: string, 29 | to?: string, 30 | options?: { 31 | signal?: AbortSignal; 32 | context?: FMPContext; 33 | } 34 | ): Promise { 35 | return super.get( 36 | "/historical-price-eod/light", 37 | { symbol, from, to }, 38 | options 39 | ); 40 | } 41 | 42 | /** 43 | * Get full chart data for a stock symbol 44 | * @param symbol Stock symbol 45 | * @param from Optional start date (YYYY-MM-DD) 46 | * @param to Optional end date (YYYY-MM-DD) 47 | * @param options Optional parameters including abort signal and context 48 | * @returns Array of chart data 49 | */ 50 | async getFullChart( 51 | symbol: string, 52 | from?: string, 53 | to?: string, 54 | options?: { 55 | signal?: AbortSignal; 56 | context?: FMPContext; 57 | } 58 | ): Promise { 59 | return super.get( 60 | "/historical-price-eod/full", 61 | { symbol, from, to }, 62 | options 63 | ); 64 | } 65 | 66 | /** 67 | * Get unadjusted chart data for a stock symbol 68 | * @param symbol Stock symbol 69 | * @param from Optional start date (YYYY-MM-DD) 70 | * @param to Optional end date (YYYY-MM-DD) 71 | * @param options Optional parameters including abort signal and context 72 | * @returns Array of unadjusted chart data 73 | */ 74 | async getUnadjustedChart( 75 | symbol: string, 76 | from?: string, 77 | to?: string, 78 | options?: { 79 | signal?: AbortSignal; 80 | context?: FMPContext; 81 | } 82 | ): Promise { 83 | return super.get( 84 | "/historical-price-eod/non-split-adjusted", 85 | { symbol, from, to }, 86 | options 87 | ); 88 | } 89 | 90 | /** 91 | * Get dividend-adjusted chart data for a stock symbol 92 | * @param symbol Stock symbol 93 | * @param from Optional start date (YYYY-MM-DD) 94 | * @param to Optional end date (YYYY-MM-DD) 95 | * @param options Optional parameters including abort signal and context 96 | * @returns Array of dividend-adjusted chart data 97 | */ 98 | async getDividendAdjustedChart( 99 | symbol: string, 100 | from?: string, 101 | to?: string, 102 | options?: { 103 | signal?: AbortSignal; 104 | context?: FMPContext; 105 | } 106 | ): Promise { 107 | return super.get( 108 | "/historical-price-eod/dividend-adjusted", 109 | { symbol, from, to }, 110 | options 111 | ); 112 | } 113 | 114 | /** 115 | * Get intraday chart data for a stock symbol 116 | * @param symbol Stock symbol 117 | * @param interval Time interval (1min, 5min, 15min, 30min, 1hour, 4hour) 118 | * @param from Optional start date (YYYY-MM-DD) 119 | * @param to Optional end date (YYYY-MM-DD) 120 | * @param options Optional parameters including abort signal and context 121 | * @returns Array of chart data 122 | */ 123 | async getIntradayChart( 124 | symbol: string, 125 | interval: Interval, 126 | from?: string, 127 | to?: string, 128 | options?: { 129 | signal?: AbortSignal; 130 | context?: FMPContext; 131 | } 132 | ): Promise { 133 | return super.get( 134 | `/historical-chart/${interval}`, 135 | { symbol, from, to }, 136 | options 137 | ); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /scripts/verify-npm-ready.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env tsx 2 | 3 | /** 4 | * Simple verification script to ensure the package is ready for NPM publishing 5 | * This script performs essential checks without complex installation testing 6 | */ 7 | 8 | import { readFileSync, existsSync } from "fs"; 9 | import { execSync } from "child_process"; 10 | 11 | interface PackageJson { 12 | name: string; 13 | version: string; 14 | mcpName?: string; 15 | main: string; 16 | bin?: Record; 17 | files?: string[]; 18 | } 19 | 20 | /** 21 | * Executes a command safely and returns success status 22 | * @param command - Command to execute 23 | * @returns true if command succeeded, false otherwise 24 | */ 25 | function safeExecute(command: string): boolean { 26 | try { 27 | execSync(command, { stdio: "pipe" }); 28 | return true; 29 | } catch { 30 | return false; 31 | } 32 | } 33 | 34 | /** 35 | * Main verification function 36 | */ 37 | function main(): void { 38 | console.log("🔍 Verifying NPM publishing readiness...\n"); 39 | 40 | let allChecks = true; 41 | 42 | // Check 1: package.json exists and has required fields 43 | console.log("1. Checking package.json configuration..."); 44 | try { 45 | const packageJson: PackageJson = JSON.parse( 46 | readFileSync("package.json", "utf-8") 47 | ); 48 | 49 | if (!packageJson.mcpName) { 50 | console.log(" ❌ Missing mcpName field"); 51 | allChecks = false; 52 | } else if (!packageJson.mcpName.startsWith("io.github.imbenrabi/")) { 53 | console.log(` ❌ Invalid mcpName format: ${packageJson.mcpName}`); 54 | allChecks = false; 55 | } else { 56 | console.log(` ✅ mcpName: ${packageJson.mcpName}`); 57 | } 58 | 59 | if (!packageJson.files || !packageJson.files.includes("dist")) { 60 | console.log(" ❌ Missing dist in files array"); 61 | allChecks = false; 62 | } else { 63 | console.log(" ✅ Files array includes dist"); 64 | } 65 | 66 | if (!packageJson.bin || !packageJson.bin["fmp-mcp"]) { 67 | console.log(" ❌ Missing binary configuration"); 68 | allChecks = false; 69 | } else { 70 | console.log(" ✅ Binary configuration present"); 71 | } 72 | } catch (error) { 73 | console.log(" ❌ Failed to read package.json"); 74 | allChecks = false; 75 | } 76 | 77 | // Check 2: Build works 78 | console.log("\n2. Checking build process..."); 79 | if (safeExecute("npm run build")) { 80 | console.log(" ✅ Build successful"); 81 | } else { 82 | console.log(" ❌ Build failed"); 83 | allChecks = false; 84 | } 85 | 86 | // Check 3: Required files exist (after build) 87 | console.log("\n3. Checking required files..."); 88 | const requiredFiles = [ 89 | "LICENSE", 90 | "README.md", 91 | "dist/index.js", 92 | "dist/index.d.ts", 93 | ]; 94 | 95 | for (const file of requiredFiles) { 96 | if (existsSync(file)) { 97 | console.log(` ✅ ${file}`); 98 | } else { 99 | console.log(` ❌ Missing: ${file}`); 100 | allChecks = false; 101 | } 102 | } 103 | 104 | // Check 4: Package creation works 105 | console.log("\n4. Checking package creation..."); 106 | if (safeExecute("npm pack --dry-run")) { 107 | console.log(" ✅ Package creation successful"); 108 | } else { 109 | console.log(" ❌ Package creation failed"); 110 | allChecks = false; 111 | } 112 | 113 | // Check 5: Version consistency 114 | console.log("\n5. Checking version consistency..."); 115 | if (safeExecute("npm run version:validate")) { 116 | console.log(" ✅ Version consistency verified"); 117 | } else { 118 | console.log(" ❌ Version inconsistency detected"); 119 | allChecks = false; 120 | } 121 | 122 | // Summary 123 | console.log("\n" + "=".repeat(50)); 124 | if (allChecks) { 125 | console.log("🎉 All checks passed! Package is ready for NPM publishing."); 126 | console.log("\n📋 Manual publishing steps:"); 127 | console.log("1. npm login --registry https://registry.npmjs.org/"); 128 | console.log("2. npm publish --registry https://registry.npmjs.org/"); 129 | console.log("3. npm view financial-modeling-prep-mcp-server"); 130 | } else { 131 | console.log( 132 | "❌ Some checks failed. Please fix the issues above before publishing." 133 | ); 134 | process.exit(1); 135 | } 136 | } 137 | 138 | // Run if executed directly 139 | if (import.meta.url === `file://${process.argv[1]}`) { 140 | main(); 141 | } 142 | -------------------------------------------------------------------------------- /src/api/fundraisers/FundraisersClient.ts: -------------------------------------------------------------------------------- 1 | import { FMPClient } from "../FMPClient.js"; 2 | import type { FMPContext } from "../../types/index.js"; 3 | import type { 4 | CrowdfundingCampaign, 5 | CrowdfundingSearchResult, 6 | EquityOffering, 7 | EquityOfferingSearchResult, 8 | } from "./types.js"; 9 | 10 | 11 | 12 | export class FundraisersClient extends FMPClient { 13 | constructor(apiKey?: string) { 14 | super(apiKey); 15 | } 16 | 17 | /** 18 | * Get latest crowdfunding campaigns 19 | * @param page Optional page number (default: 0) 20 | * @param limit Optional number of results per page (default: 100) 21 | * @param options Additional options including abort signal and context 22 | * @returns Array of crowdfunding campaigns 23 | */ 24 | async getLatestCrowdfundingCampaigns( 25 | page?: number, 26 | limit?: number, 27 | options?: { 28 | signal?: AbortSignal; 29 | context?: FMPContext; 30 | } 31 | ): Promise { 32 | return super.get( 33 | "/crowdfunding-offerings-latest", 34 | { 35 | page, 36 | limit, 37 | }, 38 | options 39 | ); 40 | } 41 | 42 | /** 43 | * Search for crowdfunding campaigns by name 44 | * @param name Company name, campaign name, or platform to search for 45 | * @param options Additional options including abort signal and context 46 | * @returns Array of crowdfunding search results 47 | */ 48 | async searchCrowdfundingCampaigns( 49 | name: string, 50 | options?: { 51 | signal?: AbortSignal; 52 | context?: FMPContext; 53 | } 54 | ): Promise { 55 | return super.get( 56 | "/crowdfunding-offerings-search", 57 | { 58 | name, 59 | }, 60 | options 61 | ); 62 | } 63 | 64 | /** 65 | * Get crowdfunding campaigns by CIK 66 | * @param cik CIK number to search for 67 | * @param options Additional options including abort signal and context 68 | * @returns Array of crowdfunding campaigns 69 | */ 70 | async getCrowdfundingCampaignsByCIK( 71 | cik: string, 72 | options?: { 73 | signal?: AbortSignal; 74 | context?: FMPContext; 75 | } 76 | ): Promise { 77 | return super.get( 78 | "/crowdfunding-offerings", 79 | { 80 | cik, 81 | }, 82 | options 83 | ); 84 | } 85 | 86 | /** 87 | * Get latest equity offerings 88 | * @param page Optional page number (default: 0) 89 | * @param limit Optional number of results per page (default: 10) 90 | * @param cik Optional CIK to filter by 91 | * @param options Additional options including abort signal and context 92 | * @returns Array of equity offerings 93 | */ 94 | async getLatestEquityOfferings( 95 | page?: number, 96 | limit?: number, 97 | cik?: string, 98 | options?: { 99 | signal?: AbortSignal; 100 | context?: FMPContext; 101 | } 102 | ): Promise { 103 | return super.get( 104 | "/fundraising-latest", 105 | { 106 | page, 107 | limit, 108 | cik, 109 | }, 110 | options 111 | ); 112 | } 113 | 114 | /** 115 | * Search for equity offerings by name 116 | * @param name Company name or stock symbol to search for 117 | * @param options Additional options including abort signal and context 118 | * @returns Array of equity offering search results 119 | */ 120 | async searchEquityOfferings( 121 | name: string, 122 | options?: { 123 | signal?: AbortSignal; 124 | context?: FMPContext; 125 | } 126 | ): Promise { 127 | return super.get( 128 | "/fundraising-search", 129 | { 130 | name, 131 | }, 132 | options 133 | ); 134 | } 135 | 136 | /** 137 | * Get equity offerings by CIK 138 | * @param cik CIK number to search for 139 | * @param options Additional options including abort signal and context 140 | * @returns Array of equity offerings 141 | */ 142 | async getEquityOfferingsByCIK( 143 | cik: string, 144 | options?: { 145 | signal?: AbortSignal; 146 | context?: FMPContext; 147 | } 148 | ): Promise { 149 | return super.get( 150 | "/fundraising", 151 | { 152 | cik, 153 | }, 154 | options 155 | ); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "financial-modeling-prep-mcp-server", 3 | "version": "2.5.3", 4 | "mcpName": "io.github.imbenrabi/financial-modeling-prep-mcp-server", 5 | "description": "Model Context Protocol server for Financial Modeling Prep (FMP) API, exposing 250+ tools for financial data, market insights, and analysis.", 6 | "main": "dist/index.js", 7 | "type": "module", 8 | "bin": { 9 | "fmp-mcp": "dist/index.js" 10 | }, 11 | "files": [ 12 | "dist", 13 | "LICENSE", 14 | "README.md" 15 | ], 16 | "scripts": { 17 | "build": "tsc", 18 | "start": "node dist/index.js", 19 | "prePublishOnly": "npm run build", 20 | "dev": "tsx watch src/index.ts", 21 | "setup": "npm install", 22 | "test": "vitest", 23 | "test:run": "vitest run", 24 | "test:coverage": "vitest run --coverage", 25 | "typecheck": "tsc --noEmit", 26 | "version:validate": "tsx scripts/version-sync.ts validate", 27 | "version:sync": "tsx scripts/version-sync.ts sync", 28 | "version:info": "tsx scripts/version-sync.ts info", 29 | "test:npm-publish": "tsx scripts/test-npm-publish.ts", 30 | "verify:npm-ready": "tsx scripts/verify-npm-ready.ts", 31 | "verify:registry-submission": "node --import tsx/esm scripts/verify-registry-submission.ts", 32 | "publish:manual": "tsx scripts/manual-publish.ts publish", 33 | "publish:dry-run": "tsx scripts/manual-publish.ts publish --dry-run", 34 | "publish:validate": "tsx scripts/manual-publish.ts validate", 35 | "publish:troubleshoot": "tsx scripts/manual-publish.ts troubleshoot", 36 | "test:publish-workflow": "tsx scripts/test-manual-publish.ts", 37 | "test:complete-workflow": "tsx scripts/test-complete-workflow.ts", 38 | "test:automated-pipeline": "tsx scripts/test-automated-pipeline.ts", 39 | "test:registry-suite": "tsx scripts/test-registry-suite.ts", 40 | "test:registry": "vitest run src/registry-tests/", 41 | "test:registry-watch": "vitest src/registry-tests/", 42 | "test:registry-coverage": "vitest run src/registry-tests/ --coverage", 43 | "test:installation-verification": "tsx scripts/test-installation-verification.ts", 44 | "test:installation-ci": "vitest run src/registry-tests/InstallationMethodVerification.ci.test.ts --reporter=verbose --testTimeout=15000", 45 | "verify:installation": "tsx scripts/verify-installation-methods.ts", 46 | "audit:prod": "npm audit --omit=dev" 47 | }, 48 | "author": "imbenrabi", 49 | "license": "Apache-2.0", 50 | "dependencies": { 51 | "@modelcontextprotocol/sdk": "^1.24.3", 52 | "@smithery/sdk": "^1.5.5", 53 | "@types/axios": "0.14.4", 54 | "axios": "^1.13.2", 55 | "commander": "^14.0.0", 56 | "express": "^5.0.1", 57 | "mcp-trace": "^0.1.0", 58 | "minimist": "^1.2.8", 59 | "zod": "^3.22.4" 60 | }, 61 | "devDependencies": { 62 | "@types/express": "^5.0.2", 63 | "@types/minimist": "^1.2.5", 64 | "@types/node": "^20.19.0", 65 | "@vitest/coverage-v8": "^3.2.4", 66 | "tsx": "^4.19.2", 67 | "typescript": "^5.9.2", 68 | "vitest": "^3.2.4" 69 | }, 70 | "engines": { 71 | "node": ">=20.0.0" 72 | }, 73 | "repository": { 74 | "type": "git", 75 | "url": "https://github.com/imbenrabi/Financial-Modeling-Prep-MCP-Server" 76 | }, 77 | "bugs": { 78 | "url": "https://github.com/imbenrabi/Financial-Modeling-Prep-MCP-Server/issues" 79 | }, 80 | "homepage": "https://github.com/imbenrabi/Financial-Modeling-Prep-MCP-Server", 81 | "keywords": [ 82 | "mcp", 83 | "ai", 84 | "assistant", 85 | "fmp", 86 | "financial modeling prep", 87 | "financial data", 88 | "financial analysis", 89 | "financial planning", 90 | "financial reporting", 91 | "quotes", 92 | "symbols", 93 | "ticker", 94 | "ticker symbols", 95 | "stock symbols", 96 | "stock quotes", 97 | "stock", 98 | "stocks", 99 | "stock prices", 100 | "stock market", 101 | "stock data", 102 | "stock analysis", 103 | "stock planning", 104 | "stock reporting", 105 | "exchange", 106 | "exchanges", 107 | "financial news", 108 | "stock news", 109 | "stock market data", 110 | "stock market", 111 | "stock market news", 112 | "stock market analysis", 113 | "stock market planning", 114 | "stock market reporting", 115 | "stock market quotes", 116 | "stock market symbols", 117 | "model context protocol", 118 | "fmp api", 119 | "ai tools", 120 | "server", 121 | "node", 122 | "typescript", 123 | "market data", 124 | "statements", 125 | "news", 126 | "indexes", 127 | "economics", 128 | "crypto", 129 | "forex", 130 | "commodities", 131 | "esg", 132 | "technical indicators", 133 | "sec-filings", 134 | "earnings" 135 | ] 136 | } 137 | -------------------------------------------------------------------------------- /src/tools/economics.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { EconomicsClient } from "../api/economics/EconomicsClient.js"; 3 | import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 4 | 5 | /** 6 | * Register all economics-related tools with the MCP server 7 | * @param server The MCP server instance 8 | * @param accessToken The Financial Modeling Prep API access token (optional when using lazy loading) 9 | */ 10 | export function registerEconomicsTools( 11 | server: McpServer, 12 | accessToken?: string 13 | ): void { 14 | const economicsClient = new EconomicsClient(accessToken); 15 | 16 | server.tool( 17 | "getTreasuryRates", 18 | "Access real-time and historical Treasury rates for all maturities with the FMP Treasury Rates API. Track key benchmarks for interest rates across the economy.", 19 | { 20 | from: z 21 | .string() 22 | .optional() 23 | .describe("Optional start date (YYYY-MM-DD)"), 24 | to: z 25 | .string() 26 | .optional() 27 | .describe("Optional end date (YYYY-MM-DD)"), 28 | }, 29 | async ({ from, to }) => { 30 | try { 31 | const results = await economicsClient.getTreasuryRates(from, to); 32 | return { 33 | content: [{ type: "text", text: JSON.stringify(results, null, 2) }], 34 | }; 35 | } catch (error) { 36 | return { 37 | content: [ 38 | { 39 | type: "text", 40 | text: `Error: ${ 41 | error instanceof Error ? error.message : String(error) 42 | }`, 43 | }, 44 | ], 45 | isError: true, 46 | }; 47 | } 48 | } 49 | ); 50 | 51 | server.tool( 52 | "getEconomicIndicators", 53 | "Access real-time and historical economic data for key indicators like GDP, unemployment, and inflation with the FMP Economic Indicators API. Use this data to measure economic performance and identify growth trends.", 54 | { 55 | name: z 56 | .string() 57 | .describe("Name of the indicator"), 58 | from: z 59 | .string() 60 | .optional() 61 | .describe("Optional start date (YYYY-MM-DD)"), 62 | to: z 63 | .string() 64 | .optional() 65 | .describe("Optional end date (YYYY-MM-DD)"), 66 | }, 67 | async ({ name, from, to }) => { 68 | try { 69 | const results = await economicsClient.getEconomicIndicators( 70 | name, 71 | from, 72 | to 73 | ); 74 | return { 75 | content: [{ type: "text", text: JSON.stringify(results, null, 2) }], 76 | }; 77 | } catch (error) { 78 | return { 79 | content: [ 80 | { 81 | type: "text", 82 | text: `Error: ${ 83 | error instanceof Error ? error.message : String(error) 84 | }`, 85 | }, 86 | ], 87 | isError: true, 88 | }; 89 | } 90 | } 91 | ); 92 | 93 | server.tool( 94 | "getEconomicCalendar", 95 | "Stay informed with the FMP Economic Data Releases Calendar API. Access a comprehensive calendar of upcoming economic data releases to prepare for market impacts and make informed investment decisions.", 96 | { 97 | from: z 98 | .string() 99 | .optional() 100 | .describe("Optional start date (YYYY-MM-DD)"), 101 | to: z 102 | .string() 103 | .optional() 104 | .describe("Optional end date (YYYY-MM-DD)"), 105 | }, 106 | async ({ from, to }) => { 107 | try { 108 | const results = await economicsClient.getEconomicCalendar(from, to); 109 | return { 110 | content: [{ type: "text", text: JSON.stringify(results, null, 2) }], 111 | }; 112 | } catch (error) { 113 | return { 114 | content: [ 115 | { 116 | type: "text", 117 | text: `Error: ${ 118 | error instanceof Error ? error.message : String(error) 119 | }`, 120 | }, 121 | ], 122 | isError: true, 123 | }; 124 | } 125 | } 126 | ); 127 | 128 | server.tool( 129 | "getMarketRiskPremium", 130 | "Access the market risk premium for specific dates with the FMP Market Risk Premium API. Use this key financial metric to assess the additional return expected from investing in the stock market over a risk-free investment.", 131 | {}, 132 | async () => { 133 | try { 134 | const results = await economicsClient.getMarketRiskPremium(); 135 | return { 136 | content: [{ type: "text", text: JSON.stringify(results, null, 2) }], 137 | }; 138 | } catch (error) { 139 | return { 140 | content: [ 141 | { 142 | type: "text", 143 | text: `Error: ${ 144 | error instanceof Error ? error.message : String(error) 145 | }`, 146 | }, 147 | ], 148 | isError: true, 149 | }; 150 | } 151 | } 152 | ); 153 | } 154 | -------------------------------------------------------------------------------- /src/api/search/SearchClient.ts: -------------------------------------------------------------------------------- 1 | import { FMPClient } from "../FMPClient.js"; 2 | import type { FMPContext } from "../../types/index.js"; 3 | import type { 4 | SymbolSearchResult, 5 | NameSearchResult, 6 | CIKSearchResult, 7 | CUSIPSearchResult, 8 | ISINSearchResult, 9 | StockScreenerResult, 10 | ExchangeVariantResult, 11 | } from "./types.js"; 12 | 13 | export class SearchClient extends FMPClient { 14 | constructor(apiKey?: string) { 15 | super(apiKey); 16 | } 17 | 18 | /** 19 | * Search for stock symbols by query 20 | * @param query The search query 21 | * @param limit Optional limit on number of results (default: 50) 22 | * @param exchange Optional exchange filter 23 | * @param context Optional context containing configuration 24 | * @returns Array of matching symbols 25 | */ 26 | async searchSymbol( 27 | query: string, 28 | limit?: number, 29 | exchange?: string, 30 | context?: FMPContext 31 | ): Promise { 32 | return super.get( 33 | "/search-symbol", 34 | { 35 | query, 36 | limit, 37 | exchange, 38 | }, 39 | { context } 40 | ); 41 | } 42 | 43 | /** 44 | * Search for company names by query 45 | * @param query The search query 46 | * @param limit Optional limit on number of results (default: 50) 47 | * @param exchange Optional exchange filter 48 | * @param context Optional context containing configuration 49 | * @returns Array of matching companies 50 | */ 51 | async searchName( 52 | query: string, 53 | limit?: number, 54 | exchange?: string, 55 | context?: FMPContext 56 | ): Promise { 57 | return super.get( 58 | "/search-name", 59 | { 60 | query, 61 | limit, 62 | exchange, 63 | }, 64 | { context } 65 | ); 66 | } 67 | 68 | /** 69 | * Search for companies by CIK number 70 | * @param cik The CIK number to search for 71 | * @param limit Optional limit on number of results (default: 50) 72 | * @param context Optional context containing configuration 73 | * @returns Array of matching companies 74 | */ 75 | async searchCIK( 76 | cik: string, 77 | limit?: number, 78 | context?: FMPContext 79 | ): Promise { 80 | return super.get( 81 | "/search-cik", 82 | { cik, limit }, 83 | { context } 84 | ); 85 | } 86 | 87 | /** 88 | * Search for securities by CUSIP number 89 | * @param cusip The CUSIP number to search for 90 | * @param context Optional context containing configuration 91 | * @returns Array of matching securities 92 | */ 93 | async searchCUSIP( 94 | cusip: string, 95 | context?: FMPContext 96 | ): Promise { 97 | return super.get( 98 | "/search-cusip", 99 | { cusip }, 100 | { context } 101 | ); 102 | } 103 | 104 | /** 105 | * Search for securities by ISIN number 106 | * @param isin The ISIN number to search for 107 | * @param context Optional context containing configuration 108 | * @returns Array of matching securities 109 | */ 110 | async searchISIN( 111 | isin: string, 112 | context?: FMPContext 113 | ): Promise { 114 | return super.get("/search-isin", { isin }, { context }); 115 | } 116 | 117 | /** 118 | * Search for stocks using various criteria 119 | * @param params Search criteria 120 | * @param context Optional context containing configuration 121 | * @returns Array of matching stocks 122 | */ 123 | async stockScreener( 124 | params: { 125 | marketCapMoreThan?: number; 126 | marketCapLowerThan?: number; 127 | sector?: string; 128 | industry?: string; 129 | betaMoreThan?: number; 130 | betaLowerThan?: number; 131 | priceMoreThan?: number; 132 | priceLowerThan?: number; 133 | dividendMoreThan?: number; 134 | dividendLowerThan?: number; 135 | volumeMoreThan?: number; 136 | volumeLowerThan?: number; 137 | exchange?: string; 138 | country?: string; 139 | isEtf?: boolean; 140 | isFund?: boolean; 141 | isActivelyTrading?: boolean; 142 | limit?: number; 143 | includeAllShareClasses?: boolean; 144 | }, 145 | context?: FMPContext 146 | ): Promise { 147 | return super.get("/company-screener", params, { 148 | context, 149 | }); 150 | } 151 | 152 | /** 153 | * Search for exchange variants of a symbol 154 | * @param symbol The stock symbol to search for 155 | * @param context Optional context containing configuration 156 | * @returns Array of exchange variants 157 | */ 158 | async searchExchangeVariants( 159 | symbol: string, 160 | context?: FMPContext 161 | ): Promise { 162 | return super.get( 163 | "/search-exchange-variants", 164 | { 165 | symbol, 166 | }, 167 | { context } 168 | ); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/tools/earnings-transcript.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { EarningsTranscriptClient } from "../api/earnings-transcript/EarningsTranscriptClient.js"; 3 | import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 4 | 5 | /** 6 | * Register all earnings transcript-related tools with the MCP server 7 | * @param server The MCP server instance 8 | * @param accessToken The Financial Modeling Prep API access token (optional when using lazy loading) 9 | */ 10 | export function registerEarningsTranscriptTools( 11 | server: McpServer, 12 | accessToken?: string 13 | ): void { 14 | const earningsTranscriptClient = new EarningsTranscriptClient(accessToken); 15 | 16 | server.tool( 17 | "getLatestEarningsTranscripts", 18 | "Access available earnings transcripts for companies with the FMP Latest Earning Transcripts API. Retrieve a list of companies with earnings transcripts, along with the total number of transcripts available for each company.", 19 | { 20 | limit: z.number().optional().describe("Limit the number of results"), 21 | page: z.number().optional().describe("Page number for pagination"), 22 | }, 23 | async ({ limit, page }) => { 24 | try { 25 | const results = await earningsTranscriptClient.getLatestTranscripts({ 26 | limit, 27 | page, 28 | }); 29 | return { 30 | content: [{ type: "text", text: JSON.stringify(results, null, 2) }], 31 | }; 32 | } catch (error) { 33 | return { 34 | content: [ 35 | { 36 | type: "text", 37 | text: `Error: ${ 38 | error instanceof Error ? error.message : String(error) 39 | }`, 40 | }, 41 | ], 42 | isError: true, 43 | }; 44 | } 45 | } 46 | ); 47 | 48 | server.tool( 49 | "getEarningsTranscript", 50 | "Access the full transcript of a company’s earnings call with the FMP Earnings Transcript API. Stay informed about a company’s financial performance, future plans, and overall strategy by analyzing management's communication.", 51 | { 52 | symbol: z.string().describe("Stock symbol"), 53 | year: z.string().describe("Year of the earnings call"), 54 | quarter: z 55 | .string() 56 | .describe("Quarter of the earnings call (e.g., 1, 2, 3, 4)"), 57 | limit: z.number().optional().describe("Limit the number of results"), 58 | }, 59 | async ({ symbol, year, quarter, limit }) => { 60 | try { 61 | const results = await earningsTranscriptClient.getTranscript({ 62 | symbol, 63 | year, 64 | quarter, 65 | limit, 66 | }); 67 | return { 68 | content: [{ type: "text", text: JSON.stringify(results, null, 2) }], 69 | }; 70 | } catch (error) { 71 | return { 72 | content: [ 73 | { 74 | type: "text", 75 | text: `Error: ${ 76 | error instanceof Error ? error.message : String(error) 77 | }`, 78 | }, 79 | ], 80 | isError: true, 81 | }; 82 | } 83 | } 84 | ); 85 | 86 | server.tool( 87 | "getEarningsTranscriptDates", 88 | "Access earnings call transcript dates for specific companies with the FMP Transcripts Dates By Symbol API. Get a comprehensive overview of earnings call schedules based on fiscal year and quarter.", 89 | { 90 | symbol: z.string().describe("Stock symbol"), 91 | }, 92 | async ({ symbol }) => { 93 | try { 94 | const results = await earningsTranscriptClient.getTranscriptDates({ 95 | symbol, 96 | }); 97 | return { 98 | content: [{ type: "text", text: JSON.stringify(results, null, 2) }], 99 | }; 100 | } catch (error) { 101 | return { 102 | content: [ 103 | { 104 | type: "text", 105 | text: `Error: ${ 106 | error instanceof Error ? error.message : String(error) 107 | }`, 108 | }, 109 | ], 110 | isError: true, 111 | }; 112 | } 113 | } 114 | ); 115 | 116 | server.tool( 117 | "getAvailableTranscriptSymbols", 118 | "Access a complete list of stock symbols with available earnings call transcripts using the FMP Available Earnings Transcript Symbols API. Retrieve information on which companies have earnings transcripts and how many are accessible for detailed financial analysis.", 119 | {}, 120 | async () => { 121 | try { 122 | const results = 123 | await earningsTranscriptClient.getAvailableTranscriptSymbols(); 124 | return { 125 | content: [{ type: "text", text: JSON.stringify(results, null, 2) }], 126 | }; 127 | } catch (error) { 128 | return { 129 | content: [ 130 | { 131 | type: "text", 132 | text: `Error: ${ 133 | error instanceof Error ? error.message : String(error) 134 | }`, 135 | }, 136 | ], 137 | isError: true, 138 | }; 139 | } 140 | } 141 | ); 142 | } 143 | -------------------------------------------------------------------------------- /src/api/FMPClient.ts: -------------------------------------------------------------------------------- 1 | import axios, { type AxiosInstance, type AxiosError, type AxiosRequestConfig } from "axios"; 2 | 3 | interface FMPErrorResponse { 4 | message: string; 5 | [key: string]: any; 6 | } 7 | 8 | export class FMPClient { 9 | private readonly apiKey?: string; 10 | private readonly baseUrl: string = "https://financialmodelingprep.com/stable"; 11 | private readonly client: AxiosInstance; 12 | 13 | constructor(apiKey?: string) { 14 | this.apiKey = apiKey; 15 | this.client = axios.create({ 16 | baseURL: this.baseUrl, 17 | }); 18 | } 19 | 20 | // Get the API key from the context or the instance 21 | private getApiKey(context?: { 22 | config?: { FMP_ACCESS_TOKEN?: string }; 23 | }): string { 24 | const configApiKey = context?.config?.FMP_ACCESS_TOKEN; 25 | 26 | if (configApiKey) { 27 | return configApiKey; 28 | } 29 | 30 | // Fall back to constructor parameter or environment variable 31 | const apiKey = this.apiKey || process.env.FMP_ACCESS_TOKEN; 32 | 33 | if (!apiKey) { 34 | throw new Error( 35 | "FMP_ACCESS_TOKEN is required for this operation. Please provide it in the configuration." 36 | ); 37 | } 38 | 39 | return apiKey; 40 | } 41 | 42 | protected async get( 43 | endpoint: string, 44 | params: Record = {}, 45 | options?: { 46 | signal?: AbortSignal; 47 | context?: { config?: { FMP_ACCESS_TOKEN?: string } }; 48 | } 49 | ): Promise { 50 | try { 51 | // Try to get API key from context first, fall back to instance API key 52 | const apiKey = this.getApiKey(options?.context); 53 | 54 | const config: AxiosRequestConfig = { 55 | params: { 56 | ...params, 57 | apikey: apiKey, 58 | }, 59 | }; 60 | 61 | if (options?.signal) { 62 | config.signal = options.signal; 63 | } 64 | 65 | const response = await this.client.get(endpoint, config); 66 | return response.data; 67 | } catch (error: unknown) { 68 | if (axios.isAxiosError(error)) { 69 | const axiosError = error as AxiosError; 70 | throw new Error( 71 | `FMP API Error: ${ 72 | axiosError.response?.data?.message || axiosError.message 73 | }` 74 | ); 75 | } 76 | throw new Error( 77 | `Unexpected error: ${ 78 | error instanceof Error ? error.message : String(error) 79 | }` 80 | ); 81 | } 82 | } 83 | 84 | protected async getCSV( 85 | endpoint: string, 86 | params: Record = {}, 87 | options?: { 88 | signal?: AbortSignal; 89 | context?: { config?: { FMP_ACCESS_TOKEN?: string } }; 90 | } 91 | ): Promise { 92 | try { 93 | // Try to get API key from context first, fall back to instance API key 94 | const apiKey = this.getApiKey(options?.context); 95 | 96 | const config: AxiosRequestConfig = { 97 | params: { 98 | ...params, 99 | apikey: apiKey, 100 | }, 101 | responseType: 'text', // Important: get response as text for CSV 102 | }; 103 | 104 | if (options?.signal) { 105 | config.signal = options.signal; 106 | } 107 | 108 | const response = await this.client.get(endpoint, config); 109 | return response.data; 110 | } catch (error: unknown) { 111 | if (axios.isAxiosError(error)) { 112 | const axiosError = error as AxiosError; 113 | throw new Error( 114 | `FMP API Error: ${ 115 | axiosError.response?.data?.message || axiosError.message 116 | }` 117 | ); 118 | } 119 | throw new Error( 120 | `Unexpected error: ${ 121 | error instanceof Error ? error.message : String(error) 122 | }` 123 | ); 124 | } 125 | } 126 | 127 | protected async post( 128 | endpoint: string, 129 | data: any, 130 | params: Record = {}, 131 | options?: { 132 | signal?: AbortSignal; 133 | context?: { config?: { FMP_ACCESS_TOKEN?: string } }; 134 | } 135 | ): Promise { 136 | try { 137 | // Try to get API key from context first, fall back to instance API key 138 | const apiKey = this.getApiKey(options?.context); 139 | 140 | const config: AxiosRequestConfig = { 141 | params: { 142 | ...params, 143 | apikey: apiKey, 144 | }, 145 | }; 146 | 147 | if (options?.signal) { 148 | config.signal = options.signal; 149 | } 150 | 151 | const response = await this.client.post(endpoint, data, config); 152 | return response.data; 153 | } catch (error: unknown) { 154 | if (axios.isAxiosError(error)) { 155 | const axiosError = error as AxiosError; 156 | throw new Error( 157 | `FMP API Error: ${ 158 | axiosError.response?.data?.message || axiosError.message 159 | }` 160 | ); 161 | } 162 | throw new Error( 163 | `Unexpected error: ${ 164 | error instanceof Error ? error.message : String(error) 165 | }` 166 | ); 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/api/news/NewsClient.ts: -------------------------------------------------------------------------------- 1 | import { FMPClient } from "../FMPClient.js"; 2 | import type { FMPContext } from "../../types/index.js"; 3 | import type { 4 | FMPArticle, 5 | NewsArticle, 6 | NewsParams, 7 | NewsSearchParams, 8 | } from "./types.js"; 9 | 10 | export class NewsClient extends FMPClient { 11 | constructor(apiKey?: string) { 12 | super(apiKey); 13 | } 14 | 15 | /** 16 | * Get articles from Financial Modeling Prep 17 | * @param params Optional pagination parameters 18 | * @param options Optional parameters including abort signal and context 19 | */ 20 | async getFMPArticles( 21 | params: { page?: number; limit?: number } = {}, 22 | options?: { 23 | signal?: AbortSignal; 24 | context?: FMPContext; 25 | } 26 | ): Promise { 27 | return super.get("/fmp-articles", params, options); 28 | } 29 | 30 | /** 31 | * Get general news 32 | * @param params Optional parameters for filtering news 33 | * @param options Optional parameters including abort signal and context 34 | */ 35 | async getGeneralNews( 36 | params: NewsParams = {}, 37 | options?: { 38 | signal?: AbortSignal; 39 | context?: FMPContext; 40 | } 41 | ): Promise { 42 | return super.get("/news/general-latest", params, options); 43 | } 44 | 45 | /** 46 | * Get press releases 47 | * @param params Optional parameters for filtering press releases 48 | * @param options Optional parameters including abort signal and context 49 | */ 50 | async getPressReleases( 51 | params: NewsParams = {}, 52 | options?: { 53 | signal?: AbortSignal; 54 | context?: FMPContext; 55 | } 56 | ): Promise { 57 | return super.get( 58 | "/news/press-releases-latest", 59 | params, 60 | options 61 | ); 62 | } 63 | 64 | /** 65 | * Get stock news 66 | * @param params Optional parameters for filtering stock news 67 | * @param options Optional parameters including abort signal and context 68 | */ 69 | async getStockNews( 70 | params: NewsParams = {}, 71 | options?: { 72 | signal?: AbortSignal; 73 | context?: FMPContext; 74 | } 75 | ): Promise { 76 | return super.get("/news/stock-latest", params, options); 77 | } 78 | 79 | /** 80 | * Get crypto news 81 | * @param params Optional parameters for filtering crypto news 82 | * @param options Optional parameters including abort signal and context 83 | */ 84 | async getCryptoNews( 85 | params: NewsParams = {}, 86 | options?: { 87 | signal?: AbortSignal; 88 | context?: FMPContext; 89 | } 90 | ): Promise { 91 | return super.get("/news/crypto-latest", params, options); 92 | } 93 | 94 | /** 95 | * Get forex news 96 | * @param params Optional parameters for filtering forex news 97 | * @param options Optional parameters including abort signal and context 98 | */ 99 | async getForexNews( 100 | params: NewsParams = {}, 101 | options?: { 102 | signal?: AbortSignal; 103 | context?: FMPContext; 104 | } 105 | ): Promise { 106 | return super.get("/news/forex-latest", params, options); 107 | } 108 | 109 | /** 110 | * Search press releases by symbols 111 | * @param params Search parameters for press releases 112 | * @param options Optional parameters including abort signal and context 113 | */ 114 | async searchPressReleases( 115 | params: NewsSearchParams, 116 | options?: { 117 | signal?: AbortSignal; 118 | context?: FMPContext; 119 | } 120 | ): Promise { 121 | return super.get("/news/press-releases", params, options); 122 | } 123 | 124 | /** 125 | * Search stock news by symbols 126 | * @param params Search parameters for stock news 127 | * @param options Optional parameters including abort signal and context 128 | */ 129 | async searchStockNews( 130 | params: NewsSearchParams, 131 | options?: { 132 | signal?: AbortSignal; 133 | context?: FMPContext; 134 | } 135 | ): Promise { 136 | return super.get("/news/stock", params, options); 137 | } 138 | 139 | /** 140 | * Search crypto news by symbols 141 | * @param params Search parameters for crypto news 142 | * @param options Optional parameters including abort signal and context 143 | */ 144 | async searchCryptoNews( 145 | params: NewsSearchParams, 146 | options?: { 147 | signal?: AbortSignal; 148 | context?: FMPContext; 149 | } 150 | ): Promise { 151 | return super.get("/news/crypto", params, options); 152 | } 153 | 154 | /** 155 | * Search forex news by symbols 156 | * @param params Search parameters for forex news 157 | * @param options Optional parameters including abort signal and context 158 | */ 159 | async searchForexNews( 160 | params: NewsSearchParams, 161 | options?: { 162 | signal?: AbortSignal; 163 | context?: FMPContext; 164 | } 165 | ): Promise { 166 | return super.get("/news/forex", params, options); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /.github/RELEASE.md: -------------------------------------------------------------------------------- 1 | # Admin: Release Process 2 | 3 | This document is intended for repo admins and maintainers. 4 | 5 | ## GitHub Release 6 | 7 | We create GitHub Releases automatically when pushing a tag that starts with `v`. 8 | 9 | - No build or publish is performed in this workflow. 10 | - CI builds already run on `main` for pushes and PRs. 11 | - The job only creates a GitHub Release. 12 | 13 | ### Release notes generation 14 | 15 | Our workflow uses `softprops/action-gh-release` with `generate_release_notes: true`. 16 | 17 | This means: 18 | 19 | - GitHub auto-generates release notes from commits/PRs since the last tag. 20 | - Quality of notes depends on commit messages and PR titles. Prefer Conventional Commits (e.g., `feat: ...`, `fix: ...`). 21 | - You can edit the generated release notes in the GitHub UI after the release is created. 22 | 23 | Optional ways to influence notes: 24 | 25 | - Create an annotated tag with a meaningful message, e.g.: 26 | ```bash 27 | git tag -a v1.2.0 -m "feat: dynamic toolsets\nfix: retry token resolution\nchore: docs updates" 28 | git push origin v1.2.0 29 | ``` 30 | The generated notes will still be created by GitHub, but the tag message is shown in the release and can provide extra context. 31 | 32 | ### Recommended Release Flow 33 | 34 | ```bash 35 | # 1) Ensure you are on an up-to-date main 36 | git checkout main && git pull 37 | 38 | # 2) Run local verification (optional but recommended) 39 | npm run verify:npm-ready 40 | 41 | # 3) Bump version and create a tag (choose one) 42 | npm version patch -m "release: v%s" 43 | # or 44 | npm version minor -m "release: v%s" 45 | # or 46 | npm version major -m "release: v%s" 47 | 48 | # 4) Push commit and tags to trigger the Release and NPM Publish workflow 49 | git push && git push --tags 50 | 51 | # Alternative (manual tag with a custom message): 52 | # git tag -a v1.1.0 -m "feat: X\nfix: Y\nchore: Z" 53 | # git push origin v1.1.0 54 | ``` 55 | 56 | ### What Happens After Pushing a Tag 57 | 58 | 1. **GitHub Actions workflow triggers** (`Create Release and Publish to NPM`) 59 | 2. **Package verification** ensures NPM readiness (includes build) 60 | 3. **NPM publication** publishes to the public registry 61 | 4. **GitHub Release** is created with auto-generated notes 62 | 63 | **Note**: Tests are not run since tags are created from main where CI already validates the code. 64 | 65 | ### Monitoring the Release 66 | 67 | - Check the **Actions** tab in GitHub to monitor workflow progress 68 | - Verify NPM publication at: `https://www.npmjs.com/package/financial-modeling-prep-mcp-server` 69 | - Check the **Releases** page for the GitHub release 70 | 71 | Notes: 72 | 73 | - Create tags from commits on `main` only. 74 | - Release notes are auto-generated from commits/PRs since the last tag. 75 | - After the release is created, you may edit the notes in the GitHub UI if needed. 76 | 77 | ## NPM Publish 78 | 79 | The package is automatically published to NPM as part of the GitHub Release workflow. 80 | 81 | ### Automated Publishing Process 82 | 83 | When a tag starting with `v` is pushed, the workflow: 84 | 85 | 1. **Verifies NPM readiness** using our custom verification script (includes build) 86 | 2. **Publishes to NPM** using the `NPM_TOKEN` secret 87 | 3. **Creates a GitHub Release** with auto-generated notes 88 | 89 | **Note**: Tests are not run in the release workflow since tags are created from main where CI already runs tests. 90 | 91 | ### NPM Token Setup 92 | 93 | The workflow requires an `NPM_TOKEN` secret to be configured in the repository. 94 | 95 | **Quick setup:** 96 | 97 | 1. Run: `npm token create --type=automation` 98 | 2. Copy the generated token 99 | 3. Add it as `NPM_TOKEN` secret in GitHub repository settings 100 | 101 | **Detailed instructions:** See [scripts/setup-npm-token.md](../scripts/setup-npm-token.md) 102 | 103 | ### Publishing Verification 104 | 105 | The workflow includes several verification steps: 106 | 107 | - Runs all tests (`npm run test:run`) 108 | - Verifies package configuration (`npm run verify:npm-ready`) 109 | - Ensures build succeeds before publishing 110 | - Publishes with `--access public` for scoped packages 111 | 112 | ### Manual Override 113 | 114 | If you need to publish manually (e.g., for hotfixes): 115 | 116 | ```bash 117 | # Ensure you're logged in to NPM 118 | npm login 119 | 120 | # Verify readiness 121 | npm run verify:npm-ready 122 | 123 | # Publish manually 124 | npm publish --access public 125 | ``` 126 | 127 | ### Troubleshooting 128 | 129 | **NPM Token Issues:** 130 | 131 | - Ensure `NPM_TOKEN` secret is set in repository settings 132 | - Token must have publish permissions for the package 133 | - Use automation tokens for CI/CD workflows 134 | 135 | **Version Conflicts:** 136 | 137 | - If version already exists on NPM, bump version and create new tag 138 | - Check existing versions: `npm view financial-modeling-prep-mcp-server versions --json` 139 | 140 | **Build Failures:** 141 | 142 | - Workflow will fail if `verify:npm-ready` script fails (includes build verification) 143 | - Check Actions logs for detailed error messages 144 | - Tests are not run in release workflow (they run in CI on main) 145 | 146 | **Permission Issues:** 147 | 148 | - Ensure NPM account has publish permissions for the package 149 | - For first-time publishing, package name must be available 150 | -------------------------------------------------------------------------------- /src/api/cot/types.ts: -------------------------------------------------------------------------------- 1 | export interface COTReport { 2 | symbol: string; 3 | date: string; 4 | name: string; 5 | sector: string; 6 | marketAndExchangeNames: string; 7 | cftcContractMarketCode: string; 8 | cftcMarketCode: string; 9 | cftcRegionCode: string; 10 | cftcCommodityCode: string; 11 | openInterestAll: number; 12 | noncommPositionsLongAll: number; 13 | noncommPositionsShortAll: number; 14 | noncommPositionsSpreadAll: number; 15 | commPositionsLongAll: number; 16 | commPositionsShortAll: number; 17 | totReptPositionsLongAll: number; 18 | totReptPositionsShortAll: number; 19 | nonreptPositionsLongAll: number; 20 | nonreptPositionsShortAll: number; 21 | openInterestOld: number; 22 | noncommPositionsLongOld: number; 23 | noncommPositionsShortOld: number; 24 | noncommPositionsSpreadOld: number; 25 | commPositionsLongOld: number; 26 | commPositionsShortOld: number; 27 | totReptPositionsLongOld: number; 28 | totReptPositionsShortOld: number; 29 | nonreptPositionsLongOld: number; 30 | nonreptPositionsShortOld: number; 31 | openInterestOther: number; 32 | noncommPositionsLongOther: number; 33 | noncommPositionsShortOther: number; 34 | noncommPositionsSpreadOther: number; 35 | commPositionsLongOther: number; 36 | commPositionsShortOther: number; 37 | totReptPositionsLongOther: number; 38 | totReptPositionsShortOther: number; 39 | nonreptPositionsLongOther: number; 40 | nonreptPositionsShortOther: number; 41 | changeInOpenInterestAll: number; 42 | changeInNoncommLongAll: number; 43 | changeInNoncommShortAll: number; 44 | changeInNoncommSpeadAll: number; 45 | changeInCommLongAll: number; 46 | changeInCommShortAll: number; 47 | changeInTotReptLongAll: number; 48 | changeInTotReptShortAll: number; 49 | changeInNonreptLongAll: number; 50 | changeInNonreptShortAll: number; 51 | pctOfOpenInterestAll: number; 52 | pctOfOiNoncommLongAll: number; 53 | pctOfOiNoncommShortAll: number; 54 | pctOfOiNoncommSpreadAll: number; 55 | pctOfOiCommLongAll: number; 56 | pctOfOiCommShortAll: number; 57 | pctOfOiTotReptLongAll: number; 58 | pctOfOiTotReptShortAll: number; 59 | pctOfOiNonreptLongAll: number; 60 | pctOfOiNonreptShortAll: number; 61 | pctOfOpenInterestOl: number; 62 | pctOfOiNoncommLongOl: number; 63 | pctOfOiNoncommShortOl: number; 64 | pctOfOiNoncommSpreadOl: number; 65 | pctOfOiCommLongOl: number; 66 | pctOfOiCommShortOl: number; 67 | pctOfOiTotReptLongOl: number; 68 | pctOfOiTotReptShortOl: number; 69 | pctOfOiNonreptLongOl: number; 70 | pctOfOiNonreptShortOl: number; 71 | pctOfOpenInterestOther: number; 72 | pctOfOiNoncommLongOther: number; 73 | pctOfOiNoncommShortOther: number; 74 | pctOfOiNoncommSpreadOther: number; 75 | pctOfOiCommLongOther: number; 76 | pctOfOiCommShortOther: number; 77 | pctOfOiTotReptLongOther: number; 78 | pctOfOiTotReptShortOther: number; 79 | pctOfOiNonreptLongOther: number; 80 | pctOfOiNonreptShortOther: number; 81 | tradersTotAll: number; 82 | tradersNoncommLongAll: number; 83 | tradersNoncommShortAll: number; 84 | tradersNoncommSpreadAll: number; 85 | tradersCommLongAll: number; 86 | tradersCommShortAll: number; 87 | tradersTotReptLongAll: number; 88 | tradersTotReptShortAll: number; 89 | tradersTotOl: number; 90 | tradersNoncommLongOl: number; 91 | tradersNoncommShortOl: number; 92 | tradersNoncommSpeadOl: number; 93 | tradersCommLongOl: number; 94 | tradersCommShortOl: number; 95 | tradersTotReptLongOl: number; 96 | tradersTotReptShortOl: number; 97 | tradersTotOther: number; 98 | tradersNoncommLongOther: number; 99 | tradersNoncommShortOther: number; 100 | tradersNoncommSpreadOther: number; 101 | tradersCommLongOther: number; 102 | tradersCommShortOther: number; 103 | tradersTotReptLongOther: number; 104 | tradersTotReptShortOther: number; 105 | concGrossLe4TdrLongAll: number; 106 | concGrossLe4TdrShortAll: number; 107 | concGrossLe8TdrLongAll: number; 108 | concGrossLe8TdrShortAll: number; 109 | concNetLe4TdrLongAll: number; 110 | concNetLe4TdrShortAll: number; 111 | concNetLe8TdrLongAll: number; 112 | concNetLe8TdrShortAll: number; 113 | concGrossLe4TdrLongOl: number; 114 | concGrossLe4TdrShortOl: number; 115 | concGrossLe8TdrLongOl: number; 116 | concGrossLe8TdrShortOl: number; 117 | concNetLe4TdrLongOl: number; 118 | concNetLe4TdrShortOl: number; 119 | concNetLe8TdrLongOl: number; 120 | concNetLe8TdrShortOl: number; 121 | concGrossLe4TdrLongOther: number; 122 | concGrossLe4TdrShortOther: number; 123 | concGrossLe8TdrLongOther: number; 124 | concGrossLe8TdrShortOther: number; 125 | concNetLe4TdrLongOther: number; 126 | concNetLe4TdrShortOther: number; 127 | concNetLe8TdrLongOther: number; 128 | concNetLe8TdrShortOther: number; 129 | contractUnits: string; 130 | } 131 | 132 | export interface COTAnalysis { 133 | symbol: string; 134 | date: string; 135 | name: string; 136 | sector: string; 137 | exchange: string; 138 | currentLongMarketSituation: number; 139 | currentShortMarketSituation: number; 140 | marketSituation: string; 141 | previousLongMarketSituation: number; 142 | previousShortMarketSituation: number; 143 | previousMarketSituation: string; 144 | netPostion: number; 145 | previousNetPosition: number; 146 | changeInNetPosition: number; 147 | marketSentiment: string; 148 | reversalTrend: boolean; 149 | } 150 | 151 | export interface COTList { 152 | symbol: string; 153 | name: string; 154 | } 155 | -------------------------------------------------------------------------------- /src/api/form-13f/types.ts: -------------------------------------------------------------------------------- 1 | // Institutional Ownership Filings API types 2 | export interface InstitutionalOwnershipFiling { 3 | cik: string; 4 | name: string; 5 | date: string; 6 | filingDate: string; 7 | acceptedDate: string; 8 | formType: string; 9 | link: string; 10 | finalLink: string; 11 | } 12 | 13 | // SEC Filings Extract API types 14 | export interface SecFilingExtract { 15 | date: string; 16 | filingDate: string; 17 | acceptedDate: string; 18 | cik: string; 19 | securityCusip: string; 20 | symbol: string; 21 | nameOfIssuer: string; 22 | shares: number; 23 | titleOfClass: string; 24 | sharesType: string; 25 | putCallShare: string; 26 | value: number; 27 | link: string; 28 | finalLink: string; 29 | } 30 | 31 | // Form 13F Filings Dates API types 32 | export interface Form13FFilingDate { 33 | date: string; 34 | year: number; 35 | quarter: number; 36 | } 37 | 38 | // Filings Extract With Analytics By Holder API types 39 | export interface FilingExtractAnalytics { 40 | date: string; 41 | cik: string; 42 | filingDate: string; 43 | investorName: string; 44 | symbol: string; 45 | securityName: string; 46 | typeOfSecurity: string; 47 | securityCusip: string; 48 | sharesType: string; 49 | putCallShare: string; 50 | investmentDiscretion: string; 51 | industryTitle: string; 52 | weight: number; 53 | lastWeight: number; 54 | changeInWeight: number; 55 | changeInWeightPercentage: number; 56 | marketValue: number; 57 | lastMarketValue: number; 58 | changeInMarketValue: number; 59 | changeInMarketValuePercentage: number; 60 | sharesNumber: number; 61 | lastSharesNumber: number; 62 | changeInSharesNumber: number; 63 | changeInSharesNumberPercentage: number; 64 | quarterEndPrice: number; 65 | avgPricePaid: number; 66 | isNew: boolean; 67 | isSoldOut: boolean; 68 | ownership: number; 69 | lastOwnership: number; 70 | changeInOwnership: number; 71 | changeInOwnershipPercentage: number; 72 | holdingPeriod: number; 73 | firstAdded: string; 74 | performance: number; 75 | performancePercentage: number; 76 | lastPerformance: number; 77 | changeInPerformance: number; 78 | isCountedForPerformance: boolean; 79 | } 80 | 81 | // Holder Performance Summary API types 82 | export interface HolderPerformanceSummary { 83 | date: string; 84 | cik: string; 85 | investorName: string; 86 | portfolioSize: number; 87 | securitiesAdded: number; 88 | securitiesRemoved: number; 89 | marketValue: number; 90 | previousMarketValue: number; 91 | changeInMarketValue: number; 92 | changeInMarketValuePercentage: number; 93 | averageHoldingPeriod: number; 94 | averageHoldingPeriodTop10: number; 95 | averageHoldingPeriodTop20: number; 96 | turnover: number; 97 | turnoverAlternateSell: number; 98 | turnoverAlternateBuy: number; 99 | performance: number; 100 | performancePercentage: number; 101 | lastPerformance: number; 102 | changeInPerformance: number; 103 | performance1year: number; 104 | performancePercentage1year: number; 105 | performance3year: number; 106 | performancePercentage3year: number; 107 | performance5year: number; 108 | performancePercentage5year: number; 109 | performanceSinceInception: number; 110 | performanceSinceInceptionPercentage: number; 111 | performanceRelativeToSP500Percentage: number; 112 | performance1yearRelativeToSP500Percentage: number; 113 | performance3yearRelativeToSP500Percentage: number; 114 | performance5yearRelativeToSP500Percentage: number; 115 | performanceSinceInceptionRelativeToSP500Percentage: number; 116 | } 117 | 118 | // Holders Industry Breakdown API types 119 | export interface HolderIndustryBreakdown { 120 | date: string; 121 | cik: string; 122 | investorName: string; 123 | industryTitle: string; 124 | weight: number; 125 | lastWeight: number; 126 | changeInWeight: number; 127 | changeInWeightPercentage: number; 128 | performance: number; 129 | performancePercentage: number; 130 | lastPerformance: number; 131 | changeInPerformance: number; 132 | } 133 | 134 | // Positions Summary API types 135 | export interface PositionsSummary { 136 | symbol: string; 137 | cik: string; 138 | date: string; 139 | investorsHolding: number; 140 | lastInvestorsHolding: number; 141 | investorsHoldingChange: number; 142 | numberOf13Fshares: number; 143 | lastNumberOf13Fshares: number; 144 | numberOf13FsharesChange: number; 145 | totalInvested: number; 146 | lastTotalInvested: number; 147 | totalInvestedChange: number; 148 | ownershipPercent: number; 149 | lastOwnershipPercent: number; 150 | ownershipPercentChange: number; 151 | newPositions: number; 152 | lastNewPositions: number; 153 | newPositionsChange: number; 154 | increasedPositions: number; 155 | lastIncreasedPositions: number; 156 | increasedPositionsChange: number; 157 | closedPositions: number; 158 | lastClosedPositions: number; 159 | closedPositionsChange: number; 160 | reducedPositions: number; 161 | lastReducedPositions: number; 162 | reducedPositionsChange: number; 163 | totalCalls: number; 164 | lastTotalCalls: number; 165 | totalCallsChange: number; 166 | totalPuts: number; 167 | lastTotalPuts: number; 168 | totalPutsChange: number; 169 | putCallRatio: number; 170 | lastPutCallRatio: number; 171 | putCallRatioChange: number; 172 | } 173 | 174 | // Industry Performance Summary API types 175 | export interface IndustryPerformanceSummary { 176 | industryTitle: string; 177 | industryValue: number; 178 | date: string; 179 | } 180 | -------------------------------------------------------------------------------- /src/client-storage/ClientStorage.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; 2 | import { ClientStorage, type StorageOptions } from "./ClientStorage.js"; 3 | import type { ServerMode } from "../types/index.js"; 4 | 5 | vi.mock("@modelcontextprotocol/sdk/server/mcp.js", () => ({ 6 | McpServer: vi.fn(), 7 | })); 8 | 9 | vi.mock("../dynamic-toolset-manager/DynamicToolsetManager.js", () => ({ 10 | DynamicToolsetManager: vi.fn(), 11 | })); 12 | 13 | const mockConsoleLog = vi.fn(); 14 | const mockConsoleError = vi.fn(); 15 | 16 | describe("ClientStorage", () => { 17 | let storage: ClientStorage; 18 | let mockMcpServer: any; 19 | let mockToolManager: any; 20 | 21 | beforeEach(() => { 22 | vi.clearAllMocks(); 23 | vi.spyOn(console, "log").mockImplementation(mockConsoleLog); 24 | vi.spyOn(console, "error").mockImplementation(mockConsoleError); 25 | 26 | mockMcpServer = { close: vi.fn() }; 27 | mockToolManager = { cleanup: vi.fn() }; 28 | }); 29 | 30 | afterEach(() => { 31 | if (storage) storage.stop(); 32 | vi.restoreAllMocks(); 33 | }); 34 | 35 | describe("Constructor and Configuration", () => { 36 | it("should create storage with default options", () => { 37 | storage = new ClientStorage(); 38 | expect(storage).toBeInstanceOf(ClientStorage); 39 | expect(typeof storage.getEntryCount()).toBe("number"); 40 | }); 41 | 42 | it("should create storage with custom options", () => { 43 | const options: StorageOptions = { maxSize: 50, ttl: 60000 }; 44 | storage = new ClientStorage(options); 45 | expect(storage.getMaxSize()).toBe(50); 46 | expect(storage.getTtl()).toBe(60000); 47 | }); 48 | 49 | it("should start pruning interval on construction", () => { 50 | const setIntervalSpy = vi.spyOn(global, "setInterval"); 51 | storage = new ClientStorage(); 52 | expect(setIntervalSpy).toHaveBeenCalledWith(expect.any(Function), 1000 * 60 * 10); 53 | }); 54 | }); 55 | 56 | describe("Basic Operations", () => { 57 | beforeEach(() => { 58 | storage = new ClientStorage(); 59 | }); 60 | 61 | it("should set and get a client entry", () => { 62 | const clientId = "client-1"; 63 | const data = { mcpServer: mockMcpServer, toolManager: mockToolManager, mode: 'ALL_TOOLS' as ServerMode, staticToolSets: [] }; 64 | storage.set(clientId, data); 65 | const result = storage.get(clientId); 66 | expect(result).toEqual(data); 67 | }); 68 | 69 | it("should return null for non-existent client", () => { 70 | expect(storage.get("missing")).toBeNull(); 71 | }); 72 | 73 | it("should delete a client entry", () => { 74 | const clientId = "client-1"; 75 | const data = { mcpServer: mockMcpServer, mode: 'ALL_TOOLS' as ServerMode, staticToolSets: [] }; 76 | storage.set(clientId, data); 77 | storage.delete(clientId); 78 | expect(storage.get(clientId)).toBeNull(); 79 | }); 80 | }); 81 | 82 | describe("LRU and TTL", () => { 83 | it("should evict least recently used when max size reached", () => { 84 | const options: StorageOptions = { maxSize: 2 }; 85 | storage = new ClientStorage(options); 86 | 87 | storage.set("c1", { mcpServer: mockMcpServer, mode: 'ALL_TOOLS' as ServerMode, staticToolSets: [] }); 88 | storage.set("c2", { mcpServer: mockMcpServer, mode: 'ALL_TOOLS' as ServerMode, staticToolSets: [] }); 89 | storage.set("c3", { mcpServer: mockMcpServer, mode: 'ALL_TOOLS' as ServerMode, staticToolSets: [] }); 90 | 91 | expect(storage.get("c1")).toBeNull(); 92 | expect(storage.get("c2")).toBeDefined(); 93 | expect(storage.get("c3")).toBeDefined(); 94 | expect(mockConsoleLog).toHaveBeenCalledWith( 95 | "[ClientStorage] Max size reached. Evicting least recently used client: c1" 96 | ); 97 | }); 98 | 99 | it("should expire entries based on TTL", () => { 100 | vi.useFakeTimers(); 101 | const options: StorageOptions = { ttl: 1000 * 60 * 5 }; 102 | storage = new ClientStorage(options); 103 | 104 | storage.set("c1", { mcpServer: mockMcpServer, mode: 'ALL_TOOLS' as ServerMode, staticToolSets: [] }); 105 | vi.advanceTimersByTime(1000 * 60 * 6); 106 | expect(storage.get("c1")).toBeNull(); 107 | expect(mockConsoleLog).toHaveBeenCalledWith( 108 | "[ClientStorage] Client c1 expired. Deleting." 109 | ); 110 | vi.useRealTimers(); 111 | }); 112 | 113 | it("should prune expired entries", () => { 114 | vi.useFakeTimers(); 115 | const options: StorageOptions = { ttl: 1000 * 60 * 5 }; 116 | storage = new ClientStorage(options); 117 | 118 | storage.set("c1", { mcpServer: mockMcpServer, mode: 'ALL_TOOLS' as ServerMode, staticToolSets: [] }); 119 | vi.advanceTimersByTime(1000 * 60 * 6); 120 | 121 | // @ts-ignore access private for test via as any 122 | (storage as any).pruneExpired(); 123 | 124 | expect(storage.get("c1")).toBeNull(); 125 | expect(mockConsoleLog).toHaveBeenCalledWith("[ClientStorage] Pruning expired clients..."); 126 | expect(mockConsoleLog).toHaveBeenCalledWith("[ClientStorage] Pruning expired client: c1"); 127 | vi.useRealTimers(); 128 | }); 129 | }); 130 | 131 | describe("Resource Management", () => { 132 | it("should stop the prune interval on stop()", () => { 133 | const clearIntervalSpy = vi.spyOn(global, "clearInterval"); 134 | storage = new ClientStorage(); 135 | storage.stop(); 136 | expect(clearIntervalSpy).toHaveBeenCalled(); 137 | }); 138 | }); 139 | }); 140 | 141 | 142 | -------------------------------------------------------------------------------- /src/api/technical-indicators/TechnicalIndicatorsClient.ts: -------------------------------------------------------------------------------- 1 | import { FMPClient } from "../FMPClient.js"; 2 | import type { FMPContext } from "../../types/index.js"; 3 | import type { 4 | TechnicalIndicatorParams, 5 | SMAIndicator, 6 | EMAIndicator, 7 | WMAIndicator, 8 | DEMAIndicator, 9 | TEMAIndicator, 10 | RSIIndicator, 11 | StandardDeviationIndicator, 12 | WilliamsIndicator, 13 | ADXIndicator, 14 | } from "./types.js"; 15 | 16 | export class TechnicalIndicatorsClient extends FMPClient { 17 | constructor(apiKey?: string) { 18 | super(apiKey); 19 | } 20 | 21 | /** 22 | * Get Simple Moving Average (SMA) indicator 23 | * @param params Technical indicator parameters 24 | * @param options Optional parameters including abort signal and context 25 | */ 26 | async getSMA( 27 | params: TechnicalIndicatorParams, 28 | options?: { 29 | signal?: AbortSignal; 30 | context?: FMPContext; 31 | } 32 | ): Promise { 33 | return super.get( 34 | "/technical-indicators/sma", 35 | params, 36 | options 37 | ); 38 | } 39 | 40 | /** 41 | * Get Exponential Moving Average (EMA) indicator 42 | * @param params Technical indicator parameters 43 | * @param options Optional parameters including abort signal and context 44 | */ 45 | async getEMA( 46 | params: TechnicalIndicatorParams, 47 | options?: { 48 | signal?: AbortSignal; 49 | context?: FMPContext; 50 | } 51 | ): Promise { 52 | return super.get( 53 | "/technical-indicators/ema", 54 | params, 55 | options 56 | ); 57 | } 58 | 59 | /** 60 | * Get Weighted Moving Average (WMA) indicator 61 | * @param params Technical indicator parameters 62 | * @param options Optional parameters including abort signal and context 63 | */ 64 | async getWMA( 65 | params: TechnicalIndicatorParams, 66 | options?: { 67 | signal?: AbortSignal; 68 | context?: FMPContext; 69 | } 70 | ): Promise { 71 | return super.get( 72 | "/technical-indicators/wma", 73 | params, 74 | options 75 | ); 76 | } 77 | 78 | /** 79 | * Get Double Exponential Moving Average (DEMA) indicator 80 | * @param params Technical indicator parameters 81 | * @param options Optional parameters including abort signal and context 82 | */ 83 | async getDEMA( 84 | params: TechnicalIndicatorParams, 85 | options?: { 86 | signal?: AbortSignal; 87 | context?: FMPContext; 88 | } 89 | ): Promise { 90 | return super.get( 91 | "/technical-indicators/dema", 92 | params, 93 | options 94 | ); 95 | } 96 | 97 | /** 98 | * Get Triple Exponential Moving Average (TEMA) indicator 99 | * @param params Technical indicator parameters 100 | * @param options Optional parameters including abort signal and context 101 | */ 102 | async getTEMA( 103 | params: TechnicalIndicatorParams, 104 | options?: { 105 | signal?: AbortSignal; 106 | context?: FMPContext; 107 | } 108 | ): Promise { 109 | return super.get( 110 | "/technical-indicators/tema", 111 | params, 112 | options 113 | ); 114 | } 115 | 116 | /** 117 | * Get Relative Strength Index (RSI) indicator 118 | * @param params Technical indicator parameters 119 | * @param options Optional parameters including abort signal and context 120 | */ 121 | async getRSI( 122 | params: TechnicalIndicatorParams, 123 | options?: { 124 | signal?: AbortSignal; 125 | context?: FMPContext; 126 | } 127 | ): Promise { 128 | return super.get( 129 | "/technical-indicators/rsi", 130 | params, 131 | options 132 | ); 133 | } 134 | 135 | /** 136 | * Get Standard Deviation indicator 137 | * @param params Technical indicator parameters 138 | * @param options Optional parameters including abort signal and context 139 | */ 140 | async getStandardDeviation( 141 | params: TechnicalIndicatorParams, 142 | options?: { 143 | signal?: AbortSignal; 144 | context?: FMPContext; 145 | } 146 | ): Promise { 147 | return super.get( 148 | "/technical-indicators/standarddeviation", 149 | params, 150 | options 151 | ); 152 | } 153 | 154 | /** 155 | * Get Williams %R indicator 156 | * @param params Technical indicator parameters 157 | * @param options Optional parameters including abort signal and context 158 | */ 159 | async getWilliams( 160 | params: TechnicalIndicatorParams, 161 | options?: { 162 | signal?: AbortSignal; 163 | context?: FMPContext; 164 | } 165 | ): Promise { 166 | return super.get( 167 | "/technical-indicators/williams", 168 | params, 169 | options 170 | ); 171 | } 172 | 173 | /** 174 | * Get Average Directional Index (ADX) indicator 175 | * @param params Technical indicator parameters 176 | * @param options Optional parameters including abort signal and context 177 | */ 178 | async getADX( 179 | params: TechnicalIndicatorParams, 180 | options?: { 181 | signal?: AbortSignal; 182 | context?: FMPContext; 183 | } 184 | ): Promise { 185 | return super.get( 186 | "/technical-indicators/adx", 187 | params, 188 | options 189 | ); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/server-mode-enforcer/ServerModeEnforcer.ts: -------------------------------------------------------------------------------- 1 | import { validateDynamicToolDiscoveryConfig } from '../utils/validation.js'; 2 | import { getAvailableToolSets } from '../constants/index.js'; 3 | import type { ServerMode, ToolSet } from '../types/index.js'; 4 | 5 | /** 6 | * Simple server mode enforcer that checks environment variables and CLI arguments 7 | * for server-level mode overrides. Uses singleton pattern for global access. 8 | * 9 | * Precedence: CLI arguments > Environment variables 10 | * 11 | * @example 12 | * ```typescript 13 | * // Initialize in main() 14 | * ServerModeEnforcer.initialize(process.env, minimist(process.argv.slice(2))); 15 | * 16 | * // Access from factory 17 | * const enforcer = ServerModeEnforcer.getInstance(); 18 | * const override = enforcer.serverModeOverride; 19 | * if (override) { 20 | * console.log(`Server mode enforced: ${override}`); 21 | * } 22 | * ``` 23 | */ 24 | export class ServerModeEnforcer { 25 | private static instance: ServerModeEnforcer | null = null; 26 | 27 | private readonly _serverModeOverride: ServerMode | null; 28 | private readonly _toolSets: ToolSet[] = []; 29 | 30 | private constructor( 31 | envVars: Record, 32 | cliArgs: Record 33 | ) { 34 | const result = this._determineOverride(envVars, cliArgs); 35 | this._serverModeOverride = result.mode; 36 | this._toolSets = result.toolSets || []; 37 | } 38 | 39 | /** 40 | * Initialize the singleton instance with environment variables and CLI arguments 41 | */ 42 | public static initialize( 43 | envVars: Record, 44 | cliArgs: Record 45 | ): void { 46 | if (ServerModeEnforcer.instance) { 47 | console.warn('[ServerModeEnforcer] Already initialized, ignoring subsequent initialization'); 48 | return; 49 | } 50 | ServerModeEnforcer.instance = new ServerModeEnforcer(envVars, cliArgs); 51 | } 52 | 53 | /** 54 | * Get the singleton instance 55 | */ 56 | public static getInstance(): ServerModeEnforcer { 57 | if (!ServerModeEnforcer.instance) { 58 | throw new Error('[ServerModeEnforcer] Instance not initialized. Call ServerModeEnforcer.initialize() first.'); 59 | } 60 | return ServerModeEnforcer.instance; 61 | } 62 | 63 | /** 64 | * Reset the singleton instance (for testing) 65 | */ 66 | public static reset(): void { 67 | ServerModeEnforcer.instance = null; 68 | } 69 | 70 | /** 71 | * Gets the server mode override, or null if no override is needed 72 | */ 73 | public get serverModeOverride(): ServerMode | null { 74 | return this._serverModeOverride; 75 | } 76 | 77 | /** 78 | * Gets the validated toolsets when mode is STATIC_TOOL_SETS 79 | */ 80 | public get toolSets(): ToolSet[] { 81 | return [...this._toolSets]; // Return copy to prevent mutation 82 | } 83 | 84 | /** 85 | * Determines if there's a server-level mode override from CLI args or env vars 86 | */ 87 | private _determineOverride( 88 | envVars: Record, 89 | cliArgs: Record 90 | ): { mode: ServerMode | null; toolSets?: ToolSet[] } { 91 | // Check CLI arguments first (highest precedence) 92 | 93 | // Support multiple CLI argument variations for dynamic tool discovery 94 | const dynamicToolDiscovery = 95 | cliArgs['dynamic-tool-discovery'] === true || 96 | cliArgs['dynamicToolDiscovery'] === true; 97 | 98 | if (dynamicToolDiscovery) { 99 | return { mode: 'DYNAMIC_TOOL_DISCOVERY' }; 100 | } 101 | 102 | // Support multiple CLI argument variations for tool sets 103 | const toolSetsInput = 104 | cliArgs["fmp-tool-sets"] || 105 | cliArgs["tool-sets"] || 106 | cliArgs["toolSets"]; 107 | 108 | if (toolSetsInput && typeof toolSetsInput === 'string') { 109 | const toolSets = this._parseAndValidateToolSets(toolSetsInput); 110 | if (toolSets.length > 0) { 111 | return { mode: 'STATIC_TOOL_SETS', toolSets }; 112 | } 113 | } 114 | 115 | // Check environment variables second 116 | if (envVars.DYNAMIC_TOOL_DISCOVERY && validateDynamicToolDiscoveryConfig(envVars.DYNAMIC_TOOL_DISCOVERY)) { 117 | return { mode: 'DYNAMIC_TOOL_DISCOVERY' }; 118 | } 119 | 120 | if (envVars.FMP_TOOL_SETS) { 121 | const toolSets = this._parseAndValidateToolSets(envVars.FMP_TOOL_SETS as string); 122 | if (toolSets.length > 0) { 123 | return { mode: 'STATIC_TOOL_SETS', toolSets }; 124 | } 125 | } 126 | 127 | // No override needed 128 | return { mode: null }; 129 | } 130 | 131 | /** 132 | * Parses and validates tool sets, with error handling and process exit on invalid sets 133 | */ 134 | private _parseAndValidateToolSets(input: string): ToolSet[] { 135 | if (!input || typeof input !== 'string') { 136 | return []; 137 | } 138 | 139 | // Parse tool sets (split by comma, trim whitespace) 140 | const toolSets = input.split(",").map((s) => s.trim()) as ToolSet[]; 141 | 142 | // Get available tool sets for validation 143 | const availableToolSets = getAvailableToolSets().map(({ key }) => key); 144 | 145 | // Find invalid tool sets 146 | const invalidToolSets = toolSets.filter( 147 | (ts) => !availableToolSets.includes(ts) 148 | ); 149 | 150 | // Exit process if invalid tool sets found (matches previous behavior) 151 | if (invalidToolSets.length > 0) { 152 | console.error(`Invalid tool sets: ${invalidToolSets.join(", ")}`); 153 | console.error(`Available tool sets: ${availableToolSets.join(", ")}`); 154 | process.exit(1); 155 | } 156 | 157 | return toolSets.filter(ts => ts.length > 0); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/api/directory/DirectoryClient.ts: -------------------------------------------------------------------------------- 1 | import { FMPClient } from "../FMPClient.js"; 2 | import type { FMPContext } from "../../types/index.js"; 3 | import type { 4 | CompanySymbol, 5 | FinancialStatementSymbol, 6 | CIKEntry, 7 | SymbolChange, 8 | ETFEntry, 9 | ActivelyTradingEntry, 10 | EarningsTranscriptEntry, 11 | ExchangeEntry, 12 | SectorEntry, 13 | IndustryEntry, 14 | CountryEntry, 15 | } from "./types.js"; 16 | 17 | 18 | 19 | export class DirectoryClient extends FMPClient { 20 | constructor(apiKey?: string) { 21 | super(apiKey); 22 | } 23 | 24 | /** 25 | * Get a list of all company symbols 26 | * @param options Optional parameters including abort signal and context 27 | * @returns Array of company symbols 28 | */ 29 | async getCompanySymbols(options?: { 30 | signal?: AbortSignal; 31 | context?: FMPContext; 32 | }): Promise { 33 | return super.get("/stock-list", {}, options); 34 | } 35 | 36 | /** 37 | * Get a list of companies with available financial statements 38 | * @param options Optional parameters including abort signal and context 39 | * @returns Array of companies with financial statements 40 | */ 41 | async getFinancialStatementSymbols(options?: { 42 | signal?: AbortSignal; 43 | context?: FMPContext; 44 | }): Promise { 45 | return super.get( 46 | "/financial-statement-symbol-list", 47 | {}, 48 | options 49 | ); 50 | } 51 | 52 | /** 53 | * Get a list of CIK numbers for SEC-registered entities 54 | * @param limit Optional limit on number of results (default: 1000) 55 | * @param options Optional parameters including abort signal and context 56 | * @returns Array of CIK entries 57 | */ 58 | async getCIKList( 59 | limit?: number, 60 | options?: { 61 | signal?: AbortSignal; 62 | context?: FMPContext; 63 | } 64 | ): Promise { 65 | return super.get("/cik-list", { limit }, options); 66 | } 67 | 68 | /** 69 | * Get a list of stock symbol changes 70 | * @param invalid Optional filter for invalid symbols (default: false) 71 | * @param limit Optional limit on number of results (default: 100) 72 | * @param options Optional parameters including abort signal and context 73 | * @returns Array of symbol changes 74 | */ 75 | async getSymbolChanges( 76 | invalid?: boolean, 77 | limit?: number, 78 | options?: { 79 | signal?: AbortSignal; 80 | context?: FMPContext; 81 | } 82 | ): Promise { 83 | return super.get( 84 | "/symbol-change", 85 | { invalid, limit }, 86 | options 87 | ); 88 | } 89 | 90 | /** 91 | * Get a list of ETFs 92 | * @param options Optional parameters including abort signal and context 93 | * @returns Array of ETF entries 94 | */ 95 | async getETFList(options?: { 96 | signal?: AbortSignal; 97 | context?: FMPContext; 98 | }): Promise { 99 | return super.get("/etf-list", {}, options); 100 | } 101 | 102 | /** 103 | * Get a list of actively trading companies 104 | * @param options Optional parameters including abort signal and context 105 | * @returns Array of actively trading companies 106 | */ 107 | async getActivelyTradingList(options?: { 108 | signal?: AbortSignal; 109 | context?: FMPContext; 110 | }): Promise { 111 | return super.get( 112 | "/actively-trading-list", 113 | {}, 114 | options 115 | ); 116 | } 117 | 118 | /** 119 | * Get a list of companies with earnings transcripts 120 | * @param options Optional parameters including abort signal and context 121 | * @returns Array of companies with earnings transcripts 122 | */ 123 | async getEarningsTranscriptList(options?: { 124 | signal?: AbortSignal; 125 | context?: FMPContext; 126 | }): Promise { 127 | return super.get( 128 | "/earnings-transcript-list", 129 | {}, 130 | options 131 | ); 132 | } 133 | 134 | /** 135 | * Get a list of available exchanges 136 | * @param options Optional parameters including abort signal and context 137 | * @returns Array of available exchanges 138 | */ 139 | async getAvailableExchanges(options?: { 140 | signal?: AbortSignal; 141 | context?: FMPContext; 142 | }): Promise { 143 | return super.get("/available-exchanges", {}, options); 144 | } 145 | 146 | /** 147 | * Get a list of available sectors 148 | * @param options Optional parameters including abort signal and context 149 | * @returns Array of available sectors 150 | */ 151 | async getAvailableSectors(options?: { 152 | signal?: AbortSignal; 153 | context?: FMPContext; 154 | }): Promise { 155 | return super.get("/available-sectors", {}, options); 156 | } 157 | 158 | /** 159 | * Get a list of available industries 160 | * @param options Optional parameters including abort signal and context 161 | * @returns Array of available industries 162 | */ 163 | async getAvailableIndustries(options?: { 164 | signal?: AbortSignal; 165 | context?: FMPContext; 166 | }): Promise { 167 | return super.get("/available-industries", {}, options); 168 | } 169 | 170 | /** 171 | * Get a list of available countries 172 | * @param options Optional parameters including abort signal and context 173 | * @returns Array of available countries 174 | */ 175 | async getAvailableCountries(options?: { 176 | signal?: AbortSignal; 177 | context?: FMPContext; 178 | }): Promise { 179 | return super.get("/available-countries", {}, options); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/api/commodity/CommodityClient.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi, beforeEach } from 'vitest'; 2 | import { CommodityClient } from './CommodityClient.js'; 3 | import { FMPClient } from '../FMPClient.js'; 4 | import type { 5 | Commodity 6 | } from './types.js'; 7 | 8 | // Mock the FMPClient 9 | vi.mock('../FMPClient.js'); 10 | 11 | describe('CommodityClient', () => { 12 | let commodityClient: CommodityClient; 13 | let mockGet: ReturnType; 14 | 15 | beforeEach(() => { 16 | vi.clearAllMocks(); 17 | 18 | // Create mock for the get method 19 | mockGet = vi.fn(); 20 | 21 | // Mock FMPClient prototype get method using any to bypass protected access 22 | (FMPClient.prototype as any).get = mockGet; 23 | 24 | // Create CommodityClient instance 25 | commodityClient = new CommodityClient('test-api-key'); 26 | }); 27 | 28 | describe('listCommodities', () => { 29 | it('should call get with correct parameters', async () => { 30 | const mockData: Commodity[] = [ 31 | { 32 | symbol: 'CL', 33 | name: 'Crude Oil', 34 | exchange: 'NYMEX', 35 | tradeMonth: '2024-03', 36 | currency: 'USD' 37 | }, 38 | { 39 | symbol: 'GC', 40 | name: 'Gold', 41 | exchange: 'COMEX', 42 | tradeMonth: '2024-04', 43 | currency: 'USD' 44 | }, 45 | { 46 | symbol: 'SI', 47 | name: 'Silver', 48 | exchange: 'COMEX', 49 | tradeMonth: '2024-03', 50 | currency: 'USD' 51 | } 52 | ]; 53 | mockGet.mockResolvedValue(mockData); 54 | 55 | const result = await commodityClient.listCommodities(); 56 | 57 | expect(mockGet).toHaveBeenCalledWith('/commodity-list', {}, undefined); 58 | expect(result).toEqual(mockData); 59 | }); 60 | 61 | it('should call get with correct parameters including options', async () => { 62 | const mockData: Commodity[] = [ 63 | { 64 | symbol: 'NG', 65 | name: 'Natural Gas', 66 | exchange: 'NYMEX', 67 | tradeMonth: '2024-05', 68 | currency: 'USD' 69 | } 70 | ]; 71 | mockGet.mockResolvedValue(mockData); 72 | 73 | const abortController = new AbortController(); 74 | const options = { 75 | signal: abortController.signal, 76 | context: { config: { FMP_ACCESS_TOKEN: 'test-token' } } 77 | }; 78 | 79 | const result = await commodityClient.listCommodities(options); 80 | 81 | expect(mockGet).toHaveBeenCalledWith('/commodity-list', {}, options); 82 | expect(result).toEqual(mockData); 83 | }); 84 | 85 | it('should handle empty response', async () => { 86 | const mockData: Commodity[] = []; 87 | mockGet.mockResolvedValue(mockData); 88 | 89 | const result = await commodityClient.listCommodities(); 90 | 91 | expect(mockGet).toHaveBeenCalledWith('/commodity-list', {}, undefined); 92 | expect(result).toEqual(mockData); 93 | }); 94 | 95 | it('should handle API errors', async () => { 96 | const errorMessage = 'API Error'; 97 | mockGet.mockRejectedValue(new Error(errorMessage)); 98 | 99 | await expect(commodityClient.listCommodities()) 100 | .rejects.toThrow(errorMessage); 101 | }); 102 | 103 | it('should handle network errors', async () => { 104 | const networkError = new Error('Network Error'); 105 | mockGet.mockRejectedValue(networkError); 106 | 107 | await expect(commodityClient.listCommodities()) 108 | .rejects.toThrow('Network Error'); 109 | }); 110 | 111 | it('should handle abort signal', async () => { 112 | const abortController = new AbortController(); 113 | const abortError = new Error('Request aborted'); 114 | abortError.name = 'AbortError'; 115 | mockGet.mockRejectedValue(abortError); 116 | 117 | const options = { 118 | signal: abortController.signal 119 | }; 120 | 121 | await expect(commodityClient.listCommodities(options)) 122 | .rejects.toThrow('Request aborted'); 123 | }); 124 | 125 | it('should handle large dataset response', async () => { 126 | const mockData: Commodity[] = Array.from({ length: 100 }, (_, index) => ({ 127 | symbol: `COMM${index + 1}`, 128 | name: `Commodity ${index + 1}`, 129 | exchange: index % 2 === 0 ? 'NYMEX' : 'COMEX', 130 | tradeMonth: `2024-${String((index % 12) + 1).padStart(2, '0')}`, 131 | currency: 'USD' 132 | })); 133 | mockGet.mockResolvedValue(mockData); 134 | 135 | const result = await commodityClient.listCommodities(); 136 | 137 | expect(mockGet).toHaveBeenCalledWith('/commodity-list', {}, undefined); 138 | expect(result).toEqual(mockData); 139 | expect(result).toHaveLength(100); 140 | }); 141 | }); 142 | 143 | describe('constructor', () => { 144 | it('should create instance with API key', () => { 145 | const client = new CommodityClient('my-api-key'); 146 | expect(client).toBeInstanceOf(CommodityClient); 147 | }); 148 | 149 | it('should create instance without API key', () => { 150 | const client = new CommodityClient(); 151 | expect(client).toBeInstanceOf(CommodityClient); 152 | }); 153 | 154 | it('should extend FMPClient', () => { 155 | const client = new CommodityClient('test-key'); 156 | expect(client).toBeInstanceOf(FMPClient); 157 | }); 158 | }); 159 | 160 | describe('inheritance', () => { 161 | it('should properly extend FMPClient', () => { 162 | expect(commodityClient).toBeInstanceOf(FMPClient); 163 | expect(commodityClient).toBeInstanceOf(CommodityClient); 164 | }); 165 | 166 | it('should have access to parent class methods through inheritance', () => { 167 | // Test that the client has the expected structure 168 | expect(typeof commodityClient.listCommodities).toBe('function'); 169 | }); 170 | }); 171 | }); -------------------------------------------------------------------------------- /src/api/calendar/CalendarClient.ts: -------------------------------------------------------------------------------- 1 | import { FMPClient } from "../FMPClient.js"; 2 | import type { FMPContext } from "../../types/index.js"; 3 | import type { 4 | Dividend, 5 | EarningsReport, 6 | IPO, 7 | IPODisclosure, 8 | IPOProspectus, 9 | StockSplit, 10 | } from "./types.js"; 11 | 12 | export class CalendarClient extends FMPClient { 13 | constructor(apiKey?: string) { 14 | super(apiKey); 15 | } 16 | 17 | /** 18 | * Get dividend information for a stock symbol 19 | * @param symbol Stock symbol 20 | * @param limit Optional limit on number of results (default: 100, max: 1000) 21 | * @param options Optional parameters including abort signal and context 22 | * @returns Array of dividend information 23 | */ 24 | async getDividends( 25 | symbol: string, 26 | limit?: number, 27 | options?: { 28 | signal?: AbortSignal; 29 | context?: FMPContext; 30 | } 31 | ): Promise { 32 | return super.get("/dividends", { symbol, limit }, options); 33 | } 34 | 35 | /** 36 | * Get dividend calendar for a date range 37 | * @param from Optional start date (YYYY-MM-DD) 38 | * @param to Optional end date (YYYY-MM-DD) 39 | * @param options Optional parameters including abort signal and context 40 | * @returns Array of dividend calendar entries 41 | */ 42 | async getDividendsCalendar( 43 | from?: string, 44 | to?: string, 45 | options?: { 46 | signal?: AbortSignal; 47 | context?: FMPContext; 48 | } 49 | ): Promise { 50 | return super.get("/dividends-calendar", { from, to }, options); 51 | } 52 | 53 | /** 54 | * Get earnings reports for a stock symbol 55 | * @param symbol Stock symbol 56 | * @param limit Optional limit on number of results (default: 100, max: 1000) 57 | * @param options Optional parameters including abort signal and context 58 | * @returns Array of earnings reports 59 | */ 60 | async getEarningsReports( 61 | symbol: string, 62 | limit?: number, 63 | options?: { 64 | signal?: AbortSignal; 65 | context?: FMPContext; 66 | } 67 | ): Promise { 68 | return super.get("/earnings", { symbol, limit }, options); 69 | } 70 | 71 | /** 72 | * Get earnings calendar for a date range 73 | * @param from Optional start date (YYYY-MM-DD) 74 | * @param to Optional end date (YYYY-MM-DD) 75 | * @param options Optional parameters including abort signal and context 76 | * @returns Array of earnings calendar entries 77 | */ 78 | async getEarningsCalendar( 79 | from?: string, 80 | to?: string, 81 | options?: { 82 | signal?: AbortSignal; 83 | context?: FMPContext; 84 | } 85 | ): Promise { 86 | return super.get( 87 | "/earnings-calendar", 88 | { from, to }, 89 | options 90 | ); 91 | } 92 | 93 | /** 94 | * Get IPO calendar for a date range 95 | * @param from Optional start date (YYYY-MM-DD) 96 | * @param to Optional end date (YYYY-MM-DD) 97 | * @param options Optional parameters including abort signal and context 98 | * @returns Array of IPO calendar entries 99 | */ 100 | async getIPOCalendar( 101 | from?: string, 102 | to?: string, 103 | options?: { 104 | signal?: AbortSignal; 105 | context?: FMPContext; 106 | } 107 | ): Promise { 108 | return super.get("/ipos-calendar", { from, to }, options); 109 | } 110 | 111 | /** 112 | * Get IPO disclosures for a date range 113 | * @param from Optional start date (YYYY-MM-DD) 114 | * @param to Optional end date (YYYY-MM-DD) 115 | * @param options Optional parameters including abort signal and context 116 | * @returns Array of IPO disclosures 117 | */ 118 | async getIPODisclosures( 119 | from?: string, 120 | to?: string, 121 | options?: { 122 | signal?: AbortSignal; 123 | context?: FMPContext; 124 | } 125 | ): Promise { 126 | return super.get( 127 | "/ipos-disclosure", 128 | { from, to }, 129 | options 130 | ); 131 | } 132 | 133 | /** 134 | * Get IPO prospectuses for a date range 135 | * @param from Optional start date (YYYY-MM-DD) 136 | * @param to Optional end date (YYYY-MM-DD) 137 | * @param options Optional parameters including abort signal and context 138 | * @returns Array of IPO prospectuses 139 | */ 140 | async getIPOProspectuses( 141 | from?: string, 142 | to?: string, 143 | options?: { 144 | signal?: AbortSignal; 145 | context?: FMPContext; 146 | } 147 | ): Promise { 148 | return super.get( 149 | "/ipos-prospectus", 150 | { from, to }, 151 | options 152 | ); 153 | } 154 | 155 | /** 156 | * Get stock splits for a stock symbol 157 | * @param symbol Stock symbol 158 | * @param limit Optional limit on number of results (default: 100, max: 1000) 159 | * @param options Optional parameters including abort signal and context 160 | * @returns Array of stock splits 161 | */ 162 | async getStockSplits( 163 | symbol: string, 164 | limit?: number, 165 | options?: { 166 | signal?: AbortSignal; 167 | context?: FMPContext; 168 | } 169 | ): Promise { 170 | return super.get("/splits", { symbol, limit }, options); 171 | } 172 | 173 | /** 174 | * Get stock splits calendar for a date range 175 | * @param from Optional start date (YYYY-MM-DD) 176 | * @param to Optional end date (YYYY-MM-DD) 177 | * @param options Optional parameters including abort signal and context 178 | * @returns Array of stock splits calendar entries 179 | */ 180 | async getStockSplitsCalendar( 181 | from?: string, 182 | to?: string, 183 | options?: { 184 | signal?: AbortSignal; 185 | context?: FMPContext; 186 | } 187 | ): Promise { 188 | return super.get("/splits-calendar", { from, to }, options); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /scripts/version-sync.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env tsx 2 | 3 | import { program } from 'commander'; 4 | 5 | /** 6 | * Safely extracts error message from unknown error type. 7 | * @param error - The error to extract message from. 8 | * @returns The error message string. 9 | */ 10 | function getErrorMessage(error: unknown): string { 11 | return error instanceof Error ? error.message : String(error); 12 | } 13 | import { 14 | validateVersionConsistency, 15 | validateServerJsonSchema, 16 | synchronizeVersion, 17 | getVersionInfo, 18 | getCurrentVersion, 19 | type SyncOptions 20 | } from '../src/utils/versionSync.js'; 21 | 22 | /** 23 | * Formats validation results for console output. 24 | * @param result - The validation result to format. 25 | * @param title - Title for the validation section. 26 | */ 27 | function formatValidationResult(result: any, title: string): void { 28 | console.log(`\n${title}:`); 29 | console.log('='.repeat(title.length + 1)); 30 | 31 | if (result.isValid) { 32 | console.log('✅ All validations passed'); 33 | } else { 34 | console.log('❌ Validation failed'); 35 | } 36 | 37 | if (result.errors.length > 0) { 38 | console.log('\nErrors:'); 39 | result.errors.forEach((error: string) => console.log(` ❌ ${error}`)); 40 | } 41 | 42 | if (result.warnings.length > 0) { 43 | console.log('\nWarnings:'); 44 | result.warnings.forEach((warning: string) => console.log(` ⚠️ ${warning}`)); 45 | } 46 | } 47 | 48 | /** 49 | * Command to validate version consistency across all files. 50 | */ 51 | async function validateCommand(): Promise { 52 | try { 53 | console.log('🔍 Validating version consistency...'); 54 | 55 | const versionInfo = await getVersionInfo(); 56 | console.log('\nCurrent versions:'); 57 | console.log(` package.json: ${versionInfo.packageJson}`); 58 | console.log(` server.json: ${versionInfo.serverJson}`); 59 | console.log(` CHANGELOG.md: ${versionInfo.changelog || 'not found'}`); 60 | 61 | const consistencyResult = await validateVersionConsistency(); 62 | formatValidationResult(consistencyResult, 'Version Consistency Check'); 63 | 64 | const schemaResult = await validateServerJsonSchema(); 65 | formatValidationResult(schemaResult, 'Server.json Schema Validation'); 66 | 67 | const overallValid = consistencyResult.isValid && schemaResult.isValid; 68 | process.exit(overallValid ? 0 : 1); 69 | 70 | } catch (error) { 71 | console.error('❌ Validation failed:', getErrorMessage(error)); 72 | process.exit(1); 73 | } 74 | } 75 | 76 | /** 77 | * Command to synchronize version across all files. 78 | */ 79 | async function syncCommand(newVersion: string, options: any): Promise { 80 | try { 81 | const syncOptions: SyncOptions = { 82 | dryRun: options.dryRun, 83 | validateSchema: !options.skipSchema, 84 | updateChangelog: !options.skipChangelog 85 | }; 86 | 87 | if (options.dryRun) { 88 | console.log('🔍 Running in dry-run mode (no files will be modified)'); 89 | } 90 | 91 | console.log(`🔄 Synchronizing version to ${newVersion}...`); 92 | 93 | const result = await synchronizeVersion(newVersion, process.cwd(), syncOptions); 94 | 95 | if (result.isValid) { 96 | console.log('✅ Version synchronization completed successfully'); 97 | 98 | if (!options.dryRun) { 99 | const versionInfo = await getVersionInfo(); 100 | console.log('\nUpdated versions:'); 101 | console.log(` package.json: ${versionInfo.packageJson}`); 102 | console.log(` server.json: ${versionInfo.serverJson}`); 103 | console.log(` CHANGELOG.md: ${versionInfo.changelog || 'not found'}`); 104 | } 105 | } else { 106 | console.log('❌ Version synchronization failed'); 107 | } 108 | 109 | formatValidationResult(result, 'Synchronization Results'); 110 | 111 | process.exit(result.isValid ? 0 : 1); 112 | 113 | } catch (error) { 114 | console.error('❌ Synchronization failed:', getErrorMessage(error)); 115 | process.exit(1); 116 | } 117 | } 118 | 119 | /** 120 | * Command to show current version information. 121 | */ 122 | async function infoCommand(): Promise { 123 | try { 124 | console.log('📋 Current version information:'); 125 | 126 | const versionInfo = await getVersionInfo(); 127 | console.log(`\n package.json: ${versionInfo.packageJson}`); 128 | console.log(` server.json: ${versionInfo.serverJson}`); 129 | console.log(` CHANGELOG.md: ${versionInfo.changelog || 'not found'}`); 130 | 131 | const currentVersion = await getCurrentVersion(); 132 | console.log(`\n Current version: ${currentVersion}`); 133 | 134 | // Quick consistency check 135 | const allSame = versionInfo.packageJson === versionInfo.serverJson && 136 | (!versionInfo.changelog || versionInfo.changelog === versionInfo.packageJson); 137 | 138 | console.log(` Status: ${allSame ? '✅ Consistent' : '⚠️ Inconsistent'}`); 139 | 140 | } catch (error) { 141 | console.error('❌ Failed to get version info:', getErrorMessage(error)); 142 | process.exit(1); 143 | } 144 | } 145 | 146 | // Configure CLI program 147 | program 148 | .name('version-sync') 149 | .description('Version synchronization utilities for MCP registry publishing') 150 | .version('1.0.0'); 151 | 152 | program 153 | .command('validate') 154 | .description('Validate version consistency and server.json schema') 155 | .action(validateCommand); 156 | 157 | program 158 | .command('sync ') 159 | .description('Synchronize version across all files') 160 | .option('-d, --dry-run', 'Show what would be changed without making changes') 161 | .option('--skip-schema', 'Skip server.json schema validation') 162 | .option('--skip-changelog', 'Skip CHANGELOG.md updates') 163 | .action(syncCommand); 164 | 165 | program 166 | .command('info') 167 | .description('Show current version information') 168 | .action(infoCommand); 169 | 170 | // Parse command line arguments 171 | program.parse(); -------------------------------------------------------------------------------- /src/api/analyst/AnalystClient.ts: -------------------------------------------------------------------------------- 1 | import { FMPClient } from "../FMPClient.js"; 2 | import type { 3 | AnalystEstimate, 4 | RatingsSnapshot, 5 | HistoricalRating, 6 | PriceTargetSummary, 7 | PriceTargetConsensus, 8 | PriceTargetNews, 9 | StockGrade, 10 | HistoricalStockGrade, 11 | StockGradeSummary, 12 | StockGradeNews, 13 | } from "./types.js"; 14 | 15 | export class AnalystClient extends FMPClient { 16 | constructor(apiKey?: string) { 17 | super(apiKey); 18 | } 19 | 20 | /** 21 | * Get analyst financial estimates for a stock symbol 22 | * @param symbol Stock symbol 23 | * @param period Period (annual or quarter) 24 | * @param page Optional page number (default: 0) 25 | * @param limit Optional limit on number of results (default: 10, max: 1000) 26 | * @returns Array of analyst estimates 27 | */ 28 | async getAnalystEstimates( 29 | symbol: string, 30 | period: "annual" | "quarter", 31 | page?: number, 32 | limit?: number 33 | ): Promise { 34 | return super.get("/analyst-estimates", { 35 | symbol, 36 | period, 37 | page, 38 | limit, 39 | }); 40 | } 41 | 42 | /** 43 | * Get ratings snapshot for a stock symbol 44 | * @param symbol Stock symbol 45 | * @param limit Optional limit on number of results (default: 1) 46 | * @returns Array of ratings snapshots 47 | */ 48 | async getRatingsSnapshot( 49 | symbol: string, 50 | limit?: number 51 | ): Promise { 52 | return super.get("/ratings-snapshot", { symbol, limit }); 53 | } 54 | 55 | /** 56 | * Get historical ratings for a stock symbol 57 | * @param symbol Stock symbol 58 | * @param limit Optional limit on number of results (default: 1, max: 10000) 59 | * @returns Array of historical ratings 60 | */ 61 | async getHistoricalRatings( 62 | symbol: string, 63 | limit?: number 64 | ): Promise { 65 | return super.get("/ratings-historical", { 66 | symbol, 67 | limit, 68 | }); 69 | } 70 | 71 | /** 72 | * Get price target summary for a stock symbol 73 | * @param symbol Stock symbol 74 | * @returns Array of price target summaries 75 | */ 76 | async getPriceTargetSummary(symbol: string): Promise { 77 | return super.get("/price-target-summary", { symbol }); 78 | } 79 | 80 | /** 81 | * Get price target consensus for a stock symbol 82 | * @param symbol Stock symbol 83 | * @returns Array of price target consensus 84 | */ 85 | async getPriceTargetConsensus( 86 | symbol: string 87 | ): Promise { 88 | return super.get("/price-target-consensus", { 89 | symbol, 90 | }); 91 | } 92 | 93 | /** 94 | * Get price target news for a stock symbol 95 | * @param symbol Stock symbol 96 | * @param page Optional page number (default: 0) 97 | * @param limit Optional limit on number of results (default: 10) 98 | * @returns Array of price target news 99 | */ 100 | async getPriceTargetNews( 101 | symbol: string, 102 | page?: number, 103 | limit?: number 104 | ): Promise { 105 | return super.get("/price-target-news", { 106 | symbol, 107 | page, 108 | limit, 109 | }); 110 | } 111 | 112 | /** 113 | * Get latest price target news for all stocks 114 | * @param page Optional page number (default: 0, max: 100) 115 | * @param limit Optional limit on number of results (default: 10, max: 1000) 116 | * @returns Array of price target news 117 | */ 118 | async getPriceTargetLatestNews( 119 | page?: number, 120 | limit?: number 121 | ): Promise { 122 | return super.get("/price-target-latest-news", { 123 | page, 124 | limit, 125 | }); 126 | } 127 | 128 | /** 129 | * Get stock grades for a stock symbol 130 | * @param symbol Stock symbol 131 | * @returns Array of stock grades 132 | */ 133 | async getStockGrades(symbol: string): Promise { 134 | return super.get("/grades", { symbol }); 135 | } 136 | 137 | /** 138 | * Get historical stock grades for a stock symbol 139 | * @param symbol Stock symbol 140 | * @param limit Optional limit on number of results (default: 100, max: 1000) 141 | * @returns Array of historical stock grades 142 | */ 143 | async getHistoricalStockGrades( 144 | symbol: string, 145 | limit?: number 146 | ): Promise { 147 | return super.get("/grades-historical", { 148 | symbol, 149 | limit, 150 | }); 151 | } 152 | 153 | /** 154 | * Get stock grade summary for a stock symbol 155 | * @param symbol Stock symbol 156 | * @returns Array of stock grade summaries 157 | */ 158 | async getStockGradeSummary(symbol: string): Promise { 159 | return super.get("/grades-consensus", { symbol }); 160 | } 161 | 162 | /** 163 | * Get stock grade news for a stock symbol 164 | * @param symbol Stock symbol 165 | * @param page Optional page number (default: 0) 166 | * @param limit Optional limit on number of results (default: 1, max: 100) 167 | * @returns Array of stock grade news 168 | */ 169 | async getStockGradeNews( 170 | symbol: string, 171 | page?: number, 172 | limit?: number 173 | ): Promise { 174 | return super.get("/grades-news", { symbol, page, limit }); 175 | } 176 | 177 | /** 178 | * Get latest stock grade news for all stocks 179 | * @param page Optional page number (default: 0, max: 100) 180 | * @param limit Optional limit on number of results (default: 10, max: 1000) 181 | * @returns Array of stock grade news 182 | */ 183 | async getStockGradeLatestNews( 184 | page?: number, 185 | limit?: number 186 | ): Promise { 187 | return super.get("/grades-latest-news", { page, limit }); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/tools/index.ts: -------------------------------------------------------------------------------- 1 | import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 2 | import { registerSearchTools } from "./search.js"; 3 | import { registerDirectoryTools } from "./directory.js"; 4 | import { registerAnalystTools } from "./analyst.js"; 5 | import { registerCalendarTools } from "./calendar.js"; 6 | import { registerChartTools } from "./chart.js"; 7 | import { registerCompanyTools } from "./company.js"; 8 | import { registerCOTTools } from "./cot.js"; 9 | import { registerESGTools } from "./esg.js"; 10 | import { registerEconomicsTools } from "./economics.js"; 11 | import { registerDCFTools } from "./dcf.js"; 12 | import { registerFundTools } from "./fund.js"; 13 | import { registerCommodityTools } from "./commodity.js"; 14 | import { registerFundraisersTools } from "./fundraisers.js"; 15 | import { registerCryptoTools } from "./crypto.js"; 16 | import { registerForexTools } from "./forex.js"; 17 | import { registerStatementsTools } from "./statements.js"; 18 | import { registerForm13FTools } from "./form-13f.js"; 19 | import { registerIndexesTools } from "./indexes.js"; 20 | import { registerInsiderTradesTools } from "./insider-trades.js"; 21 | import { registerMarketPerformanceTools } from "./market-performance.js"; 22 | import { registerMarketHoursTools } from "./market-hours.js"; 23 | import { registerNewsTools } from "./news.js"; 24 | import { registerTechnicalIndicatorsTools } from "./technical-indicators.js"; 25 | import { registerQuotesTools } from "./quotes.js"; 26 | import { registerEarningsTranscriptTools } from "./earnings-transcript.js"; 27 | import { registerSECFilingsTools } from "./sec-filings.js"; 28 | import { registerGovernmentTradingTools } from "./government-trading.js"; 29 | import { registerBulkTools } from "./bulk.js"; 30 | import { getModulesForToolSets } from "../constants/index.js"; 31 | import type { ToolSet } from "../types/index.js"; 32 | 33 | /** 34 | * Module registration function mapping 35 | */ 36 | const MODULE_REGISTRATIONS = { 37 | search: registerSearchTools, 38 | directory: registerDirectoryTools, 39 | analyst: registerAnalystTools, 40 | calendar: registerCalendarTools, 41 | chart: registerChartTools, 42 | company: registerCompanyTools, 43 | cot: registerCOTTools, 44 | esg: registerESGTools, 45 | economics: registerEconomicsTools, 46 | dcf: registerDCFTools, 47 | fund: registerFundTools, 48 | commodity: registerCommodityTools, 49 | fundraisers: registerFundraisersTools, 50 | crypto: registerCryptoTools, 51 | forex: registerForexTools, 52 | statements: registerStatementsTools, 53 | "form-13f": registerForm13FTools, 54 | indexes: registerIndexesTools, 55 | "insider-trades": registerInsiderTradesTools, 56 | "market-performance": registerMarketPerformanceTools, 57 | "market-hours": registerMarketHoursTools, 58 | news: registerNewsTools, 59 | "technical-indicators": registerTechnicalIndicatorsTools, 60 | quotes: registerQuotesTools, 61 | "earnings-transcript": registerEarningsTranscriptTools, 62 | "sec-filings": registerSECFilingsTools, 63 | "government-trading": registerGovernmentTradingTools, 64 | bulk: registerBulkTools, 65 | } as const; 66 | 67 | /** 68 | * Register tools based on specified tool sets 69 | * @param server The MCP server instance 70 | * @param toolSets Array of tool set names to load (if empty, loads all tools) 71 | * @param accessToken The Financial Modeling Prep API access token (optional when using lazy loading) 72 | */ 73 | export function registerToolsBySet( 74 | server: McpServer, 75 | toolSets: ToolSet[], 76 | accessToken?: string 77 | ): void { 78 | // If no tool sets specified, load all tools for backward compatibility 79 | if (toolSets.length === 0) { 80 | registerAllTools(server, accessToken); 81 | return; 82 | } 83 | 84 | // Get the modules that should be loaded for the specified tool sets 85 | const modulesToLoad = getModulesForToolSets(toolSets); 86 | 87 | // Register each required module 88 | for (const moduleName of modulesToLoad) { 89 | const registrationFunction = 90 | MODULE_REGISTRATIONS[moduleName as keyof typeof MODULE_REGISTRATIONS]; 91 | if (registrationFunction) { 92 | registrationFunction(server, accessToken); 93 | } else { 94 | console.warn(`Unknown module: ${moduleName}`); 95 | } 96 | } 97 | 98 | console.log( 99 | `Loaded ${modulesToLoad.length} modules for tool sets: ${toolSets.join( 100 | ", " 101 | )}` 102 | ); 103 | } 104 | 105 | /** 106 | * Register all tools with the MCP server 107 | * @param server The MCP server instance 108 | * @param accessToken The Financial Modeling Prep API access token (optional when using lazy loading) 109 | */ 110 | export function registerAllTools( 111 | server: McpServer, 112 | accessToken?: string 113 | ): void { 114 | registerSearchTools(server, accessToken); 115 | registerDirectoryTools(server, accessToken); 116 | registerAnalystTools(server, accessToken); 117 | registerCalendarTools(server, accessToken); 118 | registerChartTools(server, accessToken); 119 | registerCompanyTools(server, accessToken); 120 | registerCOTTools(server, accessToken); 121 | registerESGTools(server, accessToken); 122 | registerEconomicsTools(server, accessToken); 123 | registerDCFTools(server, accessToken); 124 | registerFundTools(server, accessToken); 125 | registerCommodityTools(server, accessToken); 126 | registerFundraisersTools(server, accessToken); 127 | registerCryptoTools(server, accessToken); 128 | registerForexTools(server, accessToken); 129 | registerStatementsTools(server, accessToken); 130 | registerForm13FTools(server, accessToken); 131 | registerIndexesTools(server, accessToken); 132 | registerInsiderTradesTools(server, accessToken); 133 | registerMarketPerformanceTools(server, accessToken); 134 | registerMarketHoursTools(server, accessToken); 135 | registerNewsTools(server, accessToken); 136 | registerTechnicalIndicatorsTools(server, accessToken); 137 | registerQuotesTools(server, accessToken); 138 | registerEarningsTranscriptTools(server, accessToken); 139 | registerSECFilingsTools(server, accessToken); 140 | registerGovernmentTradingTools(server, accessToken); 141 | registerBulkTools(server, accessToken); 142 | } 143 | --------------------------------------------------------------------------------