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 服务器来暴露额外的功能。真正的力量来自于把它们编排在一起。

相关推荐
Elastic 中国社区官方博客40 分钟前
使用 Lucene 搜索你的 Bean —— Elasticsearch
大数据·开发语言·人工智能·elasticsearch·搜索引擎·全文检索·lucene
闲蛋小超人笑嘻嘻1 小时前
Git Worktree 详解
大数据·elasticsearch·搜索引擎
MayBaymax2 小时前
Elasticsearch 原理与用法
java·elasticsearch
IT大白鼠2 小时前
搜索系列 · 第 01 篇——认知入门:Elasticsearch 是什么
elasticsearch·nosql
Elasticsearch3 小时前
14 个 alerts,1 个 incident:使用 Elasticsearch 中的 ES|QL 衡量 alerting rule 噪声
elasticsearch
Elasticsearch3 小时前
Kubernetes attributes processor v1:它对 EDOT Collector 意味着什么
elasticsearch
Elasticsearch4 小时前
Elasticsearch Serverless 如何通过 hollow shards 将索引节点关闭次数降低 30%
elasticsearch
SelectDB技术团队5 小时前
Agent Trace 数据底座建设:宽表建模、全文检索与成本聚合的配置与验证步骤
大数据·python·clickhouse·elk·elasticsearch·全文检索·复杂查询
Zhu7585 小时前
快速测试-ZLMediaKit
大数据·elasticsearch·搜索引擎
Elastic 中国社区官方博客5 小时前
使用 NVIDIA cuVS 在 Elasticsearch 中实现 GPU 加速的向量索引:在 10 分钟内处理 1.38 亿个向量
大数据·数据库·elasticsearch·搜索引擎·全文检索·安全威胁分析