Skip to content
Merged
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
44 changes: 44 additions & 0 deletions .github/workflows/fred.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: fred

# GitHub Actions only reads workflows from the repository root, so every job here
# sets working-directory to the plugin's subdirectory. The paths filters keep a
# change to one plugin from running another's suite.
on:
pull_request:
paths:
- "plugins/fred/**"
- ".github/workflows/fred.yml"
push:
branches: [main]
paths:
- "plugins/fred/**"
- ".github/workflows/fred.yml"

defaults:
run:
working-directory: plugins/fred

jobs:
check:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.13"]

steps:
- uses: actions/checkout@v4

- uses: astral-sh/setup-uv@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: uv sync

- name: Lint & typecheck
run: make check

# No API key and no network: the integration suite drives a mock FRED app
# in-process over an injected httpx transport.
- name: Coverage (unit + integration)
run: make coverage
18 changes: 18 additions & 0 deletions plugins/fred/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "fred",
"version": "0.1.0",
"description": "MCP server for the FRED API: economic time series from the St. Louis Fed, with search, aligned multi-series observations, revision history, and the release calendar.",
"author": {
"name": "Walker Hughes"
},
"license": "MIT",
"homepage": "https://github.com/walkerhughes/claude/tree/main/plugins/fred",
"repository": "https://github.com/walkerhughes/claude",
"keywords": [
"fred",
"economics",
"macro",
"time-series",
"mcp"
]
}
14 changes: 14 additions & 0 deletions plugins/fred/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# The API key normally lives in ~/.fred-mcp/credentials.json as {"api_key": "..."};
# this file is for CI and scripting, where an environment is easier to inject.
#
# Nothing sources this automatically. Export what you need, e.g.
# set -a && source .env && set +a
#
# Free key, no cost and no card: https://fredaccount.stlouisfed.org/apikeys
FRED_API_KEY="<your FRED_API_KEY here>"

# Optional; defaults to https://api.stlouisfed.org/fred.
# FRED_BASE_URL="https://api.stlouisfed.org/fred"

# Optional logging level (stderr only; stdout is the MCP channel).
# FRED_LOG_LEVEL="INFO"
16 changes: 16 additions & 0 deletions plugins/fred/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info

# Virtual environments
.venv

# Environment variables
.env

# Coverage
.coverage
13 changes: 13 additions & 0 deletions plugins/fred/.mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"mcpServers": {
"fred": {
"type": "stdio",
"command": "bash",
"args": ["${CLAUDE_PLUGIN_ROOT}/scripts/start-server.sh"],
"env": {
"FRED_API_KEY": "${FRED_API_KEY:-}",
"FRED_LOG_LEVEL": "${FRED_LOG_LEVEL:-}"
}
}
}
}
1 change: 1 addition & 0 deletions plugins/fred/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.13
27 changes: 27 additions & 0 deletions plugins/fred/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.PHONY: lint lint-fix format typecheck check test test-unit test-integration coverage

lint:
uv run ruff check .

lint-fix:
uv run ruff check . --fix

format:
uv run ruff format .

typecheck:
uv run mypy src/

check: lint typecheck test-unit

test:
uv run pytest

test-unit:
uv run pytest -m unit

test-integration:
uv run pytest -m integration

coverage:
uv run pytest --cov --cov-report=term-missing
52 changes: 52 additions & 0 deletions plugins/fred/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
[project]
name = "fred-mcp"
version = "0.1.0"
description = "MCP server for the FRED economic data API"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
# 2.0 renamed FastMCP to MCPServer; the decorator API is otherwise the same.
# tastytrade is still on 1.x, so the two plugins differ here on purpose.
"mcp[cli]>=2.0",
"httpx>=0.27",
"pydantic>=2.0",
]

# Not an installable package: the server runs as `python -m src.server` from the
# project root. Declaring it as one would need a build backend and a console
# script, neither of which anything here calls.
[tool.uv]
package = false

[dependency-groups]
dev = [
"pytest>=8.0",
"pytest-asyncio>=0.23",
"pytest-cov>=6.0",
"ruff>=0.9",
"mypy>=1.0",
]

[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
markers = [
"unit: Unit tests (deselect with '-m \"not unit\"')",
"integration: Integration tests (deselect with '-m \"not integration\"')",
]
addopts = "-v"

[tool.ruff]
line-length = 120
target-version = "py313"

[tool.ruff.lint]
select = ["E", "F", "I", "W"]

[tool.coverage.run]
source = ["src"]
omit = ["tests/*"]

[tool.coverage.report]
fail_under = 80
show_missing = true
54 changes: 54 additions & 0 deletions plugins/fred/scripts/start-server.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env bash
# Launch the fred MCP server.
#
# Exists because "command": "uv" in .mcp.json assumes uv is on whatever PATH the
# MCP client spawns with. It often is not: a Homebrew uv lives in
# /opt/homebrew/bin, which is missing from the minimal PATH some launch contexts
# provide, and the failure surfaces only as an opaque JSON-RPC -32000.
#
# Diagnostics go to stderr. stdout is the JSON-RPC channel and must stay clean.
set -euo pipefail

# Resolve the plugin root from this script's own location rather than
# CLAUDE_PLUGIN_ROOT, so it works however the server is launched.
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"

find_uv() {
if command -v uv >/dev/null 2>&1; then
command -v uv
return
fi
# ponytail: fixed candidate list beats parsing shell profiles. Covers Homebrew
# on Apple Silicon and Intel, the standalone installer, and cargo install.
local candidate
for candidate in \
/opt/homebrew/bin/uv \
/usr/local/bin/uv \
"$HOME/.local/bin/uv" \
"$HOME/.cargo/bin/uv"
do
if [ -x "$candidate" ]; then
printf '%s\n' "$candidate"
return
fi
done
return 1
}

if ! UV="$(find_uv)"; then
echo "fred-mcp: cannot find the 'uv' executable." >&2
echo " Searched PATH plus /opt/homebrew/bin, /usr/local/bin, ~/.local/bin, ~/.cargo/bin." >&2
echo " Install it (https://docs.astral.sh/uv/) or symlink it into /usr/local/bin." >&2
exit 127
fi

# Drop any inherited VIRTUAL_ENV. uv already ignores a mismatched one, but it
# warns loudly on stderr about "does not match the project environment path",
# which reads like the cause of a failure.
unset VIRTUAL_ENV

# Run as a module from the project root: pyproject sets tool.uv.package = false,
# so there is no installed console script to call. The API key resolves from the
# environment or an absolute path under $HOME, so this cd does not affect it.
cd "$ROOT"
exec "$UV" run --project "$ROOT" python -m src.server
Empty file added plugins/fred/src/__init__.py
Empty file.
Loading
Loading