Skip to main content
Glama
berlinbra

AlphaVantage-MCP

get-etf-profile

Retrieve detailed ETF profile data including holdings, sector allocation, and key metrics to analyze investment characteristics and make informed decisions.

Instructions

Get comprehensive ETF profile information including holdings, sector allocation, and key metrics

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
symbolYesETF symbol (e.g., QQQ, SPY, VTI)

Implementation Reference

  • Handler logic for executing the get-etf-profile tool: extracts symbol, makes API request to ETF_PROFILE endpoint, formats response using format_etf_profile, and returns formatted text.
    elif name == "get-etf-profile":
        symbol = arguments.get("symbol")
        
        if not symbol:
            return [types.TextContent(type="text", text="Missing symbol parameter")]
    
        symbol = symbol.upper()
    
        async with httpx.AsyncClient() as client:
            etf_data = await make_alpha_request(
                client,
                "ETF_PROFILE",
                symbol
            )
    
            if isinstance(etf_data, str):
                return [types.TextContent(type="text", text=f"Error: {etf_data}")]
    
            formatted_etf = format_etf_profile(etf_data)
            etf_text = f"ETF profile for {symbol}:\n\n{formatted_etf}"
    
            return [types.TextContent(type="text", text=etf_text)]
  • JSON Schema defining the input parameters for the get-etf-profile tool: requires a 'symbol' string (e.g., QQQ).
    types.Tool(
        name="get-etf-profile",
        description="Get comprehensive ETF profile information including holdings, sector allocation, and key metrics",
        inputSchema={
            "type": "object",
            "properties": {
                "symbol": {
                    "type": "string",
                    "description": "ETF symbol (e.g., QQQ, SPY, VTI)",
                }
            },
            "required": ["symbol"],
        },
    )
  • Helper function that formats the raw ETF profile data from Alpha Vantage API into a human-readable string, including basic info, sector allocation, and top holdings.
    def format_etf_profile(etf_data: Dict[str, Any]) -> str:
        """Format ETF profile data into a concise string.
        
        Args:
            etf_data: The response data from the Alpha Vantage ETF_PROFILE endpoint
            
        Returns:
            A formatted string containing the ETF profile information
        """
        try:
            if "Error Message" in etf_data:
                return f"Error: {etf_data['Error Message']}"
    
            if not etf_data:
                return "No ETF profile data available in the response"
    
            # Extract basic ETF information
            net_assets = etf_data.get("net_assets", "N/A")
            net_expense_ratio = etf_data.get("net_expense_ratio", "N/A")
            portfolio_turnover = etf_data.get("portfolio_turnover", "N/A")
            dividend_yield = etf_data.get("dividend_yield", "N/A")
            inception_date = etf_data.get("inception_date", "N/A")
            leveraged = etf_data.get("leveraged", "N/A")
    
            formatted = [
                f"ETF Profile\n\n",
                f"Basic Information:\n"
            ]
            
            # Format net assets
            if net_assets != "N/A" and net_assets.replace('.', '').isdigit():
                formatted.append(f"Net Assets: ${float(net_assets):,.0f}\n")
            else:
                formatted.append(f"Net Assets: {net_assets}\n")
                
            # Format net expense ratio
            if net_expense_ratio != "N/A" and net_expense_ratio.replace('.', '').replace('-', '').isdigit():
                formatted.append(f"Net Expense Ratio: {float(net_expense_ratio):.3%}\n")
            else:
                formatted.append(f"Net Expense Ratio: {net_expense_ratio}\n")
                
            # Format portfolio turnover
            if portfolio_turnover != "N/A" and portfolio_turnover.replace('.', '').replace('-', '').isdigit():
                formatted.append(f"Portfolio Turnover: {float(portfolio_turnover):.1%}\n")
            else:
                formatted.append(f"Portfolio Turnover: {portfolio_turnover}\n")
                
            # Format dividend yield
            if dividend_yield != "N/A" and dividend_yield.replace('.', '').replace('-', '').isdigit():
                formatted.append(f"Dividend Yield: {float(dividend_yield):.2%}\n")
            else:
                formatted.append(f"Dividend Yield: {dividend_yield}\n")
                
            formatted.extend([
                f"Inception Date: {inception_date}\n",
                f"Leveraged: {leveraged}\n\n"
            ])
    
            # Format sectors if available
            sectors = etf_data.get("sectors", [])
            if sectors:
                formatted.append("Sector Allocation:\n")
                for sector in sectors:
                    sector_name = sector.get("sector", "Unknown")
                    weight = sector.get("weight", "0")
                    try:
                        weight_pct = float(weight) * 100
                        formatted.append(f"{sector_name}: {weight_pct:.1f}%\n")
                    except (ValueError, TypeError):
                        formatted.append(f"{sector_name}: {weight}\n")
                formatted.append("\n")
    
            # Format top holdings if available
            holdings = etf_data.get("holdings", [])
            if holdings:
                formatted.append("Top Holdings:\n")
                # Show top 10 holdings
                for i, holding in enumerate(holdings[:10]):
                    symbol = holding.get("symbol", "N/A")
                    description = holding.get("description", "N/A")
                    weight = holding.get("weight", "0")
                    
                    try:
                        weight_pct = float(weight) * 100
                        formatted.append(f"{i+1:2d}. {symbol} - {description}: {weight_pct:.2f}%\n")
                    except (ValueError, TypeError):
                        formatted.append(f"{i+1:2d}. {symbol} - {description}: {weight}\n")
                
                if len(holdings) > 10:
                    formatted.append(f"\n... and {len(holdings) - 10} more holdings\n")
                formatted.append(f"\nTotal Holdings: {len(holdings)}\n")
    
            return "".join(formatted)
        except Exception as e:
            return f"Error formatting ETF profile data: {str(e)}"
  • General helper function used by all tools to make authenticated HTTP requests to Alpha Vantage API, handles errors, rate limits, and parses responses (used with 'ETF_PROFILE' function for this tool).
    async def make_alpha_request(client: httpx.AsyncClient, function: str, symbol: Optional[str], additional_params: Optional[Dict[str, Any]] = None) -> Dict[str, Any] | str:
        """Make a request to the Alpha Vantage API with proper error handling.
        
        Args:
            client: An httpx AsyncClient instance
            function: The Alpha Vantage API function to call
            symbol: The stock/crypto symbol (can be None for some endpoints)
            additional_params: Additional parameters to include in the request
            
        Returns:
            Either a dictionary containing the API response, or a string with an error message
        """
        params = {
            "function": function,
            "apikey": API_KEY
        }
        
        if symbol:
            params["symbol"] = symbol
            
        if additional_params:
            params.update(additional_params)
    
        try:
            response = await client.get(
                ALPHA_VANTAGE_BASE,
                params=params,
                timeout=30.0
            )
    
            # Check for specific error responses
            if response.status_code == 429:
                return f"Rate limit exceeded. Error details: {response.text}"
            elif response.status_code == 403:
                return f"API key invalid or expired. Error details: {response.text}"
    
            response.raise_for_status()
    
            # Check if response is empty
            if not response.text.strip():
                return "Empty response received from Alpha Vantage API"
            
            # Special handling for EARNINGS_CALENDAR which returns CSV by default
            if function == "EARNINGS_CALENDAR":
                try:
                    # Parse CSV response
                    csv_reader = csv.DictReader(io.StringIO(response.text))
                    earnings_list = list(csv_reader)
                    return earnings_list
                except Exception as e:
                    return f"Error parsing CSV response: {str(e)}"
            
            # For other functions, expect JSON
            try:
                data = response.json()
            except ValueError as e:
                return f"Invalid JSON response from Alpha Vantage API: {response.text[:200]}"
    
            # Check for Alpha Vantage specific error messages
            if "Error Message" in data:
                return f"Alpha Vantage API error: {data['Error Message']}"
            if "Note" in data and "API call frequency" in data["Note"]:
                return f"Rate limit warning: {data['Note']}"
    
            return data
        except httpx.TimeoutException:
            return "Request timed out after 30 seconds. The Alpha Vantage API may be experiencing delays."
        except httpx.ConnectError:
            return "Failed to connect to Alpha Vantage API. Please check your internet connection."
        except httpx.HTTPStatusError as e:
            return f"HTTP error occurred: {str(e)} - Response: {e.response.text}"
        except Exception as e:
            return f"Unexpected error occurred: {str(e)}"
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden but offers minimal behavioral disclosure. It mentions what information is returned but doesn't address critical aspects like data freshness, rate limits, authentication requirements, error conditions, or whether this is a real-time or cached data source. The description is functional but lacks operational context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that communicates the core purpose without unnecessary words. It's appropriately sized for a simple lookup tool, though it could be slightly more front-loaded by starting with the most critical information about ETF data retrieval.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-parameter lookup tool with no output schema, the description provides adequate context about what information is returned but lacks details about the return format, data structure, or potential limitations. Given the simplicity of the tool and good schema coverage, it's minimally viable but could benefit from more complete operational guidance.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% with the single 'symbol' parameter well-documented in the schema. The description adds no additional parameter information beyond what the schema provides, but doesn't need to compensate for gaps. The baseline 3 is appropriate when the schema adequately documents parameters.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Get') and resource ('ETF profile information') with specific details about what information is included (holdings, sector allocation, key metrics). It distinguishes from sibling tools by focusing on ETFs rather than stocks, crypto, or earnings data, though it doesn't explicitly name alternatives.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for ETF data retrieval, but provides no explicit guidance on when to use this tool versus alternatives like get-stock-quote or get-company-info for similar financial data needs. There's no mention of prerequisites, limitations, or specific scenarios where this tool is preferred.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/berlinbra/alpha-vantage-mcp'

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