feat: Add Baozi.bet Solana prediction markets (12 new tools)#2
feat: Add Baozi.bet Solana prediction markets (12 new tools)#2bolivian-peru wants to merge 2 commits intoopenSVM:mainfrom
Conversation
Add decentralized prediction market support via Baozi.bet (Solana), making this the first multi-platform prediction market MCP server combining CeFi (Kalshi) and DeFi (Solana) markets in one tool. New tools (12): - baozi_get_markets: List active pari-mutuel markets - baozi_get_market: Get market details by Solana public key - baozi_get_agent_markets: Agent-optimized market list - baozi_get_quote: Calculate bet payout/odds before betting - baozi_get_positions: Wallet portfolio tracking - baozi_get_market_metadata: Batch off-chain metadata - baozi_get_agent_info: Agent ecosystem info - baozi_get_oracle_proofs: Oracle resolution proofs - baozi_get_skill_docs: Protocol documentation - baozi_get_guardrails: Market creation rules v7.2 - baozi_get_program_idl: On-chain program IDL - baozi_get_share_card: Social media share card generation Also adds: - BaoziAPIClient class (follows existing DFlowAPIClient pattern) - 2 new prompts (baozi_market_analysis, cross_platform_comparison) - 3 new resources (baozi://markets, baozi://agents, baozi://docs) - Smithery config: baoziApiUrl parameter - Updated README, CLAUDE.md, AGENTS.md with Baozi documentation Total: 35 tools (23 DFlow + 12 Baozi) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Unable to trigger custom agent "Code Reviewer". You have run out of credits 😔 |
Reviewer's GuideExtends the existing DFlow MCP server into a multi-platform prediction market server by adding a Baozi.bet Solana API client, 12 new Baozi-prefixed read-only tools, two prompts, three resources, and associated documentation/config updates, while keeping all existing DFlow/Kalshi behavior backward compatible. Sequence diagram for the cross_platform_comparison prompt flowsequenceDiagram
actor Agent
participant MCPClient
participant DFlowMCPServer
participant DFlowAPI
participant BaoziAPI
Agent->>MCPClient: Request prompt cross_platform_comparison(topic)
MCPClient->>DFlowMCPServer: GetPrompt(name=cross_platform_comparison, args)
DFlowMCPServer-->>MCPClient: Prompt messages (instructions)
Agent->>MCPClient: Accept and run analysis
MCPClient->>DFlowMCPServer: CallTool name=search_events (DFlow)
DFlowMCPServer->>DFlowAPI: GET /api/v1/search?q=topic
DFlowAPI-->>DFlowMCPServer: JSON events
DFlowMCPServer-->>MCPClient: Tool result events
MCPClient->>DFlowMCPServer: CallTool name=baozi_get_markets (Baozi)
DFlowMCPServer->>BaoziAPI: GET /api/v4/markets?status=Active
BaoziAPI-->>DFlowMCPServer: JSON markets
DFlowMCPServer-->>MCPClient: Tool result markets
MCPClient-->>Agent: Combined comparison (odds, fees, liquidity, settlement)
Class diagram for DFlowAPIClient and BaoziAPIClient in the MCP serverclassDiagram
class ServerConfig {
apiUrl~string~
baoziApiUrl~string~
requestTimeout~number~
}
class DFlowAPIClient {
-baseUrl string
-timeout number
+constructor(baseUrl string, timeout number)
+get(path string, params Record_string_any_) Promise_any_
+post(path string, body any) Promise_any_
}
class BaoziAPIClient {
-baseUrl string
-timeout number
+constructor(baseUrl string, timeout number)
+makeUrl(path string) string
+get(path string, params Record_string_any_) Promise_any_
}
class MCPServerFactory {
+createServer(config ServerConfig) Server
+default(config ServerConfig) Server
}
class Server {
+setRequestHandler(schema any, handler function) void
}
ServerConfig "1" --> "1" DFlowAPIClient : configures
ServerConfig "1" --> "1" BaoziAPIClient : configures
MCPServerFactory --> DFlowAPIClient : instantiates
MCPServerFactory --> BaoziAPIClient : instantiates
MCPServerFactory --> Server : builds
Server --> DFlowAPIClient : uses for DFlow_tools
Server --> BaoziAPIClient : uses for Baozi_tools
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 3 issues, and left some high level feedback:
- The new BaoziAPIClient duplicates most of the HTTP/timeout logic from DFlowAPIClient; consider extracting a shared generic HTTP client or base class to reduce repetition and keep request behavior consistent across platforms.
- The baozi share card handler hardcodes
https://baozi.bet/api/share/cardinstead of deriving from the configuredbaoziApiUrl, which could lead to inconsistencies if the base URL is overridden; using the configured base URL would keep behavior aligned with the rest of the Baozi client. - src/index.ts is getting quite large with the addition of 12 Baozi tools, prompts, and resources; you might want to consider splitting platform-specific tools and handlers (DFlow vs Baozi) into separate modules to keep the main server file more maintainable.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The new BaoziAPIClient duplicates most of the HTTP/timeout logic from DFlowAPIClient; consider extracting a shared generic HTTP client or base class to reduce repetition and keep request behavior consistent across platforms.
- The baozi share card handler hardcodes `https://baozi.bet/api/share/card` instead of deriving from the configured `baoziApiUrl`, which could lead to inconsistencies if the base URL is overridden; using the configured base URL would keep behavior aligned with the rest of the Baozi client.
- src/index.ts is getting quite large with the addition of 12 Baozi tools, prompts, and resources; you might want to consider splitting platform-specific tools and handlers (DFlow vs Baozi) into separate modules to keep the main server file more maintainable.
## Individual Comments
### Comment 1
<location> `src/index.ts:45-48` </location>
<code_context>
// API Configuration
const BASE_URL = 'https://prediction-markets-api.dflow.net';
+const BAOZI_BASE_URL = 'https://baozi.bet';
const DEFAULT_TIMEOUT = 30000; // 30 seconds
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Share-card URL bypasses configurable Baozi base URL
In `baozi_get_share_card`, the URL is built with `new URL('https://baozi.bet/api/share/card')`, which ignores the configurable `baoziApiUrl`/`BAOZI_BASE_URL`. This means the share-card call will always hit the hard-coded host instead of respecting alternate environments (staging, self-hosted, etc.). Please derive this URL from the configured base (`BaoziAPIClient.baseUrl` or `config?.baoziApiUrl`) so it stays consistent with other Baozi calls.
Suggested implementation:
```typescript
// API Configuration
const BASE_URL = 'https://prediction-markets-api.dflow.net';
const DEFAULT_TIMEOUT = 30000; // 30 seconds
```
```typescript
async function baozi_get_share_card(/* existing parameters */) {
// ...other logic...
// Derive the base URL from the configured Baozi client / config, with a sensible default
const baoziBaseUrl =
baoziClient?.baseUrl ??
config?.baoziApiUrl ??
'https://baozi.bet';
const url = new URL('/api/share/card', baoziBaseUrl);
// ...rest of the function...
}
```
Because I only see part of the file, you will need to:
1. Adjust the `baozi_get_share_card` signature and body to use the actual parameter/variable names you have in scope:
- Replace `baoziClient` with your actual `BaoziAPIClient` instance (for example `baoziApiClient` or similar).
- Replace `config` with whatever configuration object you already pass around (or import).
2. Ensure that `BaoziAPIClient.baseUrl` is initialized from `config.baoziApiUrl` when you construct the client, so all Baozi calls (including this share-card one) share the same base URL.
3. If `baozi_get_share_card` does not currently receive access to either the client or config, add an argument (e.g. `baoziClient` or `config`) and thread it through from the call sites.
</issue_to_address>
### Comment 2
<location> `src/index.ts:187` </location>
<code_context>
{
name: 'dflow-mcp-server',
- version: '1.0.0',
+ version: '1.1.0',
},
{
</code_context>
<issue_to_address>
**issue:** Server metadata version diverges from package.json version
The server now reports `version: '1.1.0'` while `package.json` still has `"version": "1.0.0"`. Please align them, or derive this value from `package.json` to prevent future drift.
</issue_to_address>
### Comment 3
<location> `src/index.ts:112` </location>
<code_context>
}
}
+class BaoziAPIClient {
+ private baseUrl: string;
+ private timeout: number;
</code_context>
<issue_to_address>
**issue (complexity):** Consider extracting a shared HttpClient abstraction so DFlowAPIClient and BaoziAPIClient become thin wrappers instead of duplicating HTTP request logic.
You can reduce complexity and duplication by extracting a shared HTTP client and turning `DFlowAPIClient` / `BaoziAPIClient` into thin configuration wrappers (or even plain instances).
Both classes currently duplicate:
- `baseUrl` + normalization
- `timeout` + `AbortController` logic
- query param handling
- error handling
- `content-type` dispatch
You can centralize this without changing behavior by introducing a generic `HttpClient` and reusing it:
```ts
class HttpClient {
private baseUrl: string;
private timeout: number;
private defaultHeaders: Record<string, string>;
constructor(
baseUrl: string,
timeout: number,
defaultHeaders: Record<string, string> = {},
) {
this.baseUrl = baseUrl.replace(/\/$/, '');
this.timeout = timeout;
this.defaultHeaders = defaultHeaders;
}
private makeUrl(path: string): string {
return `${this.baseUrl}${path.startsWith('/') ? path : '/' + path}`;
}
async get(path: string, params?: Record<string, any>): Promise<any> {
const url = new URL(this.makeUrl(path));
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
url.searchParams.append(key, String(value));
}
});
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
try {
const response = await fetch(url.toString(), {
method: 'GET',
headers: this.defaultHeaders,
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
const contentType = response.headers.get('content-type') || '';
if (contentType.includes('application/json')) {
return await response.json();
}
return { content: await response.text(), contentType };
} catch (error) {
clearTimeout(timeoutId);
if (error instanceof Error && error.name === 'AbortError') {
throw new Error('Request timeout');
}
throw error;
}
}
async post(path: string, data?: any): Promise<any> {
return this.request('POST', path, {
body: data ? JSON.stringify(data) : undefined,
});
}
// you can keep your existing request() implementation here
private async request(
method: string,
path: string,
options: { body?: string } = {},
): Promise<any> {
// existing DFlow request logic moved here
}
}
```
Then configure per-API clients instead of reimplementing them:
```ts
const dflowClient = new HttpClient(
config?.apiUrl || BASE_URL,
config?.requestTimeout || DEFAULT_TIMEOUT,
{ Accept: 'application/json' }, // whatever DFlow needs
);
const baoziClient = new HttpClient(
config?.baoziApiUrl || BAOZI_BASE_URL,
config?.requestTimeout || DEFAULT_TIMEOUT,
{ Accept: 'application/json, text/markdown, text/plain' },
);
```
If you still want named classes for type clarity, they can just wrap `HttpClient` without duplicating logic:
```ts
class BaoziAPIClient {
constructor(private client: HttpClient) {}
get(path: string, params?: Record<string, any>) {
return this.client.get(path, params);
}
// add Baozi-specific helpers here if needed
}
```
This keeps all timeout/error/content-type behavior in one place, while preserving your Baozi features and differing headers/URLs.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| } | ||
| } | ||
|
|
||
| class BaoziAPIClient { |
There was a problem hiding this comment.
issue (complexity): Consider extracting a shared HttpClient abstraction so DFlowAPIClient and BaoziAPIClient become thin wrappers instead of duplicating HTTP request logic.
You can reduce complexity and duplication by extracting a shared HTTP client and turning DFlowAPIClient / BaoziAPIClient into thin configuration wrappers (or even plain instances).
Both classes currently duplicate:
baseUrl+ normalizationtimeout+AbortControllerlogic- query param handling
- error handling
content-typedispatch
You can centralize this without changing behavior by introducing a generic HttpClient and reusing it:
class HttpClient {
private baseUrl: string;
private timeout: number;
private defaultHeaders: Record<string, string>;
constructor(
baseUrl: string,
timeout: number,
defaultHeaders: Record<string, string> = {},
) {
this.baseUrl = baseUrl.replace(/\/$/, '');
this.timeout = timeout;
this.defaultHeaders = defaultHeaders;
}
private makeUrl(path: string): string {
return `${this.baseUrl}${path.startsWith('/') ? path : '/' + path}`;
}
async get(path: string, params?: Record<string, any>): Promise<any> {
const url = new URL(this.makeUrl(path));
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
url.searchParams.append(key, String(value));
}
});
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
try {
const response = await fetch(url.toString(), {
method: 'GET',
headers: this.defaultHeaders,
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
const contentType = response.headers.get('content-type') || '';
if (contentType.includes('application/json')) {
return await response.json();
}
return { content: await response.text(), contentType };
} catch (error) {
clearTimeout(timeoutId);
if (error instanceof Error && error.name === 'AbortError') {
throw new Error('Request timeout');
}
throw error;
}
}
async post(path: string, data?: any): Promise<any> {
return this.request('POST', path, {
body: data ? JSON.stringify(data) : undefined,
});
}
// you can keep your existing request() implementation here
private async request(
method: string,
path: string,
options: { body?: string } = {},
): Promise<any> {
// existing DFlow request logic moved here
}
}Then configure per-API clients instead of reimplementing them:
const dflowClient = new HttpClient(
config?.apiUrl || BASE_URL,
config?.requestTimeout || DEFAULT_TIMEOUT,
{ Accept: 'application/json' }, // whatever DFlow needs
);
const baoziClient = new HttpClient(
config?.baoziApiUrl || BAOZI_BASE_URL,
config?.requestTimeout || DEFAULT_TIMEOUT,
{ Accept: 'application/json, text/markdown, text/plain' },
);If you still want named classes for type clarity, they can just wrap HttpClient without duplicating logic:
class BaoziAPIClient {
constructor(private client: HttpClient) {}
get(path: string, params?: Record<string, any>) {
return this.client.get(path, params);
}
// add Baozi-specific helpers here if needed
}This keeps all timeout/error/content-type behavior in one place, while preserving your Baozi features and differing headers/URLs.
- Fix baozi_get_share_card to use configured baoziClient.baseUrl instead of hardcoded https://baozi.bet (supports staging/self-hosted) - Make BaoziAPIClient.baseUrl readonly instead of private - Align package.json version with server metadata (both 1.1.0) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Thanks @sourcery-ai — great catches! Addressed in the latest push (51acf03): Fixed
Acknowledged (intentional for this PR)
|
CI NoteThe failing Evidence: The same CI job has been failing on
Likely cause: GitHub Actions minutes quota exceeded or runner configuration issue. The workflow itself ( Our code: TypeScript compiles clean locally ( |
Summary
Adds Baozi.bet — the first decentralized prediction market platform on Solana — as a second data source, making this the first multi-platform prediction market MCP server combining CeFi (Kalshi via DFlow) and DeFi (Solana) markets in one tool.
35 total tools (23 existing DFlow + 12 new Baozi)
Why This Matters
cross_platform_comparisonprompt lets agents find the same events on both platforms and compare odds, fees, and liquiditybaozi_prefix to avoid any naming conflictsBaoziAPIClientfollows the exact same pattern asDFlowAPIClient(timeout handling, URL construction, error propagation)New Baozi.bet Tools (12)
baozi_get_marketsbaozi_get_marketbaozi_get_agent_marketsbaozi_get_quotebaozi_get_positionsbaozi_get_market_metadatabaozi_get_agent_infobaozi_get_oracle_proofsbaozi_get_skill_docsbaozi_get_guardrailsbaozi_get_program_idlbaozi_get_share_cardNew Prompts (2)
baozi_market_analysiscross_platform_comparisonNew Resources (3)
baozi://marketsbaozi://agentsbaozi://docsAbout Baozi.bet
Baozi.bet is a live prediction market protocol on Solana mainnet:
@baozi.bet/mcp-server, agent registration, affiliate commissionsFWyTPzm5cfJwRKzfkscxozatSxF6Qu78JQovQUwKPruJ(Solana mainnet, live)Links: Website | Skill Docs | Agent Kitchen | npm | Twitter/X
Technical Details
Architecture
BaoziAPIClientclass following exact same pattern asDFlowAPIClientbaozi_prefix — zero risk of naming conflicts with existing toolsbaoziApiUrlparameter (defaults tohttps://baozi.bet)readOnlyHint: true,destructiveHint: false)Files Changed
src/index.tsREADME.mdCLAUDE.mdAGENTS.mdpackage.jsonNo Breaking Changes
1.0.0to1.1.0(semver minor)baoziApiUrlhas default)Test Plan
baozi_get_marketsreturns active Solana marketsbaozi_get_quotereturns valid payout calculationsbaozi_get_agent_inforeturns agent ecosystem databaozi_get_oracle_proofsreturns resolution proofsbaozi_get_share_cardreturns valid image URLbun run tsc --noEmit)bun run build)🤖 Generated with Claude Code
Summary by Sourcery
Add Baozi.bet as a second prediction market data source to the MCP server alongside DFlow/Kalshi, expanding the toolset and enabling cross-platform analysis between CeFi and DeFi markets.
New Features:
Enhancements: