Skip to content

feat: Add Baozi.bet Solana prediction markets (12 new tools)#2

Open
bolivian-peru wants to merge 2 commits intoopenSVM:mainfrom
bolivian-peru:feat/baozi-solana-prediction-markets
Open

feat: Add Baozi.bet Solana prediction markets (12 new tools)#2
bolivian-peru wants to merge 2 commits intoopenSVM:mainfrom
bolivian-peru:feat/baozi-solana-prediction-markets

Conversation

@bolivian-peru
Copy link

@bolivian-peru bolivian-peru commented Feb 20, 2026

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

  • CeFi + DeFi in one MCP server — Agents can now compare prediction markets across centralized (Kalshi, CFTC-regulated) and decentralized (Solana, on-chain) ecosystems
  • Cross-platform analysis — New cross_platform_comparison prompt lets agents find the same events on both platforms and compare odds, fees, and liquidity
  • Zero breaking changes — All 23 existing DFlow tools work exactly as before. Baozi tools use baozi_ prefix to avoid any naming conflicts
  • Same architectureBaoziAPIClient follows the exact same pattern as DFlowAPIClient (timeout handling, URL construction, error propagation)

New Baozi.bet Tools (12)

Tool Description
baozi_get_markets List active pari-mutuel markets with live odds and pool sizes
baozi_get_market Get detailed market info by Solana public key
baozi_get_agent_markets Agent-optimized market list (filter by betting status)
baozi_get_quote Calculate exact payout, fees, and odds before placing a bet
baozi_get_positions Get all positions for a Solana wallet with P&L data
baozi_get_market_metadata Batch fetch off-chain metadata (descriptions, rules, images)
baozi_get_agent_info Agent Kitchen info — registered agents, MCP tools, fees
baozi_get_oracle_proofs Oracle resolution proofs with evidence and rationale
baozi_get_skill_docs Complete protocol documentation (69 MCP tools reference)
baozi_get_guardrails Pari-mutuel market creation rules v7.2
baozi_get_program_idl Anchor IDL for direct on-chain interaction
baozi_get_share_card Generate 1200x630 PNG share card for any market

New Prompts (2)

Prompt Description
baozi_market_analysis Find best Baozi.bet opportunities (odds, pools, timing)
cross_platform_comparison Compare the same topics across Kalshi and Baozi.bet — find arbitrage between CeFi and DeFi

New Resources (3)

URI Description
baozi://markets Active Solana prediction markets with live odds
baozi://agents Agent ecosystem info (registered agents, fees, MCP details)
baozi://docs Complete Baozi protocol documentation

About Baozi.bet

Baozi.bet is a live prediction market protocol on Solana mainnet:

  • Pari-mutuel pools — Odds shift dynamically with every bet (no order book needed)
  • SOL-native — Bet and win in SOL, no USDC or wrapped tokens
  • 3 market layers — Official (curated), Labs (community-created), Private (invite-only)
  • AI agent-first — 69 MCP tools via @baozi.bet/mcp-server, agent registration, affiliate commissions
  • On-chain oracle — "Grandma Mei" oracle with transparent resolution proofs and 6-hour dispute window
  • $BAOZI token — Revenue sharing with stakers from protocol fees
  • Program: FWyTPzm5cfJwRKzfkscxozatSxF6Qu78JQovQUwKPruJ (Solana mainnet, live)

Links: Website | Skill Docs | Agent Kitchen | npm | Twitter/X

Technical Details

Architecture

  • Added BaoziAPIClient class following exact same pattern as DFlowAPIClient
  • All Baozi tools use baozi_ prefix — zero risk of naming conflicts with existing tools
  • Smithery config schema extended with optional baoziApiUrl parameter (defaults to https://baozi.bet)
  • All Baozi tools are read-only (readOnlyHint: true, destructiveHint: false)

Files Changed

File Changes
src/index.ts +548 lines — BaoziAPIClient, 12 tools, handlers, prompts, resources
README.md Updated with Baozi tools, examples, platform overview
CLAUDE.md Added Baozi architecture docs and endpoint reference
AGENTS.md Added Baozi tool categories and API endpoint table
package.json Updated description and keywords

No Breaking Changes

  • All 23 existing DFlow tools unchanged
  • Server name updated from 1.0.0 to 1.1.0 (semver minor)
  • All existing prompts and resources preserved
  • Smithery config is backwards-compatible (new baoziApiUrl has default)

Test Plan

  • Verify all 23 existing DFlow tools still work (no regressions)
  • Test baozi_get_markets returns active Solana markets
  • Test baozi_get_quote returns valid payout calculations
  • Test baozi_get_agent_info returns agent ecosystem data
  • Test baozi_get_oracle_proofs returns resolution proofs
  • Test baozi_get_share_card returns valid image URL
  • Verify TypeScript compilation passes (bun run tsc --noEmit)
  • Verify Smithery build works (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:

  • Introduce BaoziAPIClient and configuration for accessing Baozi.bet Solana prediction market APIs with customizable base URL and timeouts.
  • Add 12 read-only Baozi MCP tools for markets, quotes, wallet positions, metadata, agent info, oracle proofs, protocol docs, guardrails, program IDL, and share-card generation.
  • Expose new MCP prompts for Baozi market analysis and cross-platform comparison between DFlow/Kalshi and Baozi.bet markets.
  • Add three Baozi-specific MCP resources for active markets, agent ecosystem data, and full protocol documentation.

Enhancements:

  • Update server version, configuration schema, and factory export to support multi-platform prediction markets while preserving backwards compatibility for existing DFlow tools.
  • Expand README, AGENTS.md, and CLAUDE.md with multi-platform architecture details, tool catalogs, prompts, resources, and Baozi.bet platform overview.
  • Improve package metadata and keywords to reflect multi-platform, CeFi/DeFi, and oracle-focused prediction market capabilities.

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>
@devloai
Copy link

devloai bot commented Feb 20, 2026

Unable to trigger custom agent "Code Reviewer". You have run out of credits 😔
Please upgrade your plan or buy additional credits from the subscription page.

@sourcery-ai
Copy link

sourcery-ai bot commented Feb 20, 2026

Reviewer's Guide

Extends 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 flow

sequenceDiagram
    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)
Loading

Class diagram for DFlowAPIClient and BaoziAPIClient in the MCP server

classDiagram
    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
Loading

File-Level Changes

Change Details Files
Introduce BaoziAPIClient and wire it into the MCP server alongside the existing DFlow client, including configuration and export plumbing.
  • Extend configSchema and ServerConfig to accept an optional baoziApiUrl with a default of https://baozi.bet
  • Define BAOZI_BASE_URL constant and add BaoziAPIClient class mirroring DFlowAPIClient behavior (URL construction, query params, timeout, error handling, JSON vs text responses)
  • Instantiate baoziClient in createServer using shared timeout and new baoziApiUrl
  • Update server metadata version from 1.0.0 to 1.1.0 to reflect new, backward-compatible features
  • Ensure default export passes through baoziApiUrl in the Smithery server factory config
src/index.ts
Add 12 Baozi.bet Solana prediction market MCP tools and route them to Baozi API endpoints.
  • Append Baozi-prefixed tool definitions (baozi_get_markets, baozi_get_market, baozi_get_agent_markets, baozi_get_quote, baozi_get_positions, baozi_get_market_metadata, baozi_get_agent_info, baozi_get_oracle_proofs, baozi_get_skill_docs, baozi_get_guardrails, baozi_get_program_idl, baozi_get_share_card) to the TOOLS array with detailed input schemas and read-only annotations
  • Extend CallToolRequestSchema handler switch to call appropriate Baozi endpoints via baoziClient.get for each new tool, mapping args to query/path params
  • Implement baozi_get_share_card handler without an API call by constructing a share card URL with URLSearchParams and returning a structured object with imageUrl and metadata
src/index.ts
Expose new Baozi-aware prompts and resources for higher-level workflows and data access.
  • Extend ListPromptsRequestSchema handler to return baozi_market_analysis and cross_platform_comparison prompts with argument metadata
  • Implement GetPromptRequestSchema handler branches that synthesize user messages instructing the model to orchestrate Baozi and cross-platform tool calls
  • Extend ListResourcesRequestSchema handler to register baozi://markets, baozi://agents, and baozi://docs resources with appropriate MIME types
  • Implement GetResourceRequestSchema branches that fetch Baozi markets, agents, and docs via baoziClient and serialize them into MCP resource contents
src/index.ts
Update documentation to describe the new multi-platform architecture, tools, prompts, and resources, and to document Baozi-specific details for agents and Claude Code.
  • Rewrite and expand README to position the server as a combined DFlow/Kalshi + Baozi.bet MCP server, add platform comparison table, describe all 35 tools grouped by platform, add Baozi usage examples, prompts/resources tables, and Baozi platform overview/fees/agent guidance
  • Update AGENTS.md to reflect the dual-platform design, document Baozi tool categories and API endpoint table, and clarify development patterns and tips for both DFlow and Baozi tools
  • Revise CLAUDE.md to explain the two-platform architecture, add BaoziAPIClient and Baozi tool categories, and document configuration options including baoziApiUrl
README.md
AGENTS.md
CLAUDE.md
Adjust package metadata to reflect the expanded feature set and improve discoverability.
  • Update package.json description to emphasize multi-platform prediction markets and Baozi/Kalshi coverage
  • Add new keywords for Baozi, Kalshi, Solana, DeFi, pari-mutuel, oracle, and AI agents to aid discovery
  • (Note: version field in package.json remains 1.0.0; only the MCP server version string in code is bumped to 1.1.0)
package.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/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.
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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

}
}

class BaoziAPIClient {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

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>
@bolivian-peru
Copy link
Author

Thanks @sourcery-ai — great catches! Addressed in the latest push (51acf03):

Fixed

  1. Share card URL hardcodebaozi_get_share_card now uses baoziClient.baseUrl instead of hardcoded https://baozi.bet. Changed BaoziAPIClient.baseUrl from private to readonly to enable this. Staging/self-hosted environments will now work correctly.

  2. Version mismatchpackage.json bumped to 1.1.0 to match the server metadata version. Both are now aligned.

Acknowledged (intentional for this PR)

  1. Shared HttpClient abstraction — Agreed this is the right long-term direction. However, extracting a shared base class would require refactoring the existing DFlowAPIClient (which has post() support and slightly different error handling), and I wanted to keep this PR's blast radius minimal — only additive changes to the existing codebase. The two clients are small enough (~50 lines each) that the duplication is manageable. Happy to follow up with a refactor PR if the maintainers prefer.

  2. Single-file size — Same reasoning — splitting into modules is a bigger architectural change that I'd rather leave to the maintainers' preference. The single-file pattern was an intentional design choice in the original codebase (documented in CLAUDE.md), and the Baozi tools follow the exact same patterns for consistency.

@bolivian-peru
Copy link
Author

CI Note

The failing Test MCP Server / test check is a pre-existing repo-level issue, not caused by this PR.

Evidence: The same CI job has been failing on main since December 2025 — all 3 most recent runs on main also fail with the same pattern (0 steps executed, 2s duration, runner never starts):

Run Branch Date Result
19874844071 main Dec 2, 2025 failure (2s, 0 steps)
19873982508 main Dec 2, 2025 failure (2s, 0 steps)
19873827901 main Dec 2, 2025 failure (2s, 0 steps)

Likely cause: GitHub Actions minutes quota exceeded or runner configuration issue. The workflow itself (test.yml) looks correct — it just never gets to execute any steps.

Our code: TypeScript compiles clean locally (tsc --noEmit passes with zero errors), all 11 Baozi API endpoints return valid data, and all 23 existing DFlow tools are completely unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant