AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Schwab Marketdata Ops En

skill-kevinkda-schwab-marketdata-skill-schwab-marketdata-ops-en · by kevinkda

|

No reviews yet
0 installs
16 views
0.0% view→install

Install

$ agentstack add skill-kevinkda-schwab-marketdata-skill-schwab-marketdata-ops-en

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-kevinkda-schwab-marketdata-skill-schwab-marketdata-ops-en)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
1mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Schwab Marketdata Ops En? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

schwab-marketdata-ops-en

> Responsibility disclaimer: This skill drives the Schwab Market Data > Production API. Users are required to read > and > , and bear sole responsibility for > compliance of their usage. The authors of schwab-marketdata-mcp and > schwab-marketdata-skill are not liable for any usage that violates the > Schwab Terms of Service.

Activation handshake (run first)

When activating this skill, the first step must be a call to get_server_info to confirm that the returned server_version falls within the frontmatter compatible_mcp_version range:

get_server_info()
→ { "server_version": "0.1.x", "supported_tools": [...12 names...] }

If the version does not match or the call fails: stop immediately, tell the user to upgrade either schwab-marketdata-mcp or this skill, and do not continue any business calls.

Full handshake (including token health check):

1. get_server_info()  → verify server_version ∈ compatible_mcp_version
2. health_check()     → verify token_state == "valid"
                       and token_expires_in_days >= 0.5
3. If either step fails → stop immediately and follow the references
   to repair before continuing.

Quick Start — 7 steps from zero to hello-world

For a first-time onboarding or a new machine, read the following 7 step files in order:

| Step | File | Expected outcome | | ---- | ---- | ---------------- | | 1 | [references/quick-start/step-1-developer-portal-app.md](references/quick-start/step-1-developer-portal-app.md) | Schwab Developer Portal app created, App Key / Secret in hand | | 2 | [references/quick-start/step-2-credentials-env.md](references/quick-start/step-2-credentials-env.md) | .env populated (chmod 600), pre-commit hooks installed | | 3 | [references/quick-start/step-3-first-oauth.md](references/quick-start/step-3-first-oauth.md) | token.json written to disk (chmod 600) | | 4 | [references/quick-start/step-4-token-health-check.md](references/quick-start/step-4-token-health-check.md) | health module exits 0, token_state == "valid" | | 5 | [references/quick-start/step-5-first-mcp-tool-call.md](references/quick-start/step-5-first-mcp-tool-call.md) | Minimal Python MCP client successfully calls get_quote("VOO") | | 6 | [references/quick-start/step-6-cursor-integration.md](references/quick-start/step-6-cursor-integration.md) | ~/.cursor/mcp.json registered, agent can invoke all 12 tools | | 7 | [references/quick-start/step-7-cron-launchd-setup.md](references/quick-start/step-7-cron-launchd-setup.md) | cron / launchd health probes enabled, desktop notification channel verified |

Common usage scenarios

Following the helis "I want to..." routing pattern, dispatch directly to the right tool and reference doc based on user intent.

"I want a real-time quote"

| Scenario | What to use | Notes | | -------- | ----------- | ----- | | Single stock / ETF / index | get_quote("AAPL") / get_quote("$SPX") | symbol must be UPPERCASE; indexes need a $ prefix | | Batch (≤ 50) | get_quotes(symbols=[…]) | beyond 50, batch on the client side, otherwise SchwabValidationError(field="symbols") | | Option contract | get_quote("AAPL 240119C00170000") | OSI 21 chars: 6-char root (space-padded) + YYMMDD + C/P + 8-digit strike(×1000) |

→ Full schema in [references/tools/tool-reference-quotes.md](references/tools/tool-reference-quotes.md).

"I want to research an option chain"

  1. get_option_expiration_chain(symbol) — fetch the list of available expirations first
  2. get_option_chain(symbol, contract_type=…, strike_count=…, strike_range=…) — pull concrete contracts by ATM/OTM/ITM
  3. Multi-step research workflow → switch to the schwab-marketdata-workflows-en skill's option-chain-research.md playbook

→ Schema in [references/tools/tool-reference-options.md](references/tools/tool-reference-options.md); Greeks freshness covered in the playbook's Cautions section and [references/concepts/osi-option-symbol.md](references/concepts/osi-option-symbol.md).

"Token expired, what now?"

1. health_check()
   → inspect token_state ∈ {"valid","missing","malformed","insecure_perms"}
   → inspect token_expires_in_days (< 0.5 strongly suggests immediate reauthorize)
2. If SchwabAuthError(reason="refresh_token_expired" | "token_not_initialized"):
   uv run python -m schwab_marketdata_mcp.auth login_flow   # default
   # or manual_flow (headless / SSH-only / WSL2)
3. Re-run health_check() to confirm token_state == "valid"

→ Full mapping table and step-by-step remediation in [references/troubleshooting/auth-overview.md](references/troubleshooting/auth-overview.md); OAuth flow walkthrough in [references/oauth/oauth-overview.md](references/oauth/oauth-overview.md); token lifecycle in [references/oauth/oauth-token-lifecycle.md](references/oauth/oauth-token-lifecycle.md).

"Got rate-limited, what now?"

| Symptom | Remediation | | ------- | ----------- | | SchwabRateLimitError(retry_after_seconds=N) | Wait N seconds and retry; if it happens twice in a row, surface to the user | | stderr emits {"event":"rate_limit_warning","remaining":<20} | Switch bulk requests to get_quotes (50 per call); or lower SCHWAB_RATE_LIMIT_PER_MIN | | 0 slots, raises immediately | Apply retry-with-backoff on the agent side (schwab-py already retries SCHWAB_MAX_RETRIES times internally) |

→ Token-bucket behavior and triage in [references/operations/rate-limit-token-bucket.md](references/operations/rate-limit-token-bucket.md); 4-symptom rate-limit triage in [references/troubleshooting/rate-limit-overview.md](references/troubleshooting/rate-limit-overview.md).

"I want to inspect server health"

get_server_info()  → server_version / mcp_sdk_version / schwab_py_version / supported_tools
health_check()     → token_state / token_expires_in_days / rate_limit_remaining_per_min / recent_error_count_24h

The two together answer "Is the MCP server healthy, is the token valid, and have we exhausted the rate limit?" in under 30 seconds.

→ Schema in [references/tools/tool-reference-meta.md](references/tools/tool-reference-meta.md).

"I want candlesticks / historical data"

get_price_history(symbol, period_type, period?, frequency_type, frequency?, ...)

The (period_type, period, frequency_type, frequency) 4-tuple only accepts a restricted set of combinations — illegal combinations are silently 400'd by the server. The MCP server pre-rejects them at the Pydantic layer.

→ Schema + legal-combination table in [references/tools/tool-reference-price-history.md](references/tools/tool-reference-price-history.md); Cartesian-product error remediation in [references/troubleshooting/validation-pricehistory-cartesian.md](references/troubleshooting/validation-pricehistory-cartesian.md).

"I want instrument metadata / company fundamentals"

| Scenario | What to use | | -------- | ----------- | | Known ticker → metadata | search_instruments(symbols=["AAPL"], projection="SYMBOL_SEARCH") | | Known ticker → fundamentals | search_instruments(symbols=["AAPL"], projection="FUNDAMENTAL") | | Known 9-digit CUSIP | get_instrument_by_cusip(cusip="037833100") | | Fuzzy company-name lookup | search_instruments(symbols=["TESLA"], projection="DESCRIPTION_SEARCH") |

→ Schema in [references/tools/tool-reference-instruments.md](references/tools/tool-reference-instruments.md).

"I want top movers / unusual activity today"

get_movers(index="DJI"|"COMPX"|"SPX"|..., sort_order=..., frequency=...)

The index enum value must be passed as the enum name ("DJI"), not the wire value ("$DJI").

→ Schema + enum reference in [references/tools/tool-reference-movers.md](references/tools/tool-reference-movers.md).

"I want to call this from my own Python / TypeScript / Rust app"

| Client | File | | ------ | ---- | | Python (mcp SDK) | [references/integration/python-mcp-client.md](references/integration/python-mcp-client.md) | | TypeScript / Node | [references/integration/typescript-mcp-client.md](references/integration/typescript-mcp-client.md) | | Rust (rmcp or hand-rolled) | [references/integration/rust-mcp-client.md](references/integration/rust-mcp-client.md) | | Shell + jq pipe | [references/integration/cli-jq-pipe.md](references/integration/cli-jq-pipe.md) |

Data coverage clarifications

get_price_history is the candlestick / kline endpoint

If you're looking for OHLCV bars (candles, klines, candlesticks), get_price_history is the tool. The response carries a candles[] array, each entry exposing open / high / low / close / volume / datetime (epoch milliseconds).

Supported granularity comes from the (period_type, frequency_type, frequency) triple:

  • period_type=DAY: MINUTE × {1, 5, 10, 15, 30} (~48 days for 1-min,

~9 months for 5–30 min).

  • period_type=MONTH: DAILY / WEEKLY (up to 6 months).
  • period_type=YEAR: DAILY / WEEKLY / MONTHLY (**up to 20

years**).

  • period_type=YEAR_TO_DATE: DAILY / WEEKLY (year-to-date).

Sub-minute candles (seconds, ticks) are not in the Schwab Market Data API surface.

→ Full legal-combination table + Cartesian-product error remediation in [references/tools/tool-reference-price-history.md](references/tools/tool-reference-price-history.md) and the MCP repo's README → "Data coverage clarifications".

What the Schwab Market Data API does NOT provide

The following data is architecturally unavailable through the Schwab Market Data Production API and would require a third-party provider:

  • Time & sales / tape (trade-level) — not in REST or Streaming

since Schwab's 2024 API migration removed the TIMESALE_* services.

  • Tick-by-tick history — not in the Schwab API.
  • Level 2 historical snapshots — Streaming only, no REST history.
  • Fundamental / earnings time series (EPS history, revenue

history, etc.) — quotes carry FUNDAMENTAL fields but there is no historical endpoint.

  • News / SEC filings — not in the Market Data API.

Recommended third-party providers (Polygon.io, Tiingo, Alpaca, Databento, FMP, SEC EDGAR) for each data class are listed in the MCP repo's README → "Data coverage clarifications".

Trader API is out of scope

Trader API endpoints (account, orders, transactions, positions) are explicitly out of scope — this skill covers read-only Market Data only. If the user asks to place an order or modify positions, refuse immediately and point them at the MCP README's "Responsible use" section.

Decision tree — pick the right tool

| User intent | What to use | | ------------------------------------------ | -------------------------------------------------------- | | Single stock/ETF/index spot price | get_quote(symbol="AAPL") | | One-shot multi-symbol quotes (≤50) | get_quotes(symbols=[...]) | | Historical candles / OHLC | get_price_history(symbol, period_type, …) | | Option chain snapshot | get_option_chain(symbol, contract_type=…) | | Option expiration list | get_option_expiration_chain(symbol) | | Multi-market open/close status | get_market_hours(markets_list=[…]) | | Single-market open/close status | get_market_hour_single(market_id) | | Today's top movers | get_movers(index, sort_order) | | Fuzzy instrument lookup by ticker | search_instruments(symbols, projection) | | Exact lookup by 9-digit CUSIP | get_instrument_by_cusip(cusip) | | Inspect token state / error counters | health_check() | | Get server metadata (version, 12 tools) | get_server_info() |

Enum values must always use the enum names defined in models.py (e.g. "VOLUME", "NASDAQ", "DAY"); the MCP server internally translates them into schwab-py wire values.

Key Concepts

| Term | Definition | | ---- | ---------- | | accesstoken | Schwab OAuth bearer token, valid for 90 minutes; schwab-py auto-renews it before each API call, so the agent never has to think about it. | | refreshtoken | rotate-on-use: every refresh issues a new refreshtoken, with a hard 7-day lifetime. After expiry you must run login_flow again — there is no shortcut (Schwab OAuth is designed this way). | | TokenState | One of 4 enums returned by health_check(): valid (usable) / missing (no token.json) / insecure_perms (file mode is not 600/700) / malformed (JSON corrupt or missing fields). The first three are fixed by auth login_flow; the last typically requires backing up and deleting token.json first. | | SchwabAuthError reason | A 6-way enum attached to auth failures during business calls: refresh_token_expired_soon / refresh_token_expired / token_not_initialized / token_corrupted / insecure_token_perms / callback_url_mismatch. Each reason has its own 5-section remediation page in [references/troubleshooting/](references/troubleshooting/). | | ratelimit_bucket | The MCP server uses a token bucket + sliding window (capacity = SCHWAB_RATE_LIMIT_PER_MIN, default 120) instead of asyncio.Semaphore. Difference: a Semaphore would hold its slot during retry sleep, blocking other concurrent tools; the token bucket releases the slot during sleep, letting other tools proceed. | | OSI option symbol | A 21-character fixed format: {ROOT:6}{YYMMDD:6}{C\|P:1}{STRIKE×1000:8}, with the root right-padded with spaces if shorter than 6 chars. Example: "AAPL 240119C00170000" = AAPL 2024-01-19 Call $170.00; "BRK/B 240419P00350000" (contains /). Pass this directly to get_quote. | | pricehistory cartesian product | Schwab only accepts a restricted set of combinations for the (period_type, period, frequency_type, frequency) tuple; illegal combinations silently return 400. The MCP server pre-rejects them at the Pydantic layer to avoid wasting billed quota. The full legal-combination table lives in [references/tools/tool-reference-price-history.md](references/tools/tool-reference-price-history.md). | | Pydantic Literal: enum name vs wire value | The public API always takes the enum name (e.g. MoversIndex.DJI = "DJI"); the MCP server translates internally to the schwab-py wire value (e.g. "$DJI"). This translation layer means the agent never has to remember prefix characters ($ / lowercase / mixed case). Never pass a wire value directly — it triggers SchwabValidationError. | | Activation handshake | The two mandatory steps after skill activation: get_server_info to verify version compatibility, health_check to verify token state. Either failure stops the flow immediately — no silent fallback allowed. | | error normalization | All schwab-py exceptions are wrapped by the server into {"error": "Schwab*Error", ...} dicts and returned to the caller — they do not propagate as exceptions through the MCP protocol. The agent must always check the error field before reading the payload. | | non-redistributable | Schwab Market Data is non-redistributable; any markdown / report writes must stay inside private repositories (the workflows skill enforces this with gh repo view --json isPrivate). |

Architecture overview

┌────────────────────┐
│  AI agent (this

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [kevinkda](https://github.com/kevinkda)
- **Source:** [kevinkda/schwab-marketdata-skill](https://github.com/kevinkda/schwab-marketdata-skill)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.