使用 TypeScript 创建 Elasticsearch MCP 服务器:把 endpoint 改到 TaoToken
1. 为什么要在 TypeScript 里自己写一个 Elasticsearch MCP 服务器如果你手里有一套 Elasticsearch 知识库又想让 Claude、Cline 这类支持 MCP 的客户端直接查它最省事的路径不是等官方插件而是自己用 TypeScript 写一个 MCP 服务器。MCP 全称 Model Context Protocol是一套让大模型应用和外部系统标准化通信的协议服务器把能力注册成「工具」客户端发现并调用这些工具模型负责决定什么时候调、传什么参数。你写一次服务器所有兼容 MCP 的客户端都能复用。这件事适合三类人一是做企业内部知识库检索的 Node/前端工程师TypeScript 类型安全比 Python 方案更贴合现有技术栈二是想让 LLM 检索结果可溯源、可后处理的团队官方方案往往只返回原始命中没法加摘要和引用三是想练手 MCP 协议、理解工具注册与 Stdio 传输机制的开发者。我试过把检索、摘要、引用拆成两个工具串起来跑端到端链路是通的下面把可复制的配置和代码都摊开讲。需要先明确一点MCP 服务器本身只负责「检索 组织上下文」真正生成答案的是客户端里的大模型。所以本文的架构是——TypeScript 服务器连 Elasticsearch 做检索再把命中的文档交给模型做摘要最后把引用元数据一并返回。模型调用这一层我们统一走 TaoToken 的 Key/API 通道这样换模型、换客户端都不用改业务代码。2. 前置准备TaoToken 通道与项目依赖怎么配在写代码之前先把两件事定下来模型调用的 endpoint 指向哪里以及项目依赖装哪些。模型调用我们统一走 TaoToken它的价值在于把多家模型的调用收敛到一个 Key、一个 Base URL 上MCP 服务器里只维护一份配置后面换模型只改 Model ID 就行。第一步去 TaoToken 控制台创建一个 API Key。打开 https://taotoken.net/api-keys 登录后新建一个 Key复制出来先存到本地环境变量里别硬编码进代码。这个 Key 后面会同时用于 MCP 服务器里的模型调用。第二步确认你要用的模型 ID。在模型对话页 https://taotoken.net/model-chat 里可以试跑一下确认目标模型能正常返回再把它写进配置。Base URL 固定用 https://taotoken.net/api 注意这个地址不带任何查询参数直接作为 OpenAI 兼容的 baseURL 使用。第三步初始化项目并装依赖。Node.js 需要 20 及以上保证 MCP SDK 和 Elasticsearch 客户端兼容mkdir es-mcp-server cd es-mcp-server npm init -y npm install elastic/elasticsearch modelcontextprotocol/sdk openai zod npm install --save-dev typescript ts-node types/node依赖分工要说清楚elastic/elasticsearch封装 ES 的 REST 接口负责 DSL 查询modelcontextprotocol/sdk提供服务器骨架、工具注册和 Stdio 传输openai这个 SDK 我们用来调 TaoToken 的兼容接口因为 TaoToken 的 API 是 OpenAI 兼容格式直接改 baseURL 即可zod做运行时参数校验弥补 TypeScript 只在编译期检查的短板。第四步准备环境变量。在项目根目录建一个.env记得加进.gitignore把 ES 地址、ES 认证、TaoToken Key 都放进去ELASTICSEARCH_ENDPOINThttp://localhost:9200 ELASTICSEARCH_API_KEY TAOTOKEN_API_KEYsk-你的TaoToken密钥 TAOTOKEN_BASE_URLhttps://taotoken.net/api TAOTOKEN_MODELgpt-4o-mini这里有个容易踩的坑ES 本地开发通常没开认证ELASTICSEARCH_API_KEY留空即可但 Elastic Cloud 强制要求 API Key留空会直接 401。TaoToken 这边TAOTOKEN_BASE_URL一定要写完整到/api少写路径会导致请求打到错误的路由上。3. 可复制配置tsconfig、MCP 服务器与工具注册代码这一节是全文的核心所有片段都可以直接复制。先建tsconfig.json保证编译产物是 Node 20 能跑的 ESM{ compilerOptions: { target: ES2022, module: node16, moduleResolution: node16, outDir: ./dist, rootDir: ./src, strict: true, esModuleInterop: true, skipLibCheck: true }, include: [src/**/*.ts] }然后在package.json里补上type: module和启动脚本否则node16模块解析会报错{ type: module, scripts: { build: tsc, start: node dist/index.js } }接下来是服务器主体src/index.ts。先写依赖导入和客户端初始化注意模型客户端这里指向 TaoTokenimport { z } from zod; import { Client } from elastic/elasticsearch; import { McpServer } from modelcontextprotocol/sdk/server/mcp.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import OpenAI from openai; const ES_ENDPOINT process.env.ELASTICSEARCH_ENDPOINT ?? http://localhost:9200; const ES_API_KEY process.env.ELASTICSEARCH_API_KEY ?? ; const TAOTOKEN_KEY process.env.TAOTOKEN_API_KEY ?? ; const TAOTOKEN_BASE process.env.TAOTOKEN_BASE_URL ?? https://taotoken.net/api; const TAOTOKEN_MODEL process.env.TAOTOKEN_MODEL ?? gpt-4o-mini; const INDEX documents; const es new Client({ node: ES_ENDPOINT, auth: ES_API_KEY ? { apiKey: ES_API_KEY } : undefined, }); const llm new OpenAI({ apiKey: TAOTOKEN_KEY, baseURL: TAOTOKEN_BASE, });这里三件套要记牢Base URL 是https://taotoken.net/apiKey 是控制台创建的TAOTOKEN_API_KEYModel ID 是TAOTOKEN_MODEL。三者缺一模型调用就会失败。然后是 Zod 模式定义和服务器实例。Zod 在这里不只是校验它还能反推 TypeScript 类型省得手写 interfaceconst SearchResultSchema z.object({ id: z.number(), title: z.string(), content: z.string(), tags: z.array(z.string()), score: z.number(), }); type SearchResult z.infertypeof SearchResultSchema; const server new McpServer({ name: Elasticsearch RAG MCP, version: 1.0.0, });注册第一个工具search_docs负责 ES 全文检索。查询 DSL 里title^2给标题加权fuzziness: AUTO做拼写容错server.registerTool( search_docs, { title: Search Documents, description: Full-text search over the Elasticsearch knowledge base., inputSchema: { query: z.string().describe(Search terms), max_results: z.number().optional().default(5), }, outputSchema: { results: z.array(SearchResultSchema), total: z.number(), }, }, async ({ query, max_results }) { const res await es.search({ index: INDEX, size: max_results, query: { bool: { must: [ { multi_match: { query, fields: [title^2, content, tags], fuzziness: AUTO, }, }, ], }, }, }); const results: SearchResult[] res.hits.hits.map((hit: any) ({ id: hit._source.id, title: hit._source.title, content: hit._source.content, tags: hit._source.tags, score: hit._score ?? 0, })); const total typeof res.hits.total number ? res.hits.total : res.hits.total?.value ?? 0; return { content: [ { type: text, text: Found ${results.length} docs:\n results.map((r, i) [${i 1}] ${r.title} (${r.score.toFixed(2)})).join(\n), }, ], structuredContent: { results, total }, }; } );注册第二个工具summarize_and_cite把检索结果交给 TaoToken 上的模型做摘要同时返回引用元数据server.registerTool( summarize_and_cite, { title: Summarize and Cite, description: Summarize search results and return citations., inputSchema: { results: z.array(SearchResultSchema), question: z.string(), max_docs: z.number().optional().default(5), }, outputSchema: { summary: z.string(), citations: z.array( z.object({ id: z.number(), title: z.string(), score: z.number(), }) ), }, }, async ({ results, question, max_docs }) { const used results.slice(0, max_docs); const context used .map((r, i) [Doc ${i 1}: ${r.title}]\n${r.content}) .join(\n\n---\n\n); const completion await llm.chat.completions.create({ model: TAOTOKEN_MODEL, messages: [ { role: system, content: Answer the question strictly based on the provided documents. If not found, say so., }, { role: user, content: Question: ${question}\n\n${context} }, ], temperature: 0.3, max_tokens: 800, }); const summary completion.choices[0]?.message?.content ?? No summary.; const citations used.map((r) ({ id: r.id, title: r.title, score: r.score, })); return { content: [ { type: text, text: ${summary}\n\nSources:\n citations.map((c, i) [${i 1}] ${c.title}).join(\n), }, ], structuredContent: { summary, citations }, }; } );最后启动 Stdio 传输const transport new StdioServerTransport(); await server.connect(transport);编译一下npm run build产物在dist/index.js这就是客户端要启动的入口文件。4. 验证请求从客户端配置到一次端到端检索代码写完不算完得真跑通一次。先在客户端里注册这个 MCP 服务器。以 Claude Desktop 为例配置文件在 macOS 是~/Library/Application Support/Claude/claude_desktop_config.jsonWindows 是%APPDATA%\Claude\claude_desktop_config.json。写入{ mcpServers: { es-rag: { command: node, args: [/绝对路径/es-mcp-server/dist/index.js], env: { ELASTICSEARCH_ENDPOINT: http://localhost:9200, ELASTICSEARCH_API_KEY: , TAOTOKEN_API_KEY: sk-你的TaoToken密钥, TAOTOKEN_BASE_URL: https://taotoken.net/api, TAOTOKEN_MODEL: gpt-4o-mini } } } }args必须是绝对路径相对路径客户端找不到。改完重启客户端在工具列表里应该能看到es-rag下的两个工具。验证分两步。第一步单独测检索直接问「搜索关于认证和访问控制的文档」客户端会调用search_docs返回带相关性得分的命中列表。如果 ES 里没数据先灌几条测试文档curl -X POST http://localhost:9200/documents/_doc/1 \ -H Content-Type: application/json \ -d {id:1,title:OAuth 2.0 Authentication,content:OAuth 2.0 uses access tokens and refresh tokens for secure API access.,tags:[auth,oauth]}第二步测工具串联问「改进认证和访问控制的主要建议是什么附引用」。客户端会先调search_docs再把结果传给summarize_and_cite最终返回一段摘要加引用列表。这一步能跑通说明 ES 检索、TaoToken 模型调用、MCP 工具编排三条链路都正常。如果你想在命令行里单独验证模型通道是否通可以写个最小脚本import OpenAI from openai; const llm new OpenAI({ apiKey: process.env.TAOTOKEN_API_KEY, baseURL: https://taotoken.net/api, }); const r await llm.chat.completions.create({ model: gpt-4o-mini, messages: [{ role: user, content: ping }], }); console.log(r.choices[0].message.content);返回正常内容就说明 Key、Base URL、Model ID 三件套没问题问题只可能出在 MCP 层。5. 常见报错排查401、local proxy failed 与 reading choices跑不通的时候报错信息基本能定位到具体环节。下面几个是我实际遇到过的。401 Unauthorized模型侧多半是 TaoToken Key 没读到或写错。检查env里的TAOTOKEN_API_KEY是否和代码里的变量名完全一致注意大小写。还有一种情况是 Key 复制时带了空格肉眼看不出来重新复制一次。如果报错信息里出现invalid api key基本就是这个原因。401 UnauthorizedES 侧Elastic Cloud 必须带 API Key本地 ES 如果开了安全认证也会要求。确认ELASTICSEARCH_API_KEY是否为空、是否过期。本地开发建议先关掉 ES 安全认证减少变量。local proxy failed / connection refused这是客户端启动 MCP 服务器时连不上。先确认args里的路径是绝对路径且文件存在再确认node命令在系统 PATH 里。如果客户端日志里出现spawn node ENOENT说明它找不到 node把command改成 node 的绝对路径比如/usr/local/bin/node。reading choices of undefined这个报错出现在completion.choices[0]那一行说明模型返回体里没有choices字段。常见原因是baseURL写错比如漏了/api或写成了别的路径请求打到了非兼容接口上。确认TAOTOKEN_BASE_URL是https://taotoken.net/api不要带尾斜杠。另一个原因是 Model ID 写错模型不存在时接口可能返回错误结构同样导致choices缺失。OAuth / 授权弹窗反复出现客户端首次调用工具会弹授权选「始终允许」即可。如果每次都弹检查配置文件是否被其他进程覆盖或者客户端版本过旧。ES 返回 0 命中但没报错检查索引名是否写对INDEX常量要和实际索引一致。另外multi_match的字段名必须存在于映射里字段名拼错不会报错只会静默返回空。排查顺序建议从外到内先单独验证 TaoToken 通道第 4 节的最小脚本再验证 ES 直连curl 一条查询最后才怀疑 MCP 层。这样能快速缩小范围不用在三个环节之间反复猜。6. 把通道固定下来后续扩展就轻松了整套跑通之后你会发现最值得固定下来的其实是模型调用这一层。把 Base URL、Key、Model ID 收敛到环境变量里MCP 服务器代码里不出现任何硬编码的模型信息后面想换模型只改一个变量。TaoToken 的接入文档在 https://taotoken.net/doc 里面有各语言 SDK 的对接示例遇到兼容性问题可以对照查。如果你打算长期跑编码类或 Agent 类任务可以看看 Coding Plan https://taotoken.net/coding-plan 它更适合高频调用的场景。日常调试模型效果用模型对话页 https://taotoken.net/model-chat 快速试跑就行。Key 管理统一在控制台 https://taotoken.net/api-keys 建议按项目分 Key方便排查和轮换。回到 MCP 服务器本身下一步可以做的扩展不少给search_docs加过滤参数支持按 tags 或时间范围筛选把摘要工具改成流式返回长文档体验更好再注册一个index_document工具让模型能往知识库里写数据。这些都不需要动传输层MCP 的工具注册机制天然支持增量扩展。真正要守住的是那条链路——ES 负责检索TaoToken 负责模型调用MCP 负责把两者串给客户端各司其职换任何一环都不影响其他部分。

相关新闻

C++鼠标乱飞别慌:把输入设备配置改到 TaoToken 的排查清单

C++鼠标乱飞别慌:把输入设备配置改到 TaoToken 的排查清单

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 15:48:37 阅读更多 →
Codex 指南:从能做什么去学怎么用,把 auth.json 改到 TaoToken

Codex 指南:从能做什么去学怎么用,把 auth.json 改到 TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 15:48:37 阅读更多 →
交互对话AI深度评测:DeepSeek、Kimi、腾讯元宝、豆包,谁才是你的最佳交互伙伴?TaoToken统一Key实测

交互对话AI深度评测:DeepSeek、Kimi、腾讯元宝、豆包,谁才是你的最佳交互伙伴?TaoToken统一Key实测

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 15:48:37 阅读更多 →

最新新闻

UE架构实战:模块化设计、GC与网络同步深度解析

UE架构实战:模块化设计、GC与网络同步深度解析

这个系列写到第五篇,终于到了我最想聊的一块:UE实战里的架构问题。前面几篇我把坐标系、渲染管线、资源打包这些地基过了一遍,如果你是从头读下来的,这会儿应该有了“引擎由哪些部件拼起来”的宏观印象。今天不一样,今…

2026/10/9 16:22:32 阅读更多 →
全国省市区经纬度MySQL数据导入与LBS查询实战

全国省市区经纬度MySQL数据导入与LBS查询实战

简介:这份 MySQL 数据资源面向需要省、市、区三级行政区划经纬度坐标的开发者与数据分析人员,可用于地图打点、区域聚合统计、地址解析、物流配送范围计算等场景,帮助省去逐条采集与清洗地理坐标的繁琐工作。压缩包内共 1 个文件,…

2026/10/9 16:22:32 阅读更多 →
Spring Boot秒杀系统实战:库存扣减、限流与异步下单防超卖

Spring Boot秒杀系统实战:库存扣减、限流与异步下单防超卖

简介:这是一套基于SpringBoot的电商秒杀系统完整项目源码,面向计算机相关专业的在校学生、教师及企业开发者,尤其适合作为毕业设计、课程设计或项目立项演示的参考方案。项目采用MySQL、SpringBoot、Redis与RabbitMQ技术栈,重点解…

2026/10/9 16:22:32 阅读更多 →
无人机视角多类别目标检测数据集:从标注格式到训练落地的完整链路

无人机视角多类别目标检测数据集:从标注格式到训练落地的完整链路

简介:这份无人机视角多类别目标检测数据集面向从事航拍视觉算法、无人机自动巡检与智慧城市研究的开发者,提供可直接用于YOLO系列模型训练与验证的标注样本。数据按训练720张、验证192张、测试103张划分,覆盖Bridge、Airplane、Bicycle、Boat…

2026/10/9 16:22:32 阅读更多 →
Oracle补丁包应用实战:从命名解析到验证回滚

Oracle补丁包应用实战:从命名解析到验证回滚

简介:面向 Oracle Database 11.2.0.3 的补丁集更新包,编号 20760997,集成 2015 年 7 月关键补丁更新(CPU),适用于 Linux x86-64 平台。补丁集更新涵盖安全修复、稳定性改进与性能优化,可降低已知…

2026/10/9 16:22:32 阅读更多 →
LSTM+RBF-BP混合模型火灾预测实战:时序信号处理与训练避坑指南

LSTM+RBF-BP混合模型火灾预测实战:时序信号处理与训练避坑指南

简介:PDF文档《基于LSTM和RBF-BP深度学习模型的火灾预测方法》收录于《齐鲁工业大学学报》2020年第3期,面向火灾预测、深度学习与多源信息融合方向的研究者及数据分析学习者。方法针对火灾信号时变非线性、单特征预测误报漏报率高等问题,利用…

2026/10/9 16:21:32 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 6:17:20 阅读更多 →