Skip to content

Commit 96439d0

Browse files
adambaloghbalogh.adam@icloud.com
andauthored
Add ChainOS tools (#121)
* chainos * mcp * rm skills * Delete skills-lock.json * custom api key * nonoptional * fix req * prompt * http * fix * clean code * adjust style * tool filter * mcp --------- Co-authored-by: balogh.adam@icloud.com <adambalogh@mac.mynetworksettings.com>
1 parent f4ffc67 commit 96439d0

7 files changed

Lines changed: 115 additions & 32 deletions

File tree

agent/agent_executors.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,6 @@
6363
GOOGLE_GEMINI_20_FLASH_MODEL = (
6464
"gemini-2.0-flash" # $0.1/M input tokens; $0.4/M output tokens
6565
)
66-
LLAMA_3_1_405B_MODEL = (
67-
"meta-llama/llama-3.1-405b-instruct" # $0.8/M input tokens; $0.8/M output tokens
68-
)
69-
DEEPSEEK_CHAT_V3_MODEL = (
70-
"deepseek/deepseek-chat-v3-0324" # $0.27/M input tokens; $1.1/M output tokens
71-
)
7266
GROK_MODEL = "x-ai/grok-2-1212" # $2/M input tokens; $10/M output tokens
7367

7468
x402_http_client = x402HttpxClientv2(
@@ -83,8 +77,6 @@
8377
# Select model based on configuration
8478
SUGGESTIONS_MODEL = GOOGLE_GEMINI_20_FLASH_MODEL
8579
REASONING_MODEL = GOOGLE_GEMINI_20_FLASH_MODEL
86-
BASE_URL = "https://generativelanguage.googleapis.com/v1beta/"
87-
API_KEY = os.getenv("GEMINI_API_KEY")
8880

8981

9082
def create_suggestions_model() -> BaseChatModel:
@@ -119,7 +111,10 @@ def create_investor_executor() -> any:
119111
return agent_executor
120112

121113

122-
def create_analytics_executor(token_metadata_repo: TokenMetadataRepo) -> any:
114+
def create_analytics_executor(
115+
token_metadata_repo: TokenMetadataRepo,
116+
extra_tools: list = None,
117+
) -> any:
123118
openai_model = ChatOpenAI(
124119
model=REASONING_MODEL,
125120
temperature=0.0,
@@ -131,9 +126,13 @@ def create_analytics_executor(token_metadata_repo: TokenMetadataRepo) -> any:
131126
base_url=config.LLM_SERVER_URL,
132127
)
133128

129+
tools = create_analytics_agent_toolkit(token_metadata_repo)
130+
if extra_tools:
131+
tools.extend(extra_tools)
132+
134133
analytics_executor = create_react_agent(
135134
model=openai_model,
136-
tools=create_analytics_agent_toolkit(token_metadata_repo),
135+
tools=tools,
137136
)
138137

139138
return analytics_executor

agent/prompts.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,5 +105,3 @@ def get_analytics_prompt(
105105
)
106106

107107
return analytics_agent_prompt
108-
109-

onchain/okx/__init__.py

Whitespace-only changes.

onchain/okx/mcp_client.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import logging
2+
import os
3+
from typing import List
4+
5+
from langchain_core.tools import BaseTool
6+
from langchain_mcp_adapters.client import MultiServerMCPClient
7+
8+
logger = logging.getLogger(__name__)
9+
10+
OKX_MCP_URL = "https://web3.okx.com/api/v1/onchainos-mcp"
11+
12+
# Allowlist of OKX MCP tools to expose (read-only market data only)
13+
ALLOWED_TOOLS = {
14+
# Market prices & charts
15+
"dex-okx-market-price",
16+
"dex-okx-market-candlesticks",
17+
"dex-okx-market-candlesticks-history",
18+
"dex-okx-market-price-chains",
19+
# Token discovery & analytics
20+
"dex-okx-market-token-search",
21+
"dex-okx-market-token-basic-info",
22+
"dex-okx-market-token-ranking",
23+
"dex-okx-market-token-holder",
24+
# Smart money signals
25+
"dex-okx-market-signal-list",
26+
"dex-okx-market-signal-supported-chains",
27+
# Balance / portfolio (read-only)
28+
"dex-okx-balance-chains",
29+
"dex-okx-balance-total-token-balances",
30+
"dex-okx-balance-total-value",
31+
"dex-okx-balance-specific-token-balance",
32+
# DEX
33+
"dex-okx-dex-liquidity",
34+
}
35+
36+
37+
class OKXMCPClient:
38+
"""Manages the OKX MCP connection and exposes market data tools for the agent."""
39+
40+
def __init__(self, api_key: str | None = None):
41+
self._api_key = api_key or os.getenv("OKX_API_KEY", "")
42+
self._client = None
43+
self._tools: List[BaseTool] = []
44+
45+
async def connect(self) -> None:
46+
"""Connect to OKX MCP server and load market data tools."""
47+
self._client = MultiServerMCPClient(
48+
{
49+
"okx": {
50+
"transport": "streamable_http",
51+
"url": OKX_MCP_URL,
52+
"headers": {
53+
"OK-ACCESS-KEY": self._api_key,
54+
},
55+
}
56+
}
57+
)
58+
59+
all_tools = await self._client.get_tools()
60+
self._tools = [t for t in all_tools if t.name in ALLOWED_TOOLS]
61+
62+
blocked = [t.name for t in all_tools if t.name not in ALLOWED_TOOLS]
63+
logger.info(
64+
f"OKX MCP: loaded {len(self._tools)} market data tools, "
65+
f"blocked {len(blocked)} execution tools: {blocked}"
66+
)
67+
68+
async def disconnect(self) -> None:
69+
"""Disconnect from the MCP server."""
70+
self._client = None
71+
self._tools = []
72+
73+
def get_tools(self) -> List[BaseTool]:
74+
"""Return the loaded market data tools for use in an agent toolkit."""
75+
return self._tools

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,5 @@ pytest-asyncio>=0.21.0
3030
og-test-v2-x402==0.0.11.dev3
3131
eth-account>=0.13.4
3232
web3>=7.3.0
33-
cachetools>=6.2.4
33+
cachetools>=6.2.4
34+
langchain-mcp-adapters==0.1.14

server/fastapi_server.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from onchain.pools.solana.kamino_protocol import KaminoProtocol
2020
from onchain.tokens.metadata import TokenMetadataRepo
2121
from onchain.portfolio.solana_portfolio import PortfolioFetcher
22+
from onchain.okx.mcp_client import OKXMCPClient
2223
from api.api_types import (
2324
AgentChatRequest,
2425
AgentMessage,
@@ -127,34 +128,42 @@ def create_fastapi_app() -> FastAPI:
127128
app.state.jup_validator = jup_validator
128129
app.state.cow_validator = cow_validator
129130

131+
# Initialize OKX MCP client
132+
okx_mcp_client = OKXMCPClient()
133+
130134
@app.on_event("shutdown")
131135
async def shutdown_event():
136+
await okx_mcp_client.disconnect()
132137
await protocol_registry.shutdown()
133138
await token_metadata_repo.close()
134139
await portfolio_fetcher.close()
135140
await jup_validator.close()
136141
await cow_validator.close()
137142

138-
# Initialize agents
139-
suggestions_model = create_suggestions_model()
140-
analytics_agent = create_analytics_executor(token_metadata_repo)
141-
investor_agent = create_investor_executor()
142-
143143
# Initialize protocol registry
144144
protocol_registry = ProtocolRegistry(token_metadata_repo)
145145
protocol_registry.register_protocol(OrcaProtocol())
146146
protocol_registry.register_protocol(SaveProtocol())
147147
protocol_registry.register_protocol(KaminoProtocol())
148148

149-
# Store agents in app state
149+
# Store in app state (agents created at startup after OKX MCP connects)
150+
suggestions_model = create_suggestions_model()
151+
investor_agent = create_investor_executor()
150152
app.state.suggestions_model = suggestions_model
151-
app.state.analytics_agent = analytics_agent
152153
app.state.investor_agent = investor_agent
153154
app.state.protocol_registry = protocol_registry
154155

155156
@app.on_event("startup")
156157
async def startup_event():
157158
await protocol_registry.initialize()
159+
await okx_mcp_client.connect()
160+
okx_tools = okx_mcp_client.get_tools()
161+
app.state.analytics_agent = create_analytics_executor(
162+
token_metadata_repo, extra_tools=okx_tools
163+
)
164+
logging.info(
165+
f"Analytics agent created with {len(okx_tools)} OKX market data tools"
166+
)
158167

159168
# Exception handlers
160169
@app.exception_handler(ValidationError)
@@ -315,7 +324,7 @@ async def run_agent(
315324
request=agent_request,
316325
portfolio=portfolio,
317326
investor_agent=investor_agent,
318-
analytics_agent=analytics_agent,
327+
analytics_agent=app.state.analytics_agent,
319328
)
320329

321330
return (

templates/analytics_agent.jinja2

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
1-
You are OpenGradient's razor-sharp degen crypto analyst BitQuant.
2-
You embody the crisp demeanor of a degen advisor with a decisive edge.
1+
You are OpenGradient's razor-sharp crypto analyst BitQuant.
2+
You embody the crisp demeanor of a crypto analyst with a decisive edge.
33
You're authoritative, funny, and efficiently cut to the chase, delivering calculated moves and data-backed information.
44
You help users analyze market trends, evaluate protocols, tokens, buy and swap tokens on Solana chain, and stay up to date with the latest trends in the crypto space.
55

6-
IMPORTANT: ALWAYS use the provided tools to answer questions instead of relying on general knowledge or analysis.
6+
IMPORTANT: ALWAYS use the provided tools to answer questions instead of relying on general knowledge or analysis. You have access to ALL tools in the tools parameter — use whichever tools are most appropriate.
77

88
## Supported Use Cases:
99

10-
- Buy or swap a token on Solana: first make sure you have the token metadata using the `search_token()` tool or in the message history, then return the token ID to buy in the following format: ```swap:<insert token_id>```. Do not make up the token ID yourself.
11-
- Look up a token on any chain: use the `search_token()` tool to get the token metadata. In your answer, include the ID of the token in the following format: ```token:<insert token_id>```.
12-
- Get the price of a token: use the `get_coingecko_current_price()` tool to get the price of a token. Use the CoinGecko ID for all CoinGecko tools, i.e. 'bitcoin', instead of the symbol, 'BTC'.
13-
- Analyze a user's portfolio: use the `analyze_wallet_portfolio()` tool to get the portfolio analysis
14-
- Analyze market trends: look at TVL on major chains using show_defi_llama_historical_global_tvl() and analyze_price_trend() for major tokens like BTC and ETH to get an overall sentiment and direction in the market
15-
- Analyze trending tokens or memecoins on a chain: use the `get_trending_tokens()` tool to get the trending tokens. In your answer, include the ID of each token you mention in the following format: ```token:<insert token_id>```.
16-
- Analyze the risk of a token: use the `evaluate_token_risk()` tool to get the risk analysis. If you are unsure about the token address, use the `search_token()` tool to get the token metadata (don't ask for confirmation).
17-
- Analyze the top holders of a token: use the `get_top_token_holders()` tool to get the top holders of the token.
10+
- Buy or swap a token on Solana: first make sure you have the token metadata using a token search tool or from the message history, then return the token ID to buy in the following format: ```swap:<insert token_id>```. Do not make up the token ID yourself.
11+
- Look up a token on any chain. In your answer, include the ID of the token in the following format: ```token:<insert token_id>```.
12+
- Get the price of a token. Use the CoinGecko ID for CoinGecko tools, i.e. 'bitcoin', instead of the symbol, 'BTC'.
13+
- Analyze a user's portfolio
14+
- Analyze market trends across chains
15+
- Analyze trending tokens or memecoins on a chain. In your answer, include the ID of each token you mention in the following format: ```token:<insert token_id>```.
16+
- Analyze the risk of a token. If you are unsure about the token address, search for the token metadata first (don't ask for confirmation).
17+
- Analyze the top holders of a token
18+
- DEX market data, smart money signals, meme token analytics
1819

1920
If the user asks a more complex question, you can combine and use the tools to get the information you need to answer the question. Be intelligent and careful with the tools you use.
2021

0 commit comments

Comments
 (0)