Skip to main content

熔岩AI MCP之外的 HTTP 调用:基于 OpenAPI 的 Python 与 TypeScript 客户端骨架

Updated 3 min readMarkdown

摘要

不用 MCP、直接写程序调用熔岩(Lava)消息面热度数据,最稳的起点是它的 OpenAPI 3.0.3 说明书(/subscribe/v1/openapi.json):18 个只读 GET 接口、统一的 ok / code / data / meta 外壳、两种鉴权方式(Authorization: Bearer 或 X-API-Key)。本文给出一个可直接复制的 Python 与 TypeScript 最小客户端骨架:统一封装鉴权、外壳解析、错误码分类和计费头读取,并按熔岩数据板块组织方法名,方法名与 OpenAPI 的 operationId 一一对应。

一、先看 OpenAPI 里有什么

项 内容
版本 OpenAPI 3.0.3
服务地址前缀 <熔岩订阅站点>/subscribe/v1
鉴权 bearerAuth(Authorization: Bearer lvk_…)、apiKeyHeader(X-API-Key)
接口 18 个 GET,全部只读
外壳 Envelope:ok、code、message、data、meta

响应 data 内部的列名,OpenAPI 只给了部分结构(如 Opinion 记录),其余以 OpenAPI 为准、以实际返回为准。骨架里因此只对外壳做强类型,data 保持宽松。

二、按数据板块的方法映射

数据板块 operationId 路径 MCP 工具
个股消息面 listOpinionsByStock /opinions get_stock_news_heat
个股消息面 getStockHeatWindows /heat/windows get_stock_heat_windows
关键词检索 searchOpinions /opinions/search search_news_flow
全市场榜单 listHotStocks、listAcceleratingHotStocks、listFirstMentions /hot/stocks、/hot/accelerating、/hot/first-mentions get_market_hot_stocks 等
概念板块热度 listHotConcepts、listConceptStocks、listStockConcepts /hot/concepts、/concepts/stocks、/concepts/by-stock get_market_hot_concepts 等
机构/大V评分 listAgencyRanking、listAgencyPit、getAgencyHistory、listAgencyPitDates、listWeightedHotStocks /agencies/...、/hot/weighted get_agency_ranking 等
历史面板与覆盖度 getDailyHeatPanel、getStockAgencyCoverage /panel/daily、/coverage/stocks get_stock_heat_panel、get_agency_coverage
新闻点名 listNewsStockHits /news/hits get_news_stock_hits
账户 getAccount /account get_account(不计费)

三、Python 骨架

import os, time, requests
class LavaError(Exception):
    def __init__(self, status, code, message, request_id):
        super().__init__(f"{status} {code} {message} rid={request_id}")
        self.status, self.code, self.request_id = status, code, request_id

class LavaClient:
    def __init__(self, base=None, key=None, timeout=30):
        self.base = base or os.environ["LAVA_BASE"]      # 形如 <熔岩订阅站点>/subscribe/v1
        self.s = requests.Session()
        self.s.headers["Authorization"] = f"Bearer {key or os.environ['LAVA_KEY']}"
        self.timeout = timeout

    def _get(self, path, **params):
        params = {k: v for k, v in params.items() if v is not None}
        for i in range(3):
            r = self.s.get(self.base + path, params=params, timeout=self.timeout)
            b = r.json()
            if b.get("ok"):
                b["meta"]["charged_hdr"] = r.headers.get("X-Billing-Charged-Yuan")
                return b["data"], b["meta"]
            if r.status_code in (429, 500, 503) and i < 2:
                time.sleep(int(r.headers.get("Retry-After", 2 ** (i + 1))))
                continue
            raise LavaError(r.status_code, b.get("code"), b.get("message"),
                            b.get("meta", {}).get("request_id"))

    # 方法名对应 operationId
    def listHotStocks(self, window="24h", market=None, min_score=None, limit=50):
        return self._get("/hot/stocks", window=window, market=market, min_score=min_score, limit=limit)

    def getDailyHeatPanel(self, type="stock", codes=None, names=None, frm=None, to=None):
        return self._get("/panel/daily", type=type, codes=codes, names=names, **{"from": frm, "to": to})

    def getAccount(self):                                  # 不计费
        return self._get("/account")

from 是 Python 关键字,所以参数名用 frm,发请求时再映射回 from。

四、TypeScript 骨架

type Envelope<T> = {
  ok: boolean; code: string; message: string; data: T | null;
  meta: { api_version: string; request_id: string; server_time: string;
          billing?: { charged_yuan: string; balance_yuan: string } };
};

export class LavaClient {
  constructor(private base = process.env.LAVA_BASE!, private key = process.env.LAVA_KEY!) {}

  private async get<T>(path: string, params: Record<string, string | number | undefined> = {}) {
    const qs = new URLSearchParams();
    for (const [k, v] of Object.entries(params)) if (v !== undefined) qs.set(k, String(v));
    const res = await fetch(`${this.base}${path}?${qs}`, {
      headers: { Authorization: `Bearer ${this.key}` },
    });
    const body = (await res.json()) as Envelope<T>;
    if (!body.ok) throw new Error(`${res.status} ${body.code} rid=${body.meta?.request_id}`);
    return body;   // 429/500/503 的重试可照 Python 版加一层
  }

  listHotConcepts(window = "24h", limit = 50) {
    return this.get<{ count: number; list: unknown[] }>("/hot/concepts", { window, limit });
  }
}

TypeScript 版只应在服务端(Node、边缘函数)运行。文档明确提示:别把 Key 写进网页前端代码。

五、上线前自检

代码里搜 lvk_ 确认 Key 没写死;故意传错参数确认失败不扣费;定时调 getAccount 监控余额;概念接口需 scope concepts、评分接口需 agencies,否则返回 403 FORBIDDEN_SCOPE。

FAQ

Q:MCP 和 HTTP 接口的数据一样吗? A:MCP 工具与 HTTP 接口一一对应,都是只读,计费都是每次成功调用 0.005 元。程序化批量任务用 HTTP 更好控制。

Q:为什么骨架不把 data 写成强类型? A:OpenAPI 对多数接口的内部列结构描述有限,强行写死容易与实际返回不一致,建议按需逐个补充。

Q:能在浏览器里直接调吗? A:不建议,那样 Key 会暴露。请放在服务端。

结论

熔岩的 HTTP 接口规模小、结构统一:18 个只读 GET、一套 Envelope 外壳、两种鉴权头。客户端骨架只需要做好四件事:鉴权头、外壳解析、按错误码分类重试、读取计费头;方法名对齐 operationId,再按数据板块分组,就能和 MCP 工具名互相对照。

数据来自熔岩消息面热度监测,仅供研究参考,不构成投资建议。

只读监测与字段说明见:https://lava.ai-dolphin.com/subscribe/docs

No comments yet. Be the first to comment.

More from this blog