Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ path to `uvx`.

```bash
# Claude CLI
claude mcp add massive -e MASSIVE_API_KEY=your_api_key_here -- uvx --from git+https://github.com/massive-com/mcp_massive@v0.6.0 mcp_massive
claude mcp add massive -e MASSIVE_API_KEY=your_api_key_here -- uvx --from git+https://github.com/massive-com/mcp_massive@v0.7.0 mcp_massive
```

This command will install the MCP server in your current project.
Expand Down Expand Up @@ -83,7 +83,7 @@ Make sure you complete the various fields.
"command": "<path_to_your_uvx_install>/uvx",
"args": [
"--from",
"git+https://github.com/massive-com/mcp_massive@v0.6.0",
"git+https://github.com/massive-com/mcp_massive@v0.7.0",
"mcp_massive"
],
"env": {
Expand Down Expand Up @@ -130,12 +130,36 @@ This MCP server implements all Massive.com API endpoints as tools, including:
- `get_last_trade` - Latest trade for a symbol
- `list_ticker_news` - Recent news articles for tickers
- `get_snapshot_ticker` - Current market snapshot for a ticker
- `list_snapshot_options_chain` - Option chain snapshot with greeks and market data
- `get_market_status` - Current market status and trading hours
- `list_stock_financials` - Fundamental financial data
- And many more...

Each tool follows the Massive.com SDK parameter structure while converting responses to standard JSON that LLMs can easily process.

### Output Filtering

Some tools support output filtering to reduce response size and token usage. These tools accept additional parameters:

| Parameter | Description |
|-----------|-------------|
| `fields` | Comma-separated field names or a preset (e.g., `"ticker,close"` or `"preset:greeks"`) |
| `output_format` | Output format: `"csv"` (default), `"json"`, or `"compact"` |
| `aggregate` | Return only `"first"` or `"last"` record |

**Available field presets:**

| Preset | Fields |
|--------|--------|
| `price` | ticker, close, timestamp |
| `ohlcv` | ticker, open, high, low, close, volume, timestamp |
| `summary` | ticker, close, volume, change_percent |
| `greeks` | details_ticker, details_strike_price, details_expiration_date, details_contract_type, greeks_delta, greeks_gamma, greeks_theta, greeks_vega, implied_volatility |
| `options_summary` | details_ticker, details_strike_price, details_expiration_date, details_contract_type, day_close, day_open, day_volume, open_interest, implied_volatility |
| `options_quote` | details_ticker, details_strike_price, details_contract_type, last_quote_bid, last_quote_ask, last_quote_bid_size, last_quote_ask_size |

Example: `fields="preset:greeks"` returns only the greek values for options contracts.

## Development

### Running Locally
Expand Down
1 change: 1 addition & 0 deletions entrypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Backwards-compatible entrypoint script.
This script delegates to the main package CLI entry point.
"""

from mcp_massive import main

if __name__ == "__main__":
Expand Down
8 changes: 6 additions & 2 deletions src/mcp_massive/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,12 @@ def main() -> None:
if massive_api_key:
print("Starting Massive MCP server with API key configured.")
elif polygon_api_key:
print("Warning: POLYGON_API_KEY is deprecated. Please migrate to MASSIVE_API_KEY.")
print("Starting Massive MCP server with API key configured (using deprecated POLYGON_API_KEY).")
print(
"Warning: POLYGON_API_KEY is deprecated. Please migrate to MASSIVE_API_KEY."
)
print(
"Starting Massive MCP server with API key configured (using deprecated POLYGON_API_KEY)."
)
# Set MASSIVE_API_KEY from POLYGON_API_KEY for backward compatibility
os.environ["MASSIVE_API_KEY"] = polygon_api_key
else:
Expand Down
222 changes: 222 additions & 0 deletions src/mcp_massive/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
"""
Output filtering module for MCP Massive server.

This module provides server-side filtering capabilities to reduce context token usage
by allowing field selection, output format selection, and row aggregation.
"""

import json
from dataclasses import dataclass
from typing import Optional, List, Dict, Any, Literal


# Field presets for common use cases
FIELD_PRESETS = {
# Price presets
"price": ["ticker", "close", "timestamp"],
"last_price": ["close"],
# OHLC presets
"ohlc": ["ticker", "open", "high", "low", "close", "timestamp"],
"ohlcv": ["ticker", "open", "high", "low", "close", "volume", "timestamp"],
# Summary presets
"summary": ["ticker", "close", "volume", "change_percent"],
"minimal": ["ticker", "close"],
# Volume presets
"volume": ["ticker", "volume", "timestamp"],
# Details presets
"details": ["ticker", "name", "market", "locale", "primary_exchange"],
"info": ["ticker", "name", "description", "homepage_url"],
# News presets
"news_headlines": ["title", "published_utc", "author"],
"news_summary": ["title", "description", "published_utc", "article_url"],
# Trade presets
"trade": ["price", "size", "timestamp"],
"quote": ["bid", "ask", "bid_size", "ask_size", "timestamp"],
# Options presets (field names are flattened from nested API response)
"greeks": [
"details_ticker",
"details_strike_price",
"details_expiration_date",
"details_contract_type",
"greeks_delta",
"greeks_gamma",
"greeks_theta",
"greeks_vega",
"implied_volatility",
],
"options_summary": [
"details_ticker",
"details_strike_price",
"details_expiration_date",
"details_contract_type",
"day_close",
"day_open",
"day_volume",
"open_interest",
"implied_volatility",
],
"options_quote": [
"details_ticker",
"details_strike_price",
"details_contract_type",
"last_quote_bid",
"last_quote_ask",
"last_quote_bid_size",
"last_quote_ask_size",
],
}


@dataclass
class FilterOptions:
"""Options for filtering MCP tool outputs."""

# Field selection
fields: Optional[List[str]] = None # Include only these fields
exclude_fields: Optional[List[str]] = None # Exclude these fields

# Output format
format: Literal["csv", "json", "compact"] = "csv"

# Aggregation
aggregate: Optional[Literal["first", "last"]] = None

# Row filtering (future enhancement)
conditions: Optional[Dict[str, Any]] = None # {"volume_gt": 1000000}


def parse_filter_params(
fields: Optional[str] = None,
output_format: str = "csv",
aggregate: Optional[str] = None,
) -> FilterOptions:
"""
Parse tool parameters into FilterOptions.

Args:
fields: Comma-separated field names or preset name (e.g., "ticker,close" or "preset:price")
output_format: Desired output format ("csv", "json", or "compact")
aggregate: Aggregation method ("first", "last", or None)

Returns:
FilterOptions instance
"""
# Parse fields parameter
field_list = None
if fields:
# Check if it's a preset
if fields.startswith("preset:"):
preset_name = fields[7:] # Remove "preset:" prefix
field_list = FIELD_PRESETS.get(preset_name)
if field_list is None:
raise ValueError(
f"Unknown preset: {preset_name}. Available presets: {', '.join(FIELD_PRESETS.keys())}"
)
else:
# Parse comma-separated fields
field_list = [f.strip() for f in fields.split(",") if f.strip()]

# Validate output format
if output_format not in ["csv", "json", "compact"]:
raise ValueError(
f"Invalid output_format: {output_format}. Must be 'csv', 'json', or 'compact'"
)

# Validate aggregate
if aggregate and aggregate not in ["first", "last"]:
raise ValueError(
f"Invalid aggregate: {aggregate}. Must be 'first', 'last', or None"
)

return FilterOptions(
fields=field_list,
format=output_format,
aggregate=aggregate,
)


def apply_filters(data: dict | str, options: FilterOptions) -> str:
"""
Apply filtering to API response data.

Args:
data: JSON string or dict from Massive API
options: Filtering options to apply

Returns:
Filtered and formatted string response
"""
# Import formatters here to avoid circular imports
from .formatters import (
json_to_csv_filtered,
json_to_compact,
json_to_json_filtered,
)

# Parse JSON if it's a string
if isinstance(data, str):
parsed_data = json.loads(data)
else:
parsed_data = data

# Apply aggregation if specified
if options.aggregate:
parsed_data = _apply_aggregation(parsed_data, options.aggregate)

# Route to appropriate formatter based on output format
if options.format == "csv":
return json_to_csv_filtered(
parsed_data,
fields=options.fields,
exclude_fields=options.exclude_fields,
)
elif options.format == "json":
return json_to_json_filtered(
parsed_data,
fields=options.fields,
)
elif options.format == "compact":
return json_to_compact(
parsed_data,
fields=options.fields,
)
else:
raise ValueError(f"Unsupported format: {options.format}")


def _apply_aggregation(data: dict | list, method: str) -> dict | list:
"""
Apply aggregation to extract a single record.

Args:
data: JSON data (dict or list)
method: Aggregation method ("first" or "last")

Returns:
Aggregated data
"""
# Extract records
if isinstance(data, dict) and "results" in data:
records = data["results"]
elif isinstance(data, list):
records = data
else:
# Single record, return as-is
return data

if not records:
return data

# Apply aggregation
if method == "first":
aggregated_record = records[0]
elif method == "last":
aggregated_record = records[-1]
else:
raise ValueError(f"Unknown aggregation method: {method}")

# Preserve structure
if isinstance(data, dict) and "results" in data:
return {**data, "results": [aggregated_record]}
else:
return [aggregated_record]
Loading