Skip to main content
Glama
app.py28.3 kB
""" MonteWalk - Institutional-Grade Quantitative Finance Tools for AI Agents This module implements a dual-mode Gradio 6 application that serves as both: 1. A standalone web UI for testing trading tools 2. An MCP (Model Context Protocol) server for AI assistant integration ARCHITECTURE: - 25 trading tools across 9 categories (market data, execution, risk, etc.) - 4 live resources (portfolio, watchlist, news, crypto) - 6 pre-built agentic workflow prompts - Multi-source data aggregation (Yahoo Finance, Alpaca, CoinGecko, NewsAPI) - FinBERT sentiment analysis via Modal serverless GPU UI NAVIGATION: - Landing Page: Hero section with feature showcase - UI Dashboard: Portfolio/watchlist/crypto/news panels + toolbox + settings - MCP Client Setup: Configuration instructions for Claude Desktop, VSCode, etc. MCP INTEGRATION: Launch with `demo.launch(mcp_server=True)` to enable MCP protocol. AI assistants can then access all tools via stdio or HTTP connection. USAGE: # Gradio UI + MCP Server uv run app.py # Pure MCP Server (for Claude Desktop) uv run server.py Built for the MCP 1st Birthday Hackathon. Repository: https://github.com/N-lia/MonteWalk """ import gradio as gr import logging import sys from typing import Callable import inspect from dotenv import load_dotenv load_dotenv() # Local imports (tool implementations) from config import LOG_FILE from theme import ProfessionalTheme from tools.market_data import get_price, get_fundamentals, get_orderbook from tools.execution import ( place_order, cancel_order, get_positions, flatten, get_order_history, ) from tools.risk_engine import ( portfolio_risk, var, max_drawdown, monte_carlo_simulation, ) from tools.backtesting import run_backtest, walk_forward_analysis from tools.feature_engineering import ( compute_indicators, rolling_stats, get_technical_summary, ) from tools.portfolio_optimizer import mean_variance_optimize, risk_parity from tools.logger import setup_logging, log_action from tools.news_intelligence import ( get_news, analyze_sentiment, get_symbol_sentiment, get_latest_news_for_watchlist, ) from tools.watchlist import ( add_to_watchlist, remove_from_watchlist, get_watchlist_data, _load_watchlist, ) from tools.crypto_data import ( get_crypto_price, get_crypto_market_data, get_trending_crypto, search_crypto, ) from tools.unusual_scanner import scan_unusual_activity from tools.alpaca_broker import get_broker from tools.resources import ( get_algo_cheat_sheet, get_classic_papers, get_risk_checklist, ) setup_logging() logger = logging.getLogger(__name__) # ---------------------------------------------------------------------- # Helper tools used on the landing page # ---------------------------------------------------------------------- def health_check() -> str: return "MonteWalk Server is running and healthy." def get_account_info() -> str: try: broker = get_broker() account = broker.get_account() return f""" === ALPACA ACCOUNT INFO === Cash: ${account['cash']:,.2f} Equity: ${account['equity']:,.2f} Buying Power: ${account['buying_power']:,.2f} Portfolio Value: ${account['portfolio_value']:,.2f} Pattern Day Trader: {account['pattern_day_trader']} Day Trade Count: {account['daytrade_count']} """ except Exception as e: return f"ERROR: Failed to get account info - {str(e)}" def get_portfolio_summary() -> str: try: broker = get_broker() account = broker.get_account() positions = broker.get_all_positions() summary = [ "=== PORTFOLIO SUMMARY (Alpaca Paper Trading) ===", f"Cash: ${account['cash']:,.2f}", f"Equity: ${account['equity']:,.2f}", f"Buying Power: ${account['buying_power']:,.2f}", f"Positions: {len(positions)}", "--- Holdings ---", ] for symbol, details in positions.items(): qty = details["qty"] price = details["current_price"] pl = details["unrealized_pl"] pl_pct = details["unrealized_plpc"] * 100 summary.append( f"{symbol}: {qty} shares @ ${price:.2f} (P/L: ${pl:,.2f} / {pl_pct:+.2f}%)" ) if not positions: summary.append("(No open positions)") return "\n".join(summary) except Exception as e: return f"ERROR: Failed to get portfolio - {str(e)}" def get_watchlist_resource() -> str: data = get_watchlist_data() if not data: return "Watchlist is empty. Use add_to_watchlist() to track symbols." summary = ["=== MARKET WATCHLIST ==="] for symbol, info in data.items(): if "error" in info: summary.append(f"{symbol}: ERROR - {info['error']}") else: price = info.get("price", 0.0) summary.append(f"{symbol}: ${price:,.2f}") return "\n".join(summary) def get_news_resource() -> str: return get_latest_news_for_watchlist() def get_crypto_resource() -> str: return get_trending_crypto() # ---------------------------------------------------------------------- # Prompt‑style tools (converted to functions) # ---------------------------------------------------------------------- def morning_briefing() -> str: portfolio = get_positions() owned_symbols = list(portfolio.get("positions", {}).keys()) watchlist = _load_watchlist() owned_but_not_watched = [s for s in owned_symbols if s not in watchlist] return f""" Please generate a Morning Trading Briefing. STEP 0: SYNC WATCHLIST (Auto‑maintenance) {f"⚠️ Detected {len(owned_but_not_watched)} owned symbols not in watchlist: {owned_but_not_watched}" if owned_but_not_watched else "✅ Watchlist is synced with portfolio"} CONTEXT: 1. Portfolio cash ${portfolio.get('cash',0):,.2f}, equity ${portfolio.get('equity',0):,.2f} Positions: {owned_symbols} 2. Watchlist: {watchlist} BRIEFING STEPS: - Review held positions, watchlist moves, top headlines, sentiment, risk checks, and give recommendations. """ def analyze_ticker(symbol: str) -> str: return f""" Please perform a comprehensive analysis on {symbol}. Steps: price trends, fundamentals, recent news, sentiment (FinBERT), technical indicators (RSI, MACD). Output an executive summary with buy/sell/hold recommendation. """ def risk_analysis() -> str: portfolio = get_positions() positions = portfolio.get("positions", {}) return f""" Perform a risk analysis on the portfolio: {dict(positions)} Include volatility, VaR (95%), max drawdown, Monte Carlo forecast and sector concentration. """ def backtest_strategy(symbol: str, fast_ma: int = 10, slow_ma: int = 50) -> str: return f""" Backtest a moving‑average crossover on {symbol} (fast={fast_ma}, slow={slow_ma}). Run historical test (2020‑01‑01 → 2023‑12‑31) and walk‑forward validation. Report viability, weaknesses and suggested parameter tweaks. """ def crypto_market_update() -> str: return """ Generate a Crypto Market Update using trending coins, BTC/ETH analysis and price levels. """ def portfolio_rebalance(target_symbols: str) -> str: """Portfolio rebalancing workflow prompt.""" return f""" 🔄 **PORTFOLIO REBALANCE WORKFLOW** Target Symbols: {target_symbols} 1. get_positions() - Review current holdings 2. mean_variance_optimize(tickers="{target_symbols}", lookback="1y") - Calculate optimal weights 3. For each symbol that needs rebalancing: - get_price(symbol, period="1mo", visualize=True) - Check recent trends - get_symbol_sentiment(symbol) - Validate timing 4. Execute trades via place_order() with calculated quantities 5. Log reasoning with log_action() """ def morning_gamma_hunt() -> str: """Morning workflow to find and analyze unusual market activity.""" return """ 🔥 **MORNING GAMMA HUNT WORKFLOW** Find unusual activity and deep dive into the best opportunities. **Step 1: Scan the Market** - scan_unusual_activity("big_movers", limit=15, visualize=True) - scan_unusual_activity("volume_spikes", limit=10, visualize=False) **Step 2: Pick Top 3 Tickers** Choose the 3 most interesting from the scans based on: - Price change magnitude - Volume conviction (volume_ratio > 2.0) - Sector/industry relevance **Step 3: Deep Dive Each Ticker** For each of the 3 selected tickers: 1. get_price(symbol, period="1mo", interval="1d", visualize=True) - Check 30-day trend 2. get_symbol_sentiment(symbol) - News sentiment analysis 3. get_technical_summary(symbol) - RSI, MACD signals 4. get_fundamentals(symbol) - Quick fundamental check **Step 4: Rank by Conviction** Create a ranked list with: - Symbol - Entry thesis (why interesting) - Key catalysts (news, technicals) - Risk factors - Conviction score (1-10) **Output Format:** Present as a concise markdown table with actionable insights. """ def sync_watchlist() -> str: portfolio = get_positions() owned_symbols = list(portfolio.get("positions", {}).keys()) watchlist = _load_watchlist() owned_but_not_watched = [s for s in owned_symbols if s not in watchlist] watched_but_not_owned = [s for s in watchlist if s not in owned_symbols] return f""" Synchronize watchlist with portfolio. Add missing owned symbols: {owned_but_not_watched} Optionally remove symbols not owned: {watched_but_not_owned} """ # ---------------------------------------------------------------------- # Tool organization & dynamic Interface generator # ---------------------------------------------------------------------- tools_map = { "Dashboard": [health_check, get_account_info, get_portfolio_summary], "Market Data": [get_price, get_fundamentals, get_orderbook], "Market Screener": [scan_unusual_activity], "Execution": [place_order, cancel_order, get_positions, flatten, get_order_history], "Risk Management": [portfolio_risk, var, max_drawdown, monte_carlo_simulation], "Backtesting": [run_backtest, walk_forward_analysis], "Technical Analysis": [compute_indicators, rolling_stats, get_technical_summary], "Portfolio Opt": [mean_variance_optimize, risk_parity], "News & Sentiment": [get_news, analyze_sentiment, get_symbol_sentiment, get_news_resource], "Watchlist": [add_to_watchlist, remove_from_watchlist, get_watchlist_resource, sync_watchlist], "Crypto": [ get_crypto_price, get_crypto_market_data, get_trending_crypto, search_crypto, get_crypto_resource, crypto_market_update, ], "Prompts": [ morning_briefing, morning_gamma_hunt, analyze_ticker, risk_analysis, backtest_strategy, portfolio_rebalance, ], "Resources": [get_algo_cheat_sheet, get_classic_papers, get_risk_checklist], "Utils": [log_action], } def create_interface(tool: Callable): """Create a simple Gradio Interface for any callable tool.""" sig = inspect.signature(tool) inputs = [] for name, param in sig.parameters.items(): if param.annotation is int: inputs.append(gr.Number(label=name, precision=0)) elif param.annotation is float: inputs.append(gr.Number(label=name)) elif param.annotation is bool: inputs.append(gr.Checkbox(label=name)) else: inputs.append(gr.Textbox(label=name)) output = gr.Textbox(label="Result") return gr.Interface(fn=tool, inputs=inputs, outputs=output, flagging_mode="never") # ---------------------------------------------------------------------- # UI helper callbacks # ---------------------------------------------------------------------- def save_settings(alpaca_key, alpaca_secret, newsapi_key, modal_url): try: env_content = f'''NEWSAPI_KEY="{newsapi_key}" ALPACA_API_KEY="{alpaca_key}" ALPACA_SECRET_KEY="{alpaca_secret}" MODAL_ENDPOINT_URL={modal_url} ''' with open(".env", "w") as f: f.write(env_content) return "✅ Settings saved! Restart the server to apply changes." except Exception as e: return f"❌ Error saving settings: {str(e)}" def refresh_dashboard(): return ( get_portfolio_summary(), get_watchlist_resource(), get_crypto_resource(), get_news_resource(), ) # ---------------------------------------------------------------------- # Custom CSS & static HTML # ---------------------------------------------------------------------- CUSTOM_CSS = """ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap'); :root { --glass-bg: rgba(15, 23, 42, 0.6); --glass-border: rgba(255, 255, 255, 0.05); --accent-glow: rgba(59, 130, 246, 0.15); } .gradio-container { background: transparent !important; } /* Typography & Base */ body { font-family: 'Inter', sans-serif; background-color: #020617; /* neutral-950 */ } h1, h2, h3, h4, h5, h6 { font-weight: 600; letter-spacing: -0.01em; } code, .code-block { font-family: 'JetBrains Mono', monospace; } /* Utilities */ .icon-mono { filter: grayscale(100%) brightness(1.2); display: inline-block; } /* Landing Page */ .landing-container { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 80vh; text-align: center; padding: 4rem 2rem; position: relative; overflow: hidden; } .hero-title { font-size: 4rem; font-weight: 700; margin-bottom: 1rem; color: #f8fafc; letter-spacing: -0.03em; } .hero-subtitle { font-size: 1.125rem; color: #94a3b8; margin-bottom: 3rem; line-height: 1.6; max-width: 600px; margin-left: auto; margin-right: auto; } /* Feature Grid */ .feature-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; width: 100%; max-width: 1200px; margin-top: 2rem; } .feature-card { background: var(--glass-bg); border: 1px solid var(--glass-border); padding: 1.5rem; border-radius: 12px; text-align: left; transition: transform 0.2s ease, border-color 0.2s ease; } .feature-card:hover { transform: translateY(-2px); border-color: rgba(255, 255, 255, 0.1); } .feature-icon { font-size: 1.25rem; margin-bottom: 1rem; color: #e2e8f0; opacity: 0.8; } .feature-title { font-size: 1rem; font-weight: 600; color: #f1f5f9; margin-bottom: 0.5rem; } .feature-desc { font-size: 0.875rem; color: #94a3b8; line-height: 1.5; } /* Buttons */ .primary-btn { font-weight: 500 !important; border-radius: 8px !important; } .secondary-btn { background: transparent !important; border: 1px solid var(--glass-border) !important; color: #cbd5e1 !important; font-weight: 500 !important; border-radius: 8px !important; } .secondary-btn:hover { background: rgba(255, 255, 255, 0.05) !important; border-color: rgba(255, 255, 255, 0.1) !important; } /* Setup Page */ .setup-container { max-width: 900px; margin: 0 auto; padding: 3rem 1.5rem; } .ui-container { max-width: 1400px; margin: 0 auto; padding: 2rem; } .setup-card { background: rgba(15, 23, 42, 0.4); border: 1px solid var(--glass-border); border-radius: 12px; padding: 2rem; margin-bottom: 1.5rem; } .step-number { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; background: #334155; color: #f8fafc; border-radius: 4px; font-size: 0.75rem; font-weight: 600; margin-right: 0.75rem; } .code-block { background: #0f172a; border: 1px solid #1e293b; border-radius: 6px; padding: 1rem; margin: 0.75rem 0; font-size: 0.85rem; color: #e2e8f0; overflow-x: auto; } .path-highlight { color: #60a5fa; } """ LANDING_HTML = """ <div class="landing-container"> <div class="hero-content"> <div class="hero-title">MonteWalk</div> <div class="hero-subtitle"> Institutional-grade quantitative finance tools for AI agents.<br> Real-time market data, risk analytics, and portfolio optimization via MCP. </div> <div class="feature-grid"> <div class="feature-card"> <div class="feature-icon"><span class="icon-mono">📊</span></div> <div class="feature-title">Market Intelligence</div> <div class="feature-desc">Real-time data from Alpaca, Yahoo Finance, and CoinGecko with 5-minute caching.</div> </div> <div class="feature-card"> <div class="feature-icon"><span class="icon-mono">⚡</span></div> <div class="feature-title">Execution Engine</div> <div class="feature-desc">Paper trading environment with $100k virtual capital for strategy testing.</div> </div> <div class="feature-card"> <div class="feature-icon"><span class="icon-mono">🛡️</span></div> <div class="feature-title">Risk Analytics</div> <div class="feature-desc">Institutional metrics: VaR, Monte Carlo simulations, and volatility analysis.</div> </div> <div class="feature-card"> <div class="feature-icon"><span class="icon-mono">🔬</span></div> <div class="feature-title">Backtesting</div> <div class="feature-desc">Professional walk-forward analysis engine for strategy validation.</div> </div> <div class="feature-card"> <div class="feature-icon"><span class="icon-mono">🧠</span></div> <div class="feature-title">Sentiment AI</div> <div class="feature-desc">FinBERT-powered news analysis for institutional-grade sentiment scoring.</div> </div> <div class="feature-card"> <div class="feature-icon"><span class="icon-mono">👁️</span></div> <div class="feature-title">Smart Watchlist</div> <div class="feature-desc">Automated portfolio synchronization and intelligent monitoring.</div> </div> <div class="feature-card"> <div class="feature-icon"><span class="icon-mono">📈</span></div> <div class="feature-title">Advanced Visualizations</div> <div class="feature-desc">6 chart types including candlestick, histogram, and heatmap with dark theme.</div> </div> <div class="feature-card"> <div class="feature-icon"><span class="icon-mono">🔍</span></div> <div class="feature-title">Market Scanner</div> <div class="feature-desc">Detect big movers, volume spikes, and reversal candidates in real-time.</div> </div> </div> </div> </div> """ MCP_CLIENT_SETUP_HTML = """ <div class="setup-container"> <div class="setup-header"> <h2 style="font-size: 2.5rem; font-weight: 700; margin-bottom: 0.5rem; color: #f8fafc;">Connect Your AI Assistant</h2> <p style="color: #94a3b8; font-size: 1.1rem;">Enable MonteWalk tools in your favorite AI environment.</p> </div> <div class="setup-card"> <h2 style="color: #f1f5f9; display: flex; align-items: center; gap: 1rem; font-size: 1.25rem;"> <span class="icon-mono" style="font-size: 1.5rem;">🤖</span> Claude Desktop </h2> <p style="color: #94a3b8; margin-bottom: 1.5rem; font-size: 0.95rem;">Recommended for the best agentic experience.</p> <h3 style="color: #e2e8f0; font-size: 1rem; margin-bottom: 0.5rem;"><span class="step-number">1</span> Locate Configuration</h3> <p style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 1rem;"> Open your config file at:<br> <span style="opacity: 0.7">macOS:</span> <code>~/Library/Application Support/Claude/claude_desktop_config.json</code><br> <span style="opacity: 0.7">Windows:</span> <code>%APPDATA%\\Claude\\claude_desktop_config.json</code> </p> <h3 style="color: #e2e8f0; font-size: 1rem; margin-bottom: 0.5rem;"><span class="step-number">2</span> Add Server Definition</h3> <div class="code-block">{ "mcpServers": { "montewalk": { "command": "<span class="path-highlight">/absolute/path/to/MonteWalk/.venv/bin/python</span>", "args": ["<span class="path-highlight">/absolute/path/to/MonteWalk/server.py</span>"] } } }</div> </div> <div class="setup-card"> <h2 style="color: #f1f5f9; display: flex; align-items: center; gap: 1rem; font-size: 1.25rem;"> <span class="icon-mono" style="font-size: 1.5rem;">💻</span> VSCode & Cursor </h2> <h3 style="color: #e2e8f0; font-size: 1rem; margin-top: 1.5rem; margin-bottom: 0.5rem;"><span class="step-number">1</span> Install Extension</h3> <p style="color: #94a3b8; margin-bottom: 1.5rem; font-size: 0.9rem;">Search for <strong>"Model Context Protocol"</strong> in the marketplace.</p> <h3 style="color: #e2e8f0; font-size: 1rem; margin-bottom: 0.5rem;"><span class="step-number">2</span> Configure Settings</h3> <div class="code-block">"mcp.servers": { "montewalk": { "command": "<span class="path-highlight">/absolute/path/to/MonteWalk/.venv/bin/python</span>", "args": ["<span class="path-highlight">/absolute/path/to/MonteWalk/server.py</span>"] } }</div> </div> </div> """ # ---------------------------------------------------------------------- # Gradio app definition # ---------------------------------------------------------------------- with gr.Blocks(title="MonteWalk MCP Server") as demo: # Apply custom CSS globally gr.HTML(f"<style>{CUSTOM_CSS}</style>") # ---------- Landing page ---------- with gr.Column(visible=True, elem_classes="landing-container") as landing_page: gr.HTML(LANDING_HTML) with gr.Row(elem_id="action-buttons"): with gr.Column(scale=1): pass with gr.Column(scale=2): with gr.Row(): ui_btn = gr.Button( "Test via UI", elem_classes="primary-btn", size="lg", ) client_btn = gr.Button( "Setup MCP Client", elem_classes="secondary-btn", size="lg", ) with gr.Column(scale=1): pass # ---------- UI Dashboard ---------- with gr.Column(visible=False, elem_classes="ui-container") as ui_page: # Top‑right back button with gr.Row(): back_from_ui_btn = gr.Button("← Back to Home", size="sm", elem_classes="secondary-btn") gr.Markdown("# MonteWalk Trading Terminal") with gr.Tabs(): # ----- Dashboard tab ----- with gr.Tab("Dashboard"): with gr.Row(): refresh_btn = gr.Button("Refresh Data", variant="primary") with gr.Row(): with gr.Column(scale=1): gr.Markdown("### Portfolio") portfolio_view = gr.Textbox( label="Holdings", value="Click 'Refresh Data' to load...", lines=10, interactive=False, ) with gr.Column(scale=1): gr.Markdown("### Watchlist") watchlist_view = gr.Textbox( label="Market Data", value="Click 'Refresh Data' to load...", lines=10, interactive=False, ) with gr.Row(): with gr.Column(scale=1): gr.Markdown("### Crypto Trends") crypto_view = gr.Textbox( label="Trending Coins", value="Click 'Refresh Data' to load...", lines=8, interactive=False, ) with gr.Column(scale=1): gr.Markdown("### News Feed") news_view = gr.Textbox( label="Latest Headlines", value="Click 'Refresh Data' to load...", lines=8, interactive=False, ) refresh_btn.click( fn=refresh_dashboard, inputs=[], outputs=[portfolio_view, watchlist_view, crypto_view, news_view], ) # ----- Toolbox tab ----- with gr.Tab("Toolbox"): gr.Markdown("Direct access to all MCP tools.") for category, tools in tools_map.items(): with gr.Accordion(category, open=False): for tool in tools: with gr.Accordion(tool.__name__, open=False): create_interface(tool) # ----- Settings tab ----- with gr.Tab("Settings"): gr.Markdown("### API Configuration") gr.Markdown("Update your keys below – a server restart is required for changes to take effect.") with gr.Row(): alpaca_key_input = gr.Textbox(label="Alpaca API Key", type="password", placeholder="PK...") alpaca_secret_input = gr.Textbox(label="Alpaca Secret Key", type="password", placeholder="...") with gr.Row(): newsapi_input = gr.Textbox(label="NewsAPI Key", type="password", placeholder="...") modal_url_input = gr.Textbox(label="Modal Endpoint URL", placeholder="https://...") save_btn = gr.Button("Save Settings") settings_output = gr.Textbox(label="Status") save_btn.click( fn=save_settings, inputs=[alpaca_key_input, alpaca_secret_input, newsapi_input, modal_url_input], outputs=settings_output, ) # ---------- MCP Client Setup page ---------- with gr.Column(visible=False) as client_page: with gr.Row(): back_from_client_btn = gr.Button("← Back to Home", size="sm", elem_classes="secondary-btn") gr.HTML(MCP_CLIENT_SETUP_HTML) # ---------- Navigation callbacks ---------- def go_to_ui(): return gr.update(visible=False), gr.update(visible=True), gr.update(visible=False) def go_to_client(): return gr.update(visible=False), gr.update(visible=False), gr.update(visible=True) def go_home(): return gr.update(visible=True), gr.update(visible=False), gr.update(visible=False) ui_btn.click(fn=go_to_ui, outputs=[landing_page, ui_page, client_page], scroll_to_output=True) client_btn.click(fn=go_to_client, outputs=[landing_page, ui_page, client_page], scroll_to_output=True) back_from_ui_btn.click(fn=go_home, outputs=[landing_page, ui_page, client_page], scroll_to_output=True) back_from_client_btn.click(fn=go_home, outputs=[landing_page, ui_page, client_page], scroll_to_output=True) if __name__ == "__main__": import os logger.info("Starting MonteWalk Gradio MCP Server...") server_name = os.getenv("GRADIO_SERVER_NAME", "localhost") server_port = int(os.getenv("GRADIO_SERVER_PORT", "7860")) logger.info(f"UI URL: http://{server_name}:{server_port}") logger.info("MCP enabled – tools available to clients") demo.launch( mcp_server=True, footer_links=["gradio", "settings", "api"], theme=ProfessionalTheme(), server_name=server_name, server_port=server_port )

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/N-lia/MonteWalk'

If you have feedback or need assistance with the MCP directory API, please join our Discord server