diff --git a/lib/sanbase/mcp/server.ex b/lib/sanbase/mcp/server.ex index 14d263110..44002f54d 100644 --- a/lib/sanbase/mcp/server.ex +++ b/lib/sanbase/mcp/server.ex @@ -27,6 +27,9 @@ defmodule Sanbase.MCP.Server do # Register Screener tool component(Sanbase.MCP.AssetsByMetricTool) + # Register Use Cases Catalog tool + component(Sanbase.MCP.UseCasesCatalogTool) + if Application.compile_env(:sanbase, :env) in [:test, :dev] do IO.puts("Defining the extra MCP Server tools used in dev and test") # Some tools are enabled only in dev mode so we can test things during development diff --git a/lib/sanbase/mcp/use_cases_catalog.ex b/lib/sanbase/mcp/use_cases_catalog.ex new file mode 100644 index 000000000..bc8016713 --- /dev/null +++ b/lib/sanbase/mcp/use_cases_catalog.ex @@ -0,0 +1,153 @@ +defmodule Sanbase.MCP.UseCasesCatalog do + @moduledoc """ + Catalog of analytical use cases with execution steps. + Each use case includes plain English instructions referencing specific MCP tools. + """ + + def all_use_cases do + [ + identify_market_tops() + ] + end + + defp identify_market_tops do + %{ + id: "identify_market_tops", + title: "Identify Market Tops Using Santiment Metrics", + description: """ + Multi-signal approach combining social volume, sentiment, network activity, + and on-chain metrics to identify potential market tops. This framework + emphasizes using multiple indicators together rather than relying on any + single metric. + """, + category: "market_timing", + difficulty: "intermediate", + estimated_time: "5-10 minutes", + applies_to: "Any crypto asset with sufficient data history", + steps: """ + Step 1: Check for social volume spikes during rallies + Use the fetch_metric_data tool with these parameters: + - metric: "social_volume_total" + - slugs: [your target asset, e.g., "bitcoin"] + - time_period: "30d" + - interval: "1d" + + Look for extreme spikes in social volume (3x or more above the 30-day baseline) + that occur during price rallies. This is especially reliable for mid-cap and + small-cap coins. Extreme social attention during rallies often marks local tops. + + Step 2: Analyze sentiment for overbought crowd conditions + Use the fetch_metric_data tool to check positive sentiment: + - metric: "sentiment_positive_total" + - slugs: [your target asset] + - time_period: "7d" + - interval: "1d" + + When the crowd is overly bullish, it can indicate a top. Look for: + - Positive sentiment representing > 70% of total mentions + - This elevated sentiment sustained for 3+ consecutive days + + Optionally also fetch "sentiment_negative_total" and "sentiment_balance_total" + for a more complete picture. Strongly positive sentiment balance (>+0.5) + during rallies is a bearish signal. + + Step 3: Check for network activity vs. price behavior divergence + Use the fetch_metric_data tool to check network activity: + - metric: "daily_active_addresses" + - slugs: [your target asset] + - time_period: "30d" + - interval: "1d" + + Compare the trend in daily active addresses with price movement. If price has + risen 20% or more but daily active addresses remain flat or are declining, + this divergence signals a potential top. Healthy rallies are supported by + increasing on-chain activity. + + Optionally also check "network_growth" (new addresses) for additional confirmation. + + Step 4: Check MVRV ratio for overbought valuation + Use the fetch_metric_data tool to check valuation: + - metric: "mvrv_usd" + - slugs: [your target asset] + - time_period: "90d" + - interval: "1d" + + MVRV (Market Value to Realized Value) ratio indicates whether holders are + in profit. High MVRV suggests many holders are sitting on gains and may + take profits. Thresholds vary by asset: + - Bitcoin: MVRV > 2.5 indicates overbought (bull market: > 3.5) + - Other assets: Research historical MVRV levels for the specific asset + + Step 5: Check Mean Dollar Invested Age for long-term holder distribution + Use the fetch_metric_data tool to check coin age: + - metric: "mean_dollar_invested_age" + - slugs: [your target asset] + - time_period: "180d" + - interval: "1d" + + MDIA tracks how long funds have stayed in addresses. Rising MDIA indicates + hodler accumulation, while dips suggest movement of previously idle coins. + + Every major Bitcoin top has been accompanied by a significant drop in MDIA + as long-term holders distribute coins. Look for sharp drops (>10%) during + price rallies. This is particularly relevant for Bitcoin and major assets + with long history. + """, + interpretation: """ + ## How to Interpret Combined Signals + + This framework uses multiple indicators. Assess the overall picture: + + **Strong Top Signal (High Confidence)** + When you observe 4-5 of these conditions together: + - Social volume spike 3x+ baseline during rally + - Positive sentiment > 70% sustained 3+ days + - Network activity declining while price rises 20%+ + - MVRV > 2.5 (or asset-specific threshold) + - MDIA drops > 10% during rally + + Action: Consider taking profits or tightening stop losses + + **Moderate Top Signal** + When 2-3 bearish signals are present with mixed signals across categories. + + Action: Monitor closely, consider reducing position size + + **Weak/No Top Signal** + When 0-1 bearish signals present and most metrics show healthy conditions. + + Action: Continue holding, no immediate concern + + ## Important Context + - **Small/mid-cap coins**: Social volume spikes are more reliable indicators + - **Large-cap coins (BTC, ETH)**: MDIA and network activity more important + - **Bull markets**: Higher thresholds needed (MVRV > 3.5 for BTC) + - **Bear markets**: Lower thresholds (MVRV > 1.5 may indicate local top) + - **No single metric**: Always combine multiple data points for robust analysis + + ## Setting Up Alerts + On Sanbase, you can subscribe to alerts for surges in social volume to catch + potential corrections early. Use the Social Trends tool to visualize momentum. + """, + references: [ + %{ + title: "Getting started with Santiment", + url: "https://academy.santiment.net/santiment-introduction/" + }, + %{ + title: "Getting started for traders", + url: "https://academy.santiment.net/for-traders/" + }, + %{ + title: "Understanding Short-Term Market Trends", + url: + "https://academy.santiment.net/education-and-use-cases/understanding-short-term-market-trends/" + }, + %{ + title: "Sentiment metrics", + url: "https://academy.santiment.net/metrics/sentiment-metrics/" + } + ] + } + end +end diff --git a/lib/sanbase/mcp/use_cases_catalog_tool.ex b/lib/sanbase/mcp/use_cases_catalog_tool.ex new file mode 100644 index 000000000..34f0c58d7 --- /dev/null +++ b/lib/sanbase/mcp/use_cases_catalog_tool.ex @@ -0,0 +1,76 @@ +defmodule Sanbase.MCP.UseCasesCatalogTool do + @moduledoc """ + **CALL THIS TOOL FIRST** when answering crypto analysis questions to check if the query + matches any predefined analytical strategies. + + This tool returns a catalog of proven analytical use cases with complete step-by-step + execution instructions. Each use case is a ready-to-use analytical recipe that: + - Identifies the analytical goal (e.g., "Is this asset near a top?") + - Provides 5-10 detailed steps referencing specific MCP tools to call + - Explains how to interpret the combined results + - Ensures comprehensive multi-signal analysis following best practices + + ## When to Use This Tool + + **ALWAYS call this tool FIRST** when the user asks questions like: + - "Is [asset] near a top?" or "Is [asset] overbought?" + - "Should I buy/sell [asset]?" + - "Is [asset] in an accumulation zone?" + - "What's the market sentiment for [asset]?" + - Any question about market timing, price predictions, or trading decisions + + ## How to Use This Tool + + 1. **Call this tool first** (no parameters needed) + 2. **Compare** the user's query to the available use case titles and descriptions + 3. **If a use case matches**: Follow the step-by-step instructions provided + - Each step tells you exactly which tool to call and with what parameters + - Execute the steps in order, gathering data from each + - Synthesize results using the interpretation guide + 4. **If no use case matches**: Proceed with ad-hoc analysis using individual tools + + ## Benefits of Using This Tool First + + - **Comprehensive analysis**: Use cases combine multiple signals (not just one metric) + - **Best practices**: Strategies are based on proven analytical frameworks + - **Time-saving**: Get a complete analytical recipe instead of guessing which metrics to use + - **Better answers**: Multi-signal approaches provide more reliable insights + + ## Example Use Cases Available + + - **Identify Market Tops**: Combines social volume, sentiment, network activity, MVRV, + and mean dollar age to detect potential tops with high confidence + - More use cases will be added over time + + ## Response Format + + Returns a list of use cases, each containing: + - `title`: What analytical question this use case answers + - `steps`: Plain text instructions with specific tool calls and parameters + - `interpretation`: How to combine and interpret the results + """ + + use Anubis.Server.Component, type: :tool + + alias Anubis.Server.Response + alias Sanbase.MCP.UseCasesCatalog + + schema do + end + + @impl true + def execute(_params, frame) do + use_cases = UseCasesCatalog.all_use_cases() + + simplified_use_cases = + Enum.map(use_cases, fn use_case -> + %{ + title: use_case.title, + steps: use_case.steps, + interpretation: use_case.interpretation + } + end) + + {:reply, Response.json(Response.tool(), simplified_use_cases), frame} + end +end diff --git a/test/sanbase/mcp/use_cases_catalog_tool_test.exs b/test/sanbase/mcp/use_cases_catalog_tool_test.exs new file mode 100644 index 000000000..0f92a44c0 --- /dev/null +++ b/test/sanbase/mcp/use_cases_catalog_tool_test.exs @@ -0,0 +1,82 @@ +defmodule Sanbase.MCP.UseCasesCatalogToolTest do + use SanbaseWeb.ConnCase, async: false + + alias Sanbase.MCP.UseCasesCatalog + + describe "UseCasesCatalog" do + test "returns all use cases with required fields" do + use_cases = UseCasesCatalog.all_use_cases() + + assert is_list(use_cases) + assert length(use_cases) > 0 + + Enum.each(use_cases, fn use_case -> + assert use_case.title + assert is_binary(use_case.steps) + assert String.length(use_case.steps) > 100 + assert use_case.interpretation + assert String.contains?(use_case.steps, "Step 1") + end) + end + + test "steps mention tools to use" do + use_cases = UseCasesCatalog.all_use_cases() + + Enum.each(use_cases, fn use_case -> + assert String.contains?(use_case.steps, "tool") or + String.contains?(use_case.steps, "fetch_metric_data") + end) + end + end + + describe "UseCasesCatalogTool execute/2" do + test "returns simplified list of use cases" do + frame = %{assigns: %{}} + + {:reply, response, _frame} = Sanbase.MCP.UseCasesCatalogTool.execute(%{}, frame) + + assert response.content + [%{"text" => json_text, "type" => "text"}] = response.content + use_cases = Jason.decode!(json_text) + + assert is_list(use_cases) + assert length(use_cases) > 0 + end + + test "returned use cases have only title, steps, and interpretation" do + frame = %{assigns: %{}} + + {:reply, response, _frame} = Sanbase.MCP.UseCasesCatalogTool.execute(%{}, frame) + + [%{"text" => json_text, "type" => "text"}] = response.content + use_cases = Jason.decode!(json_text) + use_case = List.first(use_cases) + + assert use_case["title"] + assert use_case["steps"] + assert use_case["interpretation"] + + assert is_binary(use_case["steps"]) + assert String.length(use_case["steps"]) > 100 + + refute Map.has_key?(use_case, "id") + refute Map.has_key?(use_case, "description") + refute Map.has_key?(use_case, "category") + refute Map.has_key?(use_case, "metadata") + end + + test "steps is plain text not a list" do + frame = %{assigns: %{}} + + {:reply, response, _frame} = Sanbase.MCP.UseCasesCatalogTool.execute(%{}, frame) + + [%{"text" => json_text, "type" => "text"}] = response.content + use_cases = Jason.decode!(json_text) + + Enum.each(use_cases, fn use_case -> + assert is_binary(use_case["steps"]) + refute is_list(use_case["steps"]) + end) + end + end +end