Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
8e2b4d5
feat: add 10 code-quality and workflow enhancements
rpwallin Mar 12, 2026
9f4772a
feat: add WebSocket price streaming and options chain analysis
rpwallin Mar 13, 2026
728a855
feat: integrate Finnhub API for alternative data and backup source
rpwallin Mar 13, 2026
0b462c3
fix: add SQL safety, credential masking, and security hardening
rpwallin Mar 13, 2026
348d491
feat: add automatic cache cleanup, atexit hooks, and aiolimiter
rpwallin Mar 13, 2026
f4d17e3
test: add comprehensive router and core module test coverage (154 tests)
rpwallin Mar 14, 2026
7ec8e7e
feat: integrate Pandera for DataFrame validation at provider boundaries
rpwallin Mar 14, 2026
7522959
refactor: replace custom circuit breaker with circuitbreaker library
rpwallin Mar 14, 2026
2c253ce
fix: address 7 critical/high findings from code quality audit
rpwallin Mar 14, 2026
6fe2ed9
refactor: implement 5 medium-priority code quality improvements
rpwallin Mar 14, 2026
4ba371d
refactor: convert f-string logging to lazy formatting, remove dead co…
rpwallin Mar 14, 2026
5ffa5f4
fix: portfolio KeyError and deprecated matplotlib style
rpwallin Mar 15, 2026
3ac3227
fix: consolidate all unmerged bug fixes from orphaned worktree branches
rpwallin Mar 15, 2026
47624b9
fix: merge remaining unique fixes from orphaned worktree branches
rpwallin Mar 15, 2026
f04ab2c
merge: integrate keen-jones worktree (modernization, finnhub, options…
rpwallin Mar 15, 2026
faeee8d
fix: merge remaining unique changes from orphaned worktrees
rpwallin Mar 15, 2026
5c13d45
fix: resolve duplicate register_finnhub_tools and improve LLM fallbac…
rpwallin Mar 15, 2026
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
13 changes: 13 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"permissions": {
"allow": [
"Bash(gh repo:*)",
"Bash(export PATH=\"$PATH:/c/Program Files/GitHub CLI\")",
"Bash(gh --version)",
"Bash(gh auth:*)",
"Bash(gh pr:*)",
"Bash(echo \"exit:$?\")",
"Bash(git add:*)"
]
}
}
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ SENTRY_DSN=https://your_key@sentry.io/project_id
# Get free Tiingo key at https://tiingo.com (500 requests/day free)
TIINGO_API_KEY=your_tiingo_api_key_here

# Finnhub API Key (recommended - alternative data, earnings, analyst recommendations)
# Get free key at https://finnhub.io (60 calls/min free)
# FINNHUB_API_KEY=your_finnhub_api_key_here

# Optional API Keys
# FRED_API_KEY=your_fred_api_key_here
# OPENAI_API_KEY=your_openai_api_key_here
Expand Down
42 changes: 42 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: CI

on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v5

- name: Install dependencies
run: uv sync --extra dev

- name: Ruff lint
run: uv run ruff check .

- name: Ruff format check
run: uv run ruff format --check .

test:
runs-on: ubuntu-latest
needs: lint
env:
MAVERICK_TEST_ENV: "true"
steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v5

- name: Install dependencies
run: uv sync --extra dev

- name: Run unit tests
run: uv run pytest -v --tb=short
7 changes: 7 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.10
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
12 changes: 11 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Maverick-MCP Makefile
# Central command interface for agent-friendly development

.PHONY: help dev dev-sse dev-http dev-stdio stop test test-all test-watch test-specific test-parallel test-cov test-speed test-speed-quick test-speed-emergency test-speed-comparison test-strategies lint format typecheck clean tail-log backend check migrate setup redis-start redis-stop experiment experiment-once benchmark-parallel benchmark-speed docker-up docker-down docker-logs
.PHONY: help dev dev-sse dev-http dev-stdio stop test test-all test-watch test-specific test-parallel test-cov test-speed test-speed-quick test-speed-emergency test-speed-comparison test-strategies lint format typecheck clean tail-log backend check ci pre-commit-install migrate setup redis-start redis-stop experiment experiment-once benchmark-parallel benchmark-speed docker-up docker-down docker-logs

# Default target
help:
Expand Down Expand Up @@ -154,6 +154,16 @@ typecheck:
check: lint typecheck
@echo "All checks passed!"

ci:
@echo "Running CI checks locally..."
@uv run ruff check .
@uv run ruff format --check .
@uv run pytest -v --tb=short

pre-commit-install:
@echo "Installing pre-commit hooks..."
@uv run pre-commit install

# Utility commands
tail-log:
@echo "Following backend logs (Ctrl+C to stop)..."
Expand Down
24 changes: 11 additions & 13 deletions maverick_mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,29 +61,27 @@ To start the Maverick-MCP server:

```bash
# Recommended: Use the Makefile
make dev
make dev # Streamable-HTTP transport (default dev server)
make dev-stdio # STDIO transport (recommended for Claude Desktop)
make dev-sse # SSE transport (legacy, for clients that require it)

# Alternative: Direct FastMCP server
python -m maverick_mcp.api.server --transport streamable-http --port 8003

# Development mode with hot reload
./scripts/dev.sh
# Alternative: Direct command
python -m maverick_mcp.api.server --transport stdio
```

Note: The server will start using streamable-http transport on port 8003. The streamable-http transport is compatible with mcp-remote, while SSE transport is not (SSE requires GET requests but mcp-remote sends POST requests).

When the server starts, you can access it at:

- http://localhost:8003
**Transport guidance** (see CLAUDE.md for full details):
- **STDIO** (recommended for Claude Desktop): Direct subprocess communication, fastest and most reliable
- **Streamable-HTTP** (`make dev`): Default dev server on port 8003, use with mcp-remote bridge
- **SSE** (`make dev-sse`): Legacy transport for clients with native SSE support

You can also start the server programmatically:

```python
from maverick_mcp.api.server import mcp

# Start the server with SSE transport
# Start with STDIO transport (recommended for Claude Desktop)
# NOTE: All financial analysis tools include appropriate disclaimers
mcp.run(transport="sse")
mcp.run(transport="stdio")
```

## Financial Analysis Tools
Expand Down
36 changes: 22 additions & 14 deletions maverick_mcp/agents/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ class PersonaAwareTool(BaseTool):

persona: InvestorPersona | None = None
# State tracking
last_analysis_time: dict[str, datetime] = {}
analyzed_stocks: dict[str, dict] = {}
key_price_levels: dict[str, dict] = {}
last_analysis_time: dict[str, datetime] = Field(default_factory=dict)
analyzed_stocks: dict[str, dict] = Field(default_factory=dict)
key_price_levels: dict[str, dict] = Field(default_factory=dict)
# Cache settings
cache_ttl: int = settings.agent.agent_cache_ttl_seconds

Expand Down Expand Up @@ -416,8 +416,8 @@ def _agent_node(self, state: dict[str, Any]) -> dict[str, Any]:
system_prompt = self._build_system_prompt()
messages = [SystemMessage(content=system_prompt)] + messages

# Call the LLM
if self.tools:
# Call the LLM — guard against FakeListLLM which lacks bind_tools
if self.tools and hasattr(self.llm, "bind_tools"):
response = self.llm.bind_tools(self.tools).invoke(messages)
else:
response = self.llm.invoke(messages)
Expand Down Expand Up @@ -466,18 +466,23 @@ def get_state_schema(self) -> type:
"""
return BaseAgentState

async def ainvoke(self, query: str, session_id: str, **kwargs) -> dict[str, Any]:
async def ainvoke(
self, query: str, session_id: str, timeout: float = 30.0, **kwargs
) -> dict[str, Any]:
"""
Invoke the agent asynchronously.

Args:
query: User query
session_id: Session identifier
timeout: Maximum seconds to wait for completion (default 30s)
**kwargs: Additional parameters

Returns:
Agent response
"""
import asyncio

config = {
"configurable": {"thread_id": session_id, "persona": self.persona.name}
}
Expand All @@ -486,14 +491,17 @@ async def ainvoke(self, query: str, session_id: str, **kwargs) -> dict[str, Any]
if "config" in kwargs:
config.update(kwargs["config"])

# Run the graph
result = await self.graph.ainvoke(
{
"messages": [HumanMessage(content=query)],
"persona": self.persona.name,
"session_id": session_id,
},
config=config,
# Run the graph with timeout protection
result = await asyncio.wait_for(
self.graph.ainvoke(
{
"messages": [HumanMessage(content=query)],
"persona": self.persona.name,
"session_id": session_id,
},
config=config,
),
timeout=timeout,
)

return self._extract_response(result)
Expand Down
2 changes: 1 addition & 1 deletion maverick_mcp/api/api_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
RequestTracingMiddleware,
)
from maverick_mcp.api.middleware.security import SecurityHeadersMiddleware
from maverick_mcp.api.routers.health import router as health_router
from maverick_mcp.api.routers.health_enhanced import router as health_router
from maverick_mcp.config.settings import settings
from maverick_mcp.utils.logging import get_logger

Expand Down
4 changes: 4 additions & 0 deletions maverick_mcp/api/error_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,10 @@ def _log_error(
log_data["context"] = context

# Log at appropriate level
# SECURITY NOTE: exc_info=True includes full stack traces which may
# contain API keys or credentials in local variables. Acceptable for
# personal-use server where logs stay local. For production/shared
# environments, consider exc_info=False or a sanitising log formatter.
log_level = error_info["log_level"]
if log_level == "error":
logger.error(
Expand Down
Loading