2525
2626from __future__ import annotations
2727
28- import json
2928import os
3029from contextlib import asynccontextmanager
3130from dataclasses import asdict
3231from datetime import datetime
33- from typing import Any , List , Optional
32+ from typing import Any
3433
3534from fastmcp import FastMCP
3635from loguru import logger
3736
38-
3937# ---------------------------------------------------------------------------
4038# Server context
4139# ---------------------------------------------------------------------------
4240
4341
4442class ServerContext :
45- broker : Any = None # AlpacaBrokerClient
43+ broker : Any = None # AlpacaBrokerClient
4644 account_id : str = ""
4745
4846
@@ -55,17 +53,14 @@ async def lifespan(server: FastMCP):
5553 logger .info ("[alpaca_mcp] Starting…" )
5654 try :
5755 from alpaca_mcp .client import AlpacaBrokerClient
58- from quantcore .execution .broker import BrokerAuthError
5956
6057 paper = os .getenv ("ALPACA_PAPER" , "true" ).lower () == "true"
6158 _ctx .broker = AlpacaBrokerClient (paper = paper )
6259
6360 # Resolve the account_id once at startup
6461 accounts = _ctx .broker .get_accounts ()
6562 _ctx .account_id = accounts [0 ].account_id if accounts else ""
66- logger .info (
67- f"[alpaca_mcp] Connected account={ _ctx .account_id } paper={ paper } "
68- )
63+ logger .info (f"[alpaca_mcp] Connected account={ _ctx .account_id } paper={ paper } " )
6964 except Exception as exc :
7065 logger .warning (f"[alpaca_mcp] Startup error: { exc } . Tools will return errors." )
7166
@@ -112,8 +107,8 @@ def get_auth_status() -> dict:
112107 broker = _require_broker ()
113108 ok = broker .check_auth ()
114109 return {
115- "success" : ok ,
116- "paper" : os .getenv ("ALPACA_PAPER" , "true" ).lower () == "true" ,
110+ "success" : ok ,
111+ "paper" : os .getenv ("ALPACA_PAPER" , "true" ).lower () == "true" ,
117112 "account_id" : _ctx .account_id ,
118113 }
119114 except Exception as exc :
@@ -124,10 +119,10 @@ def get_auth_status() -> dict:
124119def get_account () -> dict :
125120 """Return Alpaca account summary including status and account type."""
126121 try :
127- broker = _require_broker ()
122+ broker = _require_broker ()
128123 accounts = broker .get_accounts ()
129124 return {
130- "success" : True ,
125+ "success" : True ,
131126 "accounts" : [_dc_to_dict (a ) for a in accounts ],
132127 }
133128 except Exception as exc :
@@ -138,7 +133,7 @@ def get_account() -> dict:
138133def get_balance () -> dict :
139134 """Return cash, buying power, and portfolio value."""
140135 try :
141- broker = _require_broker ()
136+ broker = _require_broker ()
142137 balance = broker .get_balance (_ctx .account_id )
143138 return {"success" : True , "balance" : _dc_to_dict (balance )}
144139 except Exception as exc :
@@ -149,19 +144,19 @@ def get_balance() -> dict:
149144def get_positions () -> dict :
150145 """Return all open positions with quantity, entry price, and unrealised P&L."""
151146 try :
152- broker = _require_broker ()
147+ broker = _require_broker ()
153148 positions = broker .get_positions (_ctx .account_id )
154149 return {
155- "success" : True ,
150+ "success" : True ,
156151 "positions" : [_dc_to_dict (p ) for p in positions ],
157- "count" : len (positions ),
152+ "count" : len (positions ),
158153 }
159154 except Exception as exc :
160155 return {"success" : False , "error" : str (exc )}
161156
162157
163158@mcp .tool ()
164- def get_quote (symbols : List [str ]) -> dict :
159+ def get_quote (symbols : list [str ]) -> dict :
165160 """Return real-time best-bid/offer quotes for up to 50 symbols.
166161
167162 Args:
@@ -172,7 +167,7 @@ def get_quote(symbols: List[str]) -> dict:
172167 quotes = broker .get_quote (symbols )
173168 return {
174169 "success" : True ,
175- "quotes" : [_dc_to_dict (q ) for q in quotes ],
170+ "quotes" : [_dc_to_dict (q ) for q in quotes ],
176171 }
177172 except Exception as exc :
178173 return {"success" : False , "error" : str (exc )}
@@ -182,8 +177,8 @@ def get_quote(symbols: List[str]) -> dict:
182177def get_bars (
183178 symbol : str ,
184179 timeframe : str = "1h" ,
185- start : Optional [ str ] = None ,
186- end : Optional [ str ] = None ,
180+ start : str | None = None ,
181+ end : str | None = None ,
187182 limit : int = 200 ,
188183) -> dict :
189184 """Return historical OHLCV bars for a symbol.
@@ -200,21 +195,26 @@ def get_bars(
200195 from quantcore .data .adapters .alpaca import AlpacaAdapter
201196
202197 _TF_MAP = {
203- "1m" : Timeframe .M1 , "5m" : Timeframe .M5 , "15m" : Timeframe .M15 ,
204- "30m" : Timeframe .M30 , "1h" : Timeframe .H1 , "4h" : Timeframe .H4 ,
205- "1d" : Timeframe .D1 , "1w" : Timeframe .W1 ,
198+ "1m" : Timeframe .M1 ,
199+ "5m" : Timeframe .M5 ,
200+ "15m" : Timeframe .M15 ,
201+ "30m" : Timeframe .M30 ,
202+ "1h" : Timeframe .H1 ,
203+ "4h" : Timeframe .H4 ,
204+ "1d" : Timeframe .D1 ,
205+ "1w" : Timeframe .W1 ,
206206 }
207207 tf = _TF_MAP .get (timeframe .lower ())
208208 if tf is None :
209209 return {"success" : False , "error" : f"Unknown timeframe '{ timeframe } '" }
210210
211211 broker = _require_broker ()
212212 adapter = AlpacaAdapter (
213- api_key = broker ._api_key ,
214- secret_key = broker ._secret_key ,
213+ api_key = broker ._api_key ,
214+ secret_key = broker ._secret_key ,
215215 )
216216 start_dt = datetime .fromisoformat (start ) if start else None
217- end_dt = datetime .fromisoformat (end ) if end else None
217+ end_dt = datetime .fromisoformat (end ) if end else None
218218 df = adapter .fetch_ohlcv (symbol , tf , start_date = start_dt , end_date = end_dt )
219219 df = df .tail (limit )
220220
@@ -235,7 +235,7 @@ def preview_order(
235235 side : str ,
236236 quantity : float ,
237237 order_type : str = "market" ,
238- limit_price : Optional [ float ] = None ,
238+ limit_price : float | None = None ,
239239) -> dict :
240240 """Estimate cost and commission for an order without submitting it.
241241
@@ -248,13 +248,14 @@ def preview_order(
248248 """
249249 try :
250250 from quantcore .execution .unified_models import UnifiedOrder
251+
251252 broker = _require_broker ()
252- order = UnifiedOrder (
253- symbol = symbol ,
254- side = side ,
255- quantity = quantity ,
256- order_type = order_type ,
257- limit_price = limit_price ,
253+ order = UnifiedOrder (
254+ symbol = symbol ,
255+ side = side ,
256+ quantity = quantity ,
257+ order_type = order_type ,
258+ limit_price = limit_price ,
258259 )
259260 preview = broker .preview_order (_ctx .account_id , order )
260261 return {"success" : True , "preview" : _dc_to_dict (preview )}
@@ -268,7 +269,7 @@ def place_order(
268269 side : str ,
269270 quantity : float ,
270271 order_type : str = "market" ,
271- limit_price : Optional [ float ] = None ,
272+ limit_price : float | None = None ,
272273 time_in_force : str = "day" ,
273274 extended_hours : bool = False ,
274275) -> dict :
@@ -285,15 +286,16 @@ def place_order(
285286 """
286287 try :
287288 from quantcore .execution .unified_models import UnifiedOrder
289+
288290 broker = _require_broker ()
289- order = UnifiedOrder (
290- symbol = symbol ,
291- side = side ,
292- quantity = quantity ,
293- order_type = order_type ,
294- limit_price = limit_price ,
295- time_in_force = time_in_force ,
296- extended_hours = extended_hours ,
291+ order = UnifiedOrder (
292+ symbol = symbol ,
293+ side = side ,
294+ quantity = quantity ,
295+ order_type = order_type ,
296+ limit_price = limit_price ,
297+ time_in_force = time_in_force ,
298+ extended_hours = extended_hours ,
297299 )
298300 result = broker .place_order (_ctx .account_id , order )
299301 return {"success" : True , "order" : _dc_to_dict (result )}
@@ -310,15 +312,15 @@ def cancel_order(order_id: str) -> dict:
310312 """
311313 try :
312314 broker = _require_broker ()
313- ok = broker .cancel_order (_ctx .account_id , order_id )
315+ ok = broker .cancel_order (_ctx .account_id , order_id )
314316 return {"success" : ok , "order_id" : order_id }
315317 except Exception as exc :
316318 return {"success" : False , "error" : str (exc )}
317319
318320
319321@mcp .tool ()
320322def get_orders (
321- status : Optional [ str ] = None ,
323+ status : str | None = None ,
322324 limit : int = 50 ,
323325) -> dict :
324326 """Return order history.
@@ -332,8 +334,8 @@ def get_orders(
332334 orders = broker .get_orders (_ctx .account_id , status = status )
333335 return {
334336 "success" : True ,
335- "orders" : [_dc_to_dict (o ) for o in orders [:limit ]],
336- "count" : len (orders ),
337+ "orders" : [_dc_to_dict (o ) for o in orders [:limit ]],
338+ "count" : len (orders ),
337339 }
338340 except Exception as exc :
339341 return {"success" : False , "error" : str (exc )}
@@ -342,7 +344,7 @@ def get_orders(
342344@mcp .tool ()
343345def get_option_chains (
344346 symbol : str ,
345- expiry : Optional [ str ] = None ,
347+ expiry : str | None = None ,
346348) -> dict :
347349 """Return an options chain snapshot for a symbol.
348350
@@ -361,16 +363,18 @@ def get_option_chains(
361363 req_kwargs = {"underlying_symbol" : symbol }
362364 if expiry :
363365 req_kwargs ["expiration_date" ] = expiry
364- req = OptionChainRequest (** req_kwargs )
366+ req = OptionChainRequest (** req_kwargs )
365367 chain = cli .get_option_chain (req )
366368 # Normalise to a list of dicts
367369 contracts = []
368370 for sym , snap in chain .items ():
369- contracts .append ({
370- "symbol" : sym ,
371- "bid" : float (snap .latest_quote .bid_price ) if snap .latest_quote else None ,
372- "ask" : float (snap .latest_quote .ask_price ) if snap .latest_quote else None ,
373- })
371+ contracts .append (
372+ {
373+ "symbol" : sym ,
374+ "bid" : float (snap .latest_quote .bid_price ) if snap .latest_quote else None ,
375+ "ask" : float (snap .latest_quote .ask_price ) if snap .latest_quote else None ,
376+ }
377+ )
374378 return {"success" : True , "underlying" : symbol , "contracts" : contracts }
375379 except Exception as exc :
376380 return {"success" : False , "error" : str (exc )}
0 commit comments