Skip to content
Open
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
3 changes: 3 additions & 0 deletions lib/sanbase/mcp/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
153 changes: 153 additions & 0 deletions lib/sanbase/mcp/use_cases_catalog.ex
Original file line number Diff line number Diff line change
@@ -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
76 changes: 76 additions & 0 deletions lib/sanbase/mcp/use_cases_catalog_tool.ex
Original file line number Diff line number Diff line change
@@ -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
82 changes: 82 additions & 0 deletions test/sanbase/mcp/use_cases_catalog_tool_test.exs
Original file line number Diff line number Diff line change
@@ -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