Skip to content

Commit 47e1d05

Browse files
alexpipipiclaude
andcommitted
test+fix: 34 e2e tests, fix intraday + market_cap_series, add agent frontmatter (0.4.2)
Added comprehensive test suite (stdlib-only, runs in CI when EODHD_API_TOKEN secret is present): - tests/test_python_client.py — 30 e2e cases hitting every endpoint in SUPPORTED_ENDPOINTS against live EODHD API. Each case validates exit code, JSON response, and reports shape. Subscription-gated endpoints classified as SKIP (not FAIL). - tests/test_mcp_v1.py — full MCP protocol e2e: v2 OAuth challenge (must return 401), v1 initialize handshake (captures session ID), notifications/initialized, tools/list (verified 80 tools live), tools/call (invokes get_exchanges_list). - tests/test_skill_references.py — static cross-reference validation: SKILL.md frontmatter, endpoint-doc links, eodhd_client.py example endpoints, slash-command skill refs, agent frontmatter, plugin.json capabilities.tools vs live MCP tool count. Bugs surfaced and fixed: - skills/eodhd-api/scripts/eodhd_client.py: --endpoint intraday now converts YYYY-MM-DD --from-date/--to-date to Unix timestamps (API rejects ISO date format for this endpoint with HTTP 422). - skills/eodhd-api/scripts/market_cap_series.py: SharesOutstanding lives at SharesStats::* not Highlights::*. Also handles scalar return from "Field::Subfield" filter (was assuming dict). - agents/financial-analyst.md: added YAML frontmatter (name + description) — same class of bug as the SKILL.md fix in PR #5. - .claude-plugin/plugin.json + README.md: capabilities.tools 75 → 80 to match actual MCP v1 tool count (verified via tools/list). CI changes (.github/workflows/validate.yml): - Static test (test_skill_references.py) runs on every push/PR. - e2e tests run only if EODHD_API_TOKEN secret is configured. - Forks / external PRs without secret access skip e2e (no false negatives). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f27f4b1 commit 47e1d05

10 files changed

Lines changed: 763 additions & 36 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
},
77
"metadata": {
88
"description": "Developer-first financial data for Claude — 150k+ tickers, 70+ exchanges, MCP Server with OAuth, 7 curated workflow skills, Python client, 72 endpoint docs",
9-
"version": "0.4.1"
9+
"version": "0.4.2"
1010
},
1111
"plugins": [
1212
{

.claude-plugin/plugin.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "eodhd-claude-skills",
3-
"version": "0.4.1",
3+
"version": "0.4.2",
44
"description": "Developer-first financial data for Claude — 150k+ tickers, 70+ exchanges, real-time & historical prices, fundamentals, options, technicals, news, sentiment, macro indicators, ESG, and more via EODHD API and MCP Server.",
55
"author": {
66
"name": "EOD Historical Data",
@@ -34,7 +34,7 @@
3434
}
3535
},
3636
"capabilities": {
37-
"tools": 75,
37+
"tools": 80,
3838
"tickers": "150,000+",
3939
"exchanges": "70+",
4040
"endpoints": 72,

.github/workflows/validate.yml

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
- name: Validate plugin.json schema
2222
run: |
2323
python3 - <<'PY'
24-
import json, sys
24+
import json
2525
m = json.load(open('.claude-plugin/plugin.json'))
2626
assert isinstance(m.get('name'), str), 'name must be string'
2727
assert isinstance(m.get('version'), str), 'version must be string'
@@ -43,34 +43,12 @@ jobs:
4343
print(f'version sync OK: {p}')
4444
PY
4545
46-
- name: Validate SKILL.md frontmatter
47-
run: |
48-
pip install pyyaml --quiet
49-
python3 - <<'PY'
50-
import yaml, sys, pathlib
51-
fails = []
52-
for skill in pathlib.Path('skills').glob('*/SKILL.md'):
53-
text = skill.read_text()
54-
if not text.startswith('---'):
55-
fails.append(f'{skill}: missing YAML frontmatter')
56-
continue
57-
fm_end = text.find('---', 3)
58-
if fm_end == -1:
59-
fails.append(f'{skill}: unterminated frontmatter')
60-
continue
61-
try:
62-
fm = yaml.safe_load(text[3:fm_end])
63-
except Exception as e:
64-
fails.append(f'{skill}: YAML parse error: {e}')
65-
continue
66-
for k in ('name', 'description'):
67-
if k not in fm:
68-
fails.append(f'{skill}: missing required key "{k}"')
69-
if fails:
70-
print('\n'.join(fails))
71-
sys.exit(1)
72-
print('all SKILL.md frontmatter OK')
73-
PY
46+
- name: Static skill/agent/command references
47+
run: python3 tests/test_skill_references.py
48+
env:
49+
# If repo has an EODHD_API_TOKEN secret, the manifest tool-count check
50+
# also cross-validates against live MCP server. Missing secret = check skipped.
51+
EODHD_API_TOKEN: ${{ secrets.EODHD_API_TOKEN }}
7452

7553
mcp-endpoint:
7654
runs-on: ubuntu-latest
@@ -95,3 +73,34 @@ jobs:
9573
000) echo "FAIL: connection error"; exit 1 ;;
9674
*) echo "WARN: unexpected $CODE — review manually"; exit 0 ;;
9775
esac
76+
77+
e2e-python-client:
78+
# Run only when EODHD_API_TOKEN secret is configured in repo settings.
79+
# On forks / un-secret PRs, this job is skipped (no false negatives).
80+
runs-on: ubuntu-latest
81+
if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }}
82+
steps:
83+
- uses: actions/checkout@v4
84+
- name: Check token availability
85+
id: token
86+
env:
87+
EODHD_API_TOKEN: ${{ secrets.EODHD_API_TOKEN }}
88+
run: |
89+
if [ -n "$EODHD_API_TOKEN" ]; then
90+
echo "have=true" >> "$GITHUB_OUTPUT"
91+
else
92+
echo "have=false" >> "$GITHUB_OUTPUT"
93+
echo "::warning::EODHD_API_TOKEN secret not set — e2e tests skipped"
94+
fi
95+
96+
- name: Run Python client e2e against live API
97+
if: steps.token.outputs.have == 'true'
98+
env:
99+
EODHD_API_TOKEN: ${{ secrets.EODHD_API_TOKEN }}
100+
run: python3 tests/test_python_client.py
101+
102+
- name: Run MCP v1 + v2 e2e
103+
if: steps.token.outputs.have == 'true'
104+
env:
105+
EODHD_API_TOKEN: ${{ secrets.EODHD_API_TOKEN }}
106+
run: python3 tests/test_mcp_v1.py

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Developer-first financial data for Claude — 150,000+ tickers across 70+ exchan
66
77
## Highlights
88

9-
- **MCP Server v2** with OAuth 2.0 — 75 tools, 100+ embedded docs, smart ticker resolution
9+
- **MCP Server v2** with OAuth 2.0 — 80 tools, 100+ embedded docs, smart ticker resolution
1010
- **7 curated workflow skills** — company briefs, earnings, market overview, screening, risk, macro, options
1111
- **Financial analyst agent** — autonomous multi-source analysis
1212
- **5 slash commands**`/eodhd-analyze`, `/eodhd-compare`, `/eodhd-market`, `/eodhd-screen`, `/eodhd-macro`
@@ -168,7 +168,7 @@ The `agents/financial-analyst.md` agent autonomously:
168168
## MCP Server
169169

170170
The EODHD MCP Server v2 provides:
171-
- **75 tools** across 15 categories (prices, fundamentals, options, technicals, news, sentiment, macro, etc.)
171+
- **80 tools** across 15 categories (prices, fundamentals, options, technicals, news, sentiment, macro, etc.)
172172
- **OAuth 2.0** authentication
173173
- **100+ embedded docs** as MCP resources
174174
- **3 prompt templates** for common workflows

agents/financial-analyst.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
---
2+
name: financial-analyst
3+
description: >-
4+
Autonomous financial analyst powered by EODHD market data. Performs
5+
multi-source equity research — fundamentals, valuation, technical
6+
analysis, risk metrics, macro context, sentiment, and screening.
7+
Use when the user asks for an investment thesis, in-depth company
8+
analysis, portfolio risk review, or any task that benefits from
9+
combining several EODHD endpoints into a coherent report.
10+
---
11+
112
# Agent: Financial Analyst
213

314
## Identity

skills/eodhd-api/scripts/eodhd_client.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
from __future__ import annotations
8686

8787
import argparse
88+
import datetime
8889
import json
8990
import os
9091
import sys
@@ -382,6 +383,17 @@ def main() -> int:
382383
if args.interval:
383384
params["interval"] = args.interval
384385

386+
# Intraday endpoint requires Unix timestamps for from/to (not YYYY-MM-DD)
387+
if args.endpoint == "intraday":
388+
for key in ("from", "to"):
389+
value = params.get(key)
390+
if isinstance(value, str) and "-" in value:
391+
try:
392+
dt = datetime.datetime.strptime(value, "%Y-%m-%d")
393+
params[key] = int(dt.replace(tzinfo=datetime.timezone.utc).timestamp())
394+
except ValueError:
395+
pass # Leave unchanged; API will surface the error
396+
385397
# Technical indicators
386398
if args.function:
387399
params["function"] = args.function

skills/eodhd-api/scripts/market_cap_series.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
77
Data sources (EODHD API):
88
- /eod/{SYMBOL} → daily OHLCV (close price)
9-
- /fundamentals/{SYMBOL} → SharesOutstanding from Highlights
9+
- /fundamentals/{SYMBOL} → SharesOutstanding from SharesStats
1010
1111
For US stocks only, an alternative --method=api flag uses the dedicated
1212
/historical-market-cap/{SYMBOL} endpoint (weekly frequency, from 2019).
@@ -67,10 +67,13 @@ def get_shares_outstanding(symbol: str, token: str) -> float | None:
6767
params = urllib.parse.urlencode({
6868
"api_token": token,
6969
"fmt": "json",
70-
"filter": "Highlights::SharesOutstanding",
70+
"filter": "SharesStats::SharesOutstanding",
7171
})
7272
url = f"{BASE_URL}/fundamentals/{symbol}?{params}"
7373
data = fetch_json(url)
74+
# A "Field::Subfield" filter returns the scalar value directly, not a dict
75+
if isinstance(data, (int, float)):
76+
return float(data)
7477
if isinstance(data, dict):
7578
if "error" in data:
7679
raise RuntimeError(f"Fundamentals API error: {data['error']}")

0 commit comments

Comments
 (0)