Elasticsearch MCP 服务器:与你的 Index 聊天

访问外部知识在提升 LLM 响应能力的现代 AI 工作流中起着关键作用。但高效管理 context、确保 AI agents 之间的通信,以及扩展工具以协同工作并非易事。这就是 Model Context Protocol (MCP) 的作用所在。

Model Context Protocol 是一个开放标准,使开发者能够在他们的数据源和 AI 驱动的工具之间建立安全的双向连接。其架构非常直接:开发者可以通过 MCP servers 暴露他们的数据,或者构建连接这些服务器的 AI 应用(MCP clients)。MCP server 充当 AI 模型与外部存储的数据之间的桥梁,例如存储在 Elasticsearch vector store 中的数据。

在这篇文章中,我们将探讨:

  1. MCP server 是什么以及它如何工作。
  2. MCP 如何简化 RAG 并支持 agentic workflows。
  3. 构建 MCP server 以从 Elasticsearch 语义搜索数据的指南。
  4. 使用 MCP Inspector 和 Claude Desktop App(实现 MCP client 协议)的动手演示。

安装

Elasticsearch 及 Kibana

如果你还没有安装好自己的 Elasticsearch 及 Kibana,那么请参考一下的文章来进行安装:

在安装的时候,请选择 Elastic Stack 8.x/9.x 进行安装。在安装的时候,我们可以看到如下的安装信息:

在本文中,我们使用 Elastic Stack 9.1.2 来进行展示。

启动白金试用

我们需要使用到内置的 ELSER 模型来向量化我们的数据。我们需要启动白金试用:

这样就启动了白金试用。

下载 ELSER 模型

如果我们看到 Deployed,则表面我们已经成功地部署了 ELSER 模型。我们可以看到如下的 inference id 已经被生成:

bash 复制代码
`GET _inference/_all`AI写代码

在下面,我们将使用如上所示的 .elser-2-elasticsearch inference id。

Elasticsearch MCP 服务器更新

自从发布这篇博客以来,我们推出了 elastic/mcp-server-elasticsearch。你可以在博客文章中了解更多信息:Connect Agents to Elasticsearch with Model Context Protocol。对于最新的 mcp-server-elasticsearch,你需要参考文章 "将 agents 连接到 Elasticsearch 使用模型上下文协议 - docker" 来拿进行连接。

什么是 Model Context Protocol (MCP)?

Model Context Protocol 定义了一种结构化方式,让 AI models 可以请求信息、执行工具,并在多次交互中保持 context(详细内容请参阅官方文档)。它规范了以下之间的通信:

  • MCP Clients (聊天应用、AI assistants),代表用户请求 信息或调用工具
  • MCP Servers (搜索工具、数据库、外部 APIs),响应相关数据或执行操作。

与传统的基于 RAG 的设置仅检索文档不同,MCP 使 AI 能够通过调用一个或多个 MCP servers 暴露的多个工具来编排和执行 workflows。

MCP 架构

为什么在 RAG 中使用 MCP Server 与 Elasticsearch?

语义搜索通过理解 context 和 intent 提升了传统基于关键词的搜索。将 MCP server 与 Elasticsearch 集成,我们可以实现:

  • 更准确的搜索结果 -- MCP 允许 AI models 选择专用的搜索工具,甚至动态创建超越简单关键词匹配的结构化查询。
  • 动态 agent workflows -- AI 可以根据实时数据调用 APIs、过滤响应并触发操作。
  • 多工具集成 -- MCP 让 AI 与多个数据源交互,将搜索与实时洞察结合。

构建 MCP Server

让我们构建一个 MCP Tool,使你能够在 Elastic Search Labs 的博客中进行语义搜索。助手将根据用户的意图使用此工具。

我准备了一个 demo repo,这是抓取示例数据集、使用 semantic_text 映射嵌入抓取内容,并通过 MCP server 暴露工具进行语义搜索的良好起点。

你可以通过以下步骤轻松将其适配到你自己的用例,或查看 repo 的 README 获取更多细节。

抓取博客数据并在 Elasticsearch 中生成 Embeddings

为了支持语义查询,我们将使用 semantic_text,这是一种专门的 mapping,在数据被写入索引后会自动执行分块和 embedding。

创建一个用于存储数据的索引:

c 复制代码
`PUT search-labs-posts`AI写代码

更新 mappings。Elastic Open Crawler 默认会将网站内容填充到 body 字段中:

bash 复制代码
`

1.  PUT search-labs-posts/_mappings
2.  {
3.    "properties": {
4.      "body": {
5.        "type": "text",
6.        "copy_to": "semantic_body"
7.      },
8.      "semantic_body": {
9.        "type": "semantic_text",
10.        "inference_id": ".elser-2-elasticsearch"
11.      }
12.    }
13.  }

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

注意.elser-2-elasticsearch 是 ES 中预配置的推理端点。它将被 semantic_text mapping 用来为自动分块的内容生成稀疏 embeddings。

我正在使用 Elastic Open Crawler 快速将数据填充到 Elasticsearch 中。

你应该按照 demo repo 中列出的步骤操作。它会指导你把正确的 ES 凭证放入 crawler-config/elastic-search-labs-crawler.yml 中的爬虫配置里。

bash 复制代码
`

1.  $ pwd
2.  /Users/liuxg/python
3.  $ git clone https://github.com/liu-xiao-guo/elastic-semantic-search-mcp-server
4.  Cloning into 'elastic-semantic-search-mcp-server'...
5.  remote: Enumerating objects: 28, done.
6.  remote: Counting objects: 100% (28/28), done.
7.  remote: Compressing objects: 100% (19/19), done.
8.  remote: Total 28 (delta 8), reused 20 (delta 4), pack-reused 0 (from 0)
9.  Receiving objects: 100% (28/28), 15.62 KiB | 3.90 MiB/s, done.
10.  Resolving deltas: 100% (8/8), done.
11.  $ cd elastic-semantic-search-mcp-server/
12.  $ ls
13.  Makefile       README.md      crawler-config pyproject.toml server.py      uv.lock

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

我们首先把 env.example 拷贝到 .env 文件中,并做相应的配置:

.env

ini 复制代码
`

1.  ES_URL="https://192.168.101.192:9200"
2.  API_KEY="b2Nac21aa0I5NnpZa1ZqREJRWU46Q2lBZmU3aFNwMWVqZmtLMENLSE13QQ=="

`AI写代码

注意:请在上面根据自己的 ES 配置做相应的修改。为了能够让 docker 能正确访问我们的 ES,我们的 ES 不能使用 localhost。

对于我们的 ES 安装,我们使用的是自签名证书。我们使用如下的命令把 ES 的证书拷贝到当前的目录下:

bash 复制代码
`

1.  $ pwd
2.  /Users/liuxg/python/elastic-semantic-search-mcp-server
3.  $ cp ~/elastic/elasticsearch-9.1.4/config/certs/http_ca.crt ./crawler-config/
4.  $ ls ./crawler-config/
5.  elastic-search-labs-crawler.yml http_ca.crt                     test-crawler.yml

`AI写代码

由于我们是自签名的证书,我们需要重新修改一下 elastic-search-labs-crawler.yml 文件:

elastic-search-labs-crawler.yml

yaml 复制代码
`

1.  domains:
2.    - url: https://www.elastic.co
3.      seed_urls:
4.        - https://www.elastic.co/search-labs
5.      crawl_rules:
6.        - policy: allow
7.          type: begins
8.          pattern: /search-labs
9.        - policy: deny
10.          type: regex
11.          pattern: .*

13.  output_sink: elasticsearch
14.  output_index: search-labs-posts

16.  max_crawl_depth: 10
17.  max_title_size: 500
18.  max_body_size: 5_242_880
19.  max_keywords_size: 512
20.  max_description_size: 512
21.  max_indexed_links_count: 10
22.  max_headings_count: 10

24.  elasticsearch:
25.    host:  https://192.168.101.192
26.    port: 9200
27.    # username: elastic
28.    # password: GK1JT0+tbYQV02R9jVP*
29.    api_key: bzhZRW1wa0I5NnpZa1ZqRGhRYl86akdfMExCSkZzUUh2QVRsd2UtQ2tHUQ==
30.    bulk_api:
31.      max_items: 10
32.      max_size_bytes: 1_048_576
33.    ca_file: /app/config/http_ca.crt

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

请注意上面的 ca_file 配置。我们把证书配置进去。更多配置,请参阅文档

一旦爬虫配置完成,运行单次爬取只需:

我们接下来启动 docker,并运行如下的命令:

bash 复制代码
`

1.  docker run --rm \
2.    --entrypoint /bin/bash \
3.    -v "$(pwd)/crawler-config:/app/config" \
4.    --network host \
5.    docker.elastic.co/integrations/crawler:latest \
6.    -c "bin/crawler crawl /app/config/elastic-search-labs-crawler.yml"

`AI写代码
ini 复制代码
`

1.  $ docker run --rm \
2.  >   --entrypoint /bin/bash \
3.  >   -v "$(pwd)/crawler-config:/app/config" \
4.  >   --network host \
5.  >   docker.elastic.co/integrations/crawler:latest \
6.  >   -c "bin/crawler crawl /app/config/elastic-search-labs-crawler.yml"
7.  [2025-09-30T10:22:25.034Z] [crawl:68dbaf6069537f8689a2bd20] [primary] Initialized an in-memory URL queue for up to 10000 URLs
8.  [2025-09-30T10:22:25.038Z] [crawl:68dbaf6069537f8689a2bd20] [primary] ES connections will be authorized with configured API key
9.  [2025-09-30T10:22:25.544Z] [crawl:68dbaf6069537f8689a2bd20] [primary] Connected to ES at https://192.168.101.192:9200 - version: 9.1.4; build flavor: default
10.  [2025-09-30T10:22:25.583Z] [crawl:68dbaf6069537f8689a2bd20] [primary] Index [search-labs-posts] was found!
11.  [2025-09-30T10:22:25.583Z] [crawl:68dbaf6069537f8689a2bd20] [primary] Elasticsearch sink initialized for index [search-labs-posts] with pipeline [search-default-ingestion]
12.  [2025-09-30T10:22:25.590Z] [crawl:68dbaf6069537f8689a2bd20] [primary] Starting the primary crawl with up to 10 parallel thread(s)...
13.  [2025-09-30T10:22:26.903Z] [crawl:68dbaf6069537f8689a2bd20] [primary] Crawl status: queue_size=14, pages_visited=1, urls_allowed=15, urls_denied={}, crawl_duration_msec=1314, crawling_time_msec=1194.0, avg_response_time_msec=1194.0, active_threads=1, http_client={:max_connections=>100, :used_connections=>1}, status_codes={"200"=>1}
14.  [2025-09-30T10:22:34.467Z] [crawl:68dbaf6069537f8689a2bd20] [primary] Sending bulk request with 10 items and resetting queue...
15.  [2025-09-30T10:22:36.935Z] [crawl:68dbaf6069537f8689a2bd20] [primary] Crawl status: queue_size=706, pages_

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

我们可以在 Kibana 中查看被爬进的文档:

sql 复制代码
`GET search-labs-posts/_search`AI写代码

我们需要等待一定的时间来完成所有的文档写入。

我们可以看到有 768 篇文章被写入。

MCP Server 工具用于搜索相关博客文章

一旦你在 .env 文件中填入 ES_URL 和 API_KEY,就可以通过 MCP Inspector 工具开始与我们的 MCP server 交互。

开发非常直接,多亏了 MCP Python SDK。这里是我们所需的全部内容,不到 50 行代码!

python 复制代码
`

1.  import os
2.  import sys
3.  from dotenv import load_dotenv
4.  from elasticsearch import Elasticsearch
5.  from mcp.server.fastmcp import FastMCP

7.  # Load environment variables
8.  load_dotenv()
9.  print("Beginning", file=sys.stderr)

11.  ELASTCSEARCH_CERT_PATH = "/Users/liuxg/python/elastic-semantic-search-mcp-server/crawler-config/http_ca.crt"
12.  ES_URL = os.getenv("ES_URL")
13.  API_KEY = os.getenv("API_KEY")

15.  # We can use the following method to output some logs to help debug
16.  print(ES_URL, file=sys.stderr)
17.  print(API_KEY, file=sys.stderr)
18.  # ES_URL = "https://192.168.101.192:9200"
19.  # API_KEY = "bzhZRW1wa0I5NnpZa1ZqRGhRYl86akdfMExCSkZzUUh2QVRsd2UtQ2tHUQ=="

21.  # Setup Elasticsearch client
22.  es_client = Elasticsearch(ES_URL, api_key= API_KEY, 
23.                            ca_certs = ELASTCSEARCH_CERT_PATH, verify_certs = True)
24.  print(es_client.info())

26.  mcp = FastMCP("Search Labs Blog Search MCP", dependencies=["elasticsearch"])

29.  # Elasticsearch search function
30.  def search_search_labs(query: str) -> list[dict]:
31.      """Perform semantic search on Search Labs blog posts."""
32.      try:
33.          results = es_client.search(
34.              index="search-labs-posts",
35.              body={
36.                  "query": {
37.                      "semantic": {"query": query, "field": "semantic_body"},
38.                  },
39.                  "_source": [
40.                      "title",
41.                      "url",
42.                      "semantic_body.inference.chunks.text",
43.                  ],
44.                  "size": 5,
45.              },
46.          )
47.          return [
48.              {
49.                  "title": hit["_source"].get("title", ""),
50.                  "url": hit["_source"].get("url", ""),
51.                  "content": [
52.                      chunk.get("text", "")
53.                      for chunk in hit["_source"]
54.                      .get("semantic_body", {})
55.                      .get("inference", {})
56.                      .get("chunks", [])[:3]
57.                  ],
58.              }
59.              for hit in results.get("hits", {}).get("hits", [])
60.          ]
61.      except Exception as e:
62.          return [{"error": f"Search failed: {str(e)}"}]

65.  # MCP tool for documentation search
66.  @mcp.tool(
67.      ,
68.      description="Perform a semantic search across Search Labs blog posts for a given query.",
69.  )
70.  def search_search_labs_blog(query: str) -> str:
71.      """Returns formatted search results from Search Labs blog posts."""
72.      results = search_search_labs(query)
73.      return (
74.          "\n\n".join(
75.              [
76.                  f"### {hit['title']}\n[Read More]({hit['url']})\n- {hit['content']}"
77.                  for hit in results
78.              ]
79.          )
80.          if results
81.          else "No results found."
82.      )

85.  # Start MCP server
86.  if __name__ == "__main__":
87.      print(f"MCP server '{mcp.name}' is running...")
88.      mcp.run()

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

要运行开发版 inspector,只需调用:

go 复制代码
`make dev`AI写代码
bash 复制代码
``

1.  $ pwd
2.  /Users/liuxg/python/elastic-semantic-search-mcp-server
3.  $ ls
4.  Makefile          __pycache__       node_modules      package.json      server.py
5.  README.md         crawler-config    package-lock.json pyproject.toml    uv.lock
6.  $ sudo npm install @modelcontextprotocol/inspector@0.16.8

8.  up to date, audited 232 packages in 2s

10.  40 packages are looking for funding
11.    run `npm fund` for details

13.  found 0 vulnerabilities
14.  $ make dev
15.  uv run mcp dev server.py
16.  https://192.168.101.192:9200 bzhZRW1wa0I5NnpZa1ZqRGhRYl86akdfMExCSkZzUUh2QVRsd2UtQ2tHUQ==
17.  {'name': 'liuxgn.local', 'cluster_name': 'elasticsearch', 'cluster_uuid': 'i0uJkyoLRT-M01FTEeGwMg', 'version': {'number': '9.1.4', 'build_flavor': 'default', 'build_type': 'tar', 'build_hash': '0b7fe68d2e369469ff9e9f344ab6df64ab9c5293', 'build_date': '2025-09-16T22:05:19.073893347Z', 'build_snapshot': False, 'lucene_version': '10.2.2', 'minimum_wire_compatibility_version': '8.19.0', 'minimum_index_compatibility_version': '8.0.0'}, 'tagline': 'You Know, for Search'}
18.  Starting MCP inspector...
19.  ⚙️ Proxy server listening on localhost:6277
20.  🔑 Session token: 8cd21d58177e0c446bf3d8938e65e2f1e717e047171c89c63fb155dae7c1011e
21.     Use this token to authenticate requests or set DANGEROUSLY_OMIT_AUTH=true to disable auth

23.  🚀 MCP Inspector is up and running at:
24.     http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=8cd21d58177e0c446bf3d8938e65e2f1e717e047171c89c63fb155dae7c1011e

26.  🌐 Opening browser...

``AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

它会在 http://localhost:5173 打开 inspector,你可以通过 UI 中的协议与 MCP server 交互。

Claude Desktop 应用端到端演示

Claude Desktop App 实现了 MCP Client 接口。你可以通过运行以下命令将我们的 MCP server 添加到本地 Claude Desktop:

arduino 复制代码
`

1.  $ pwd
2.  /Users/liuxg/python/elastic-semantic-search-mcp-server
3.  $ ls
4.  Makefile          __pycache__       node_modules      package.json      server.py
5.  README.md         crawler-config    package-lock.json pyproject.toml    uv.lock
6.  $ make install-claude-config
7.  uv run mcp install server.py --with elasticsearch
8.  https://192.168.101.192:9200 bzhZRW1wa0I5NnpZa1ZqRGhRYl86akdfMExCSkZzUUh2QVRsd2UtQ2tHUQ==
9.  {'name': 'liuxgn.local', 'cluster_name': 'elasticsearch', 'cluster_uuid': 'i0uJkyoLRT-M01FTEeGwMg', 'version': {'number': '9.1.4', 'build_flavor': 'default', 'build_type': 'tar', 'build_hash': '0b7fe68d2e369469ff9e9f344ab6df64ab9c5293', 'build_date': '2025-09-16T22:05:19.073893347Z', 'build_snapshot': False, 'lucene_version': '10.2.2', 'minimum_wire_compatibility_version': '8.19.0', 'minimum_index_compatibility_version': '8.0.0'}, 'tagline': 'You Know, for Search'}
10.  [10/01/25 12:22:48] INFO     Added server 'Search Labs Blog Search MCP' to Claude config     claude.py:126
11.                      INFO     Successfully installed Search Labs Blog Search MCP in Claude app   cli.py:468

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

这会更新你主目录下的 claude_desktop_config.json。在下一次重启时,Claude 应用会检测到该 server 并加载声明的工具。我们可以在 Claude Desktop 的设置中进行查看:

在配置文件里,我们可以看到 Search Labs Blog Search MCP 的配置。由于一些原因,我们需要修改上面的 command 字段为:

bash 复制代码
`

1.  $ which uv
2.  /Users/liuxg/.local/bin/uv

`AI写代码
markdown 复制代码
`

1.  {
2.    "mcpServers": {
3.      "Search Labs Blog Search MCP": {
4.        "command": "/Users/liuxg/.local/bin/uv",
5.        "args": [
6.          "run",
7.          "--with",
8.          "elasticsearch",
9.          "--with",
10.          "mcp[cli]",
11.          "mcp",
12.          "run",
13.          "/Users/liuxg/python/elastic-semantic-search-mcp-server/server.py"
14.        ]
15.      }
16.    }
17.  }

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

否则,我们会出现如下的错误信息:

markdown 复制代码
``

1.  2025-10-02T00:50:22.532Z [Search Labs Blog Search MCP] [error] spawn uv ENOENT {
2.    metadata: {
3.      stack: 'Error: spawn uv ENOENT\n' +
4.        '    at ChildProcess._handle.onexit (node:internal/child_process:285:19)\n' +
5.        '    at onErrorNT (node:internal/child_process:483:16)\n' +
6.        '    at process.processTicksAndRejections (node:internal/process/task_queues:90:21)'
7.    }
8.  }
9.  2025-10-02T00:50:22.534Z [Search Labs Blog Search MCP] [info] Server transport closed { metadata: undefined }
10.  2025-10-02T00:50:22.534Z [Search Labs Blog Search MCP] [info] Client transport closed { metadata: undefined }
11.  2025-10-02T00:50:22.534Z [Search Labs Blog Search MCP] [info] Server transport closed unexpectedly, this is likely due to the process exiting early. If you are developing this MCP server you can add output to stderr (i.e. `console.error('...')` in JavaScript, `print('...', file=sys.stderr)` in python) and it will appear in this log. { metadata: undefined }
12.  2025-10-02T00:50:22.534Z [Search Labs Blog Search MCP] [error] Server disconnected. For troubleshooting guidance, please visit our [debugging documentation](https://modelcontextprotocol.io/docs/tools/debugging) { metadata: { context: 'connection', stack: undefined } }
13.  2025-10-02T00:55:18.336Z [Search Labs Blog Search MCP] [info] Initializing server... { metadata: undefined }
14.  2025-10-02T00:55:18.343Z [Search Labs Blog Search MCP] [info] Using MCP server command: /Users/liuxg/.local/bin/uv with args and path: {
15.    metadata: {
16.      args: [
17.        'run',
18.        '--with',
19.        'elasticsearch',
20.        '--with',
21.        'mcp[cli]',
22.        'mcp',
23.        'run',
24.        '/Users/liuxg/python/elastic-semantic-search-mcp-server/server.py',
25.        [length]: 8
26.      ],
27.      paths: [
28.        '/Users/liuxg/.nvm/versions/node/v22.14.0/bin',
29.        '/usr/local/bin',
30.        '/opt/homebrew/bin',
31.        '/opt/local/bin',
32.        '/usr/bin',
33.        '/usr/bin',
34.        '/bin',
35.        '/usr/sbin',
36.        '/sbin',
37.        [length]: 9
38.      ]
39.    }
40.  } %o

``AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

我们可以在如下的位置找到 Claude Desktop 的运行信息:

下面是 Claude 动态查找关于 semantic_text 博客文章的示例:

c 复制代码
`How semantic_text field mapping in Elasticsearch simplifies data ingestion and semantic search? List relevant labs posts`AI写代码

这只是一个起点。我们可以添加更多工具,比如一个通用的 Elasticsearch 查询工具,甚至启动更多 MCP 服务器来暴露额外的功能。真正的力量来自于把它们编排在一起。

相关推荐
tpoog8 小时前
[C++项目组件]Elasticsearch简单介绍
开发语言·c++·elasticsearch
MinggeQingchun19 小时前
Elasticsearch - Linux下使用Docker对Elasticsearch容器设置账号密码
elasticsearch·docker
Terio_my21 小时前
Spring Boot 整合 Elasticsearch
spring boot·后端·elasticsearch
经典19921 天前
Elasticsearch 讲解及 Java 应用实战:从入门到落地
java·大数据·elasticsearch
wdfk_prog1 天前
`git rm --cached`:如何让文件“脱离”版本控制
大数据·linux·c语言·笔记·git·学习·elasticsearch
2501_929382651 天前
ES-DE 前端模拟器最新版 多模拟器游戏启动器 含游戏ROM整合包 最新版
大数据·elasticsearch·游戏
Elasticsearch1 天前
CI/CD 流水线与 agentic AI:如何创建自我纠正的 monorepos
elasticsearch
gb42152872 天前
Elasticsearch 搭建(亲测)
大数据·elasticsearch·jenkins
根哥的博客2 天前
ElasticSearch启用Xpack,配置ssl证书
elasticsearch·ssl