Skip to main content
Glama
hamid-vakilzadeh

XBRL-US MCP Server

index.py9.05 kB
""" XBRL-US MCP Server A FastMCP server that provides access to XBRL-US financial data with authentication """ import logging import json from fastmcp import FastMCP, Context from xbrl_us import XBRL from smithery.decorators import smithery from pandas import DataFrame from typing import Annotated, Literal, Any from pydantic import Field import requests from funcs.middleware import SessionAuthMiddleware, ConfigSchema # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) def validate_and_convert_parameters( parameters: dict[str, Any] | str | None, ) -> dict[str, Any] | None: """ Validate and convert parameters to the correct format. This function handles cases where the LLM passes parameters as a JSON string instead of a dict object, which can happen due to serialization. Args: parameters: Either a dict, JSON string, or None Returns: A validated dict or None Raises: ValueError: If parameters cannot be parsed or are in invalid format """ if parameters is None: return None # If it's already a dict, return it if isinstance(parameters, dict): return parameters # If it's a string, try to parse it as JSON if isinstance(parameters, str): try: parsed = json.loads(parameters) if not isinstance(parsed, dict): raise ValueError( f"Parameters must be a dictionary/object, not {type(parsed).__name__}. " f"Example: {{'entity.ticker': 'AAPL', 'period.fiscal-year': 2023}}" ) return parsed except json.JSONDecodeError as e: raise ValueError( f"Invalid JSON in parameters string: {e}. " f"Parameters must be a valid JSON object like {{'entity.ticker': 'AAPL', 'period.fiscal-year': 2023}}" ) from e # Invalid type raise ValueError( f"Parameters must be a dict/object or JSON string, got {type(parameters).__name__}. " f"Example: {{'entity.ticker': 'AAPL', 'period.fiscal-year': 2023}}" ) @smithery.server(config_schema=ConfigSchema) def create_server(): # Initialize the FastMCP server with configuration schema support mcp = FastMCP("XBRL-US Data Server") mcp.add_middleware(SessionAuthMiddleware()) @mcp.tool( annotations={ "title": "Query XBRL Data", "readOnlyHint": True, "openWorldHint": True, "idempotentHint": True, }, # exclude_args=["ctx"], ) async def query( ctx: Context, endpoint: Annotated[ str, Field( description="The XBRL-US API endpoint path (e.g., '/fact/search', '/entity/search', '/report/search'). See meta endpoints for available options.", examples=["/fact/search", "/entity/search", "/report/search"], ), ], fields: Annotated[ list[str], Field( description="List of field names to retrieve from the endpoint. Must not be empty.", min_length=1, examples=[["entity.name", "fact.value", "period.instant"]], ), ], parameters: Annotated[ dict[str, str | int | float | bool | list] | str | None, Field( description="Query parameters to filter results. Must be a JSON object/dict with key-value pairs. Example: {'entity.ticker': 'AAPL', 'period.fiscal-year': 2023}", default=None, examples=[ {"entity.ticker": "AAPL", "period.fiscal-year": 2023}, {"entity.ticker": "MSFT", "report.document-type": "10-K"}, ], ), ] = None, sort: Annotated[ dict[str, Literal["desc", "asc"]] | str | None, Field( description="Optional dictionary specifying sort order for fields. Keys are field names, values are 'desc' or 'asc'", default=None, examples=[{"period.instant": "desc", "fact.value": "asc"}], ), ] = None, unique: Annotated[ bool, Field( description="Whether to return only unique/distinct results, removing duplicates", default=False, ), ] = False, limit: Annotated[ int, Field( description="Maximum number of results to return. Must be between 1 and 2000", ge=1, le=2000, default=100, ), ] = 100, ) -> DataFrame: """Query XBRL-US API endpoints for financial data and facts This tool provides flexible access to XBRL-US data by allowing queries to various API endpoints with customizable fields, filters, and sorting options. Returns: DataFrame containing the requested XBRL data with specified fields Raises: ValueError: If XBRL authentication is not configured or fields are not provided ValueError: If the API request fails """ xbrl: XBRL = ctx.get_state("xbrl") if xbrl is None: raise ValueError( "XBRL authentication required. Please provide valid credentials in the request." ) if not endpoint.startswith("/"): endpoint = "/" + endpoint if not fields: raise ValueError("fields are required") # Validate and convert parameters if provided try: validated_parameters = validate_and_convert_parameters(parameters) except ValueError as e: raise ValueError( f"Parameter validation failed: {e}\n\n" f"IMPORTANT: Parameters must be provided as a JSON object/dict, not a string.\n" f"Correct format: {{'entity.ticker': 'AAPL', 'period.fiscal-year': 2023}}\n" f"NOT as a string: '{{'entity.ticker': 'AAPL'}}'" ) from e try: validated_sort = validate_and_convert_parameters(sort) except ValueError as e: raise ValueError( f"Sort validation failed: {e}\n\n" f"IMPORTANT: Sort must be provided as a JSON object/dict, not a string.\n" f"Correct format: {{'entity.ticker': 'DESC', 'period.fiscal-year': 'ASC'}}\n" f"NOT as a string: '{{'entity.ticker': 'AAPL'}}'" ) from e try: return xbrl.query( endpoint=endpoint, fields=fields, parameters=validated_parameters, limit=limit, unique=unique, sort=validated_sort, ) except Exception as e: raise ValueError(f"Failed to fetch XBRL data: {e}") @mcp.tool( annotations={ "title": "Get XBRL Endpoint Metadata", "readOnlyHint": True, "openWorldHint": True, "idempotentHint": True, }, # exclude_args=["ctx"], ) async def list_xbrl_endpoints( endpoint: Annotated[ Literal[ "meta", "meta/assertion", "meta/concept", "meta/cube", "meta/document", "meta/dts", "meta/dts/concept", "meta/dts/network", "meta/entity", "meta/entity/report", "meta/fact", "meta/label", "meta/network", "meta/network/relationship", "meta/relationship", "meta/report", "meta/report/fact", "meta/report/network", ], Field( description="The XBRL-US metadata endpoint to query. Use 'meta' for general metadata or specific paths like 'meta/concept' for concept definitions", examples=["meta", "meta/concept", "meta/entity"], ), ], ctx: Context, ) -> dict: """List available XBRL API endpoints Args: endpoint: The XBRL-US API endpoint to query ctx: FastMCP context object containing XBRL authentication state Returns: Response from the XBRL API endpoint Raises: ValueError: If XBRL authentication is not configured """ xbrl: XBRL = ctx.get_state("xbrl") if xbrl is None: raise ValueError( "XBRL authentication required. Please provide valid credentials in the request." ) response = requests.get( url=f"https://api.xbrl.us/api/v1/{endpoint}", headers={"Authorization": f"Bearer {xbrl.access_token}"}, ) response.raise_for_status() return response.json() return mcp

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/hamid-vakilzadeh/xbrl-us-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server