mcpField
S
Staan

Staan

本地服务
github.com搜索与检索01 天前更新

关于 Staan

Staan-MCP 将 AI 客户端连接到 STAAN 搜索 API;STAAN 是拥有自有索引、侧重欧洲市场的搜索服务。它提供可选网页全文的搜索结果,以及带引用的依据型回答工具。

主要功能

  1. 01搜索多个 Staan 市场
  2. 02以 Markdown 或 HTML 返回网页全文
  3. 03限制或排除指定域名
  4. 04生成带引用的依据型回答
  5. 05支持 stdio 和 streamable HTTP

适用场景

  1. 01搜索多个欧洲市场
  2. 02在搜索结果中获取网页文本
  3. 03生成附带来源引用的回答

MCP 相关

该 MCP 服务提供 `web_search` 和 `answer` 工具,支持 stdio 与 streamable HTTP 传输。安装版可为 HTTP 添加 OAuth 2.1 访问控制。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"marcinwolnyeu-staan": {
"command": "uvx",
"args": [
"staan-mcp"
],
"env": {
"STAAN_API_KEY": "<STAAN_API_KEY>"
}
}
}
}
启动命令uvx staan-mcp
需要配置的环境变量
STAAN_API_KEY密钥Staan API key (create one in the Staan console). Sent upstream as a Bearer token; never logged.

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypistaan-mcp1.0.1

工具

2

收录的工具列表 · 更新于 2 周前

参数类型必填说明
querystring是自然语言搜索查询。
marketany否用于搜索的语言-国家/地区市场:fr-fr、de-de、en-us、en-gb、en-fr、en-ca、en-au、en-in、en-ie、en-nz、en-za、en-sg。可传入一个值、列表或通配符('en-*' 表示所有英语市场,'*' 表示全部)。多个市场的结果会按排名合并并按 URL 去重;每个市场都会单独计费,因此除非需要更广泛的覆盖范围,否则建议只传入一个市场。
offsetenum否首个结果的偏移量,用于分页:每页最多 10 条结果,每个查询最多 40 条。0 | 10 | 20 | 30
full_contentenum否随每条结果返回的页面文本。默认值 'none' 会省略文本;'markdown' 会添加文本并使响应变得非常大;'html' 会更大。none | markdown | html
exclude_domainsany否排除这些网站的结果,例如 ['pinterest.com'];也排除其子域名。最多 10 个。不能与 include_domains 同时使用。
include_domainsany否仅返回这些网站的结果,例如 ['wikipedia.org'];匹配其子域名。最多 10 个。不能与 exclude_domains 同时使用。
simple_responseboolean否false 会返回原始 Staan 响应,按市场各返回一条,而不是扁平列表。

详细介绍

Staan-MCP

Python versions PyPI version CI Codecov OpenSSF Scorecard License: MIT Ruff uv mypy: strict MCP Registry

这是一个面向欧洲搜索引擎 STAAN(Search Trusted API Access Network)的 MCP 服务,由 European Perspective SAS 构建,该公司由 Qwant 和 Ecosia 搜索引擎共同持有。它是 Exa Search 和其他 SERP 服务的开放欧洲替代方案,拥有自己的索引,主要面向欧洲市场(以及部分英联邦地区),提供其他搜索服务商可能缺少的独特视角。免费订阅每月可发起 1000 次请求。

MCP 提供两个工具:

  • web_search:网页搜索,可选获取完整页面内容(Markdown 或 HTML),并可同时搜索一个或多个 Staan 市场。
  • answer:基于资料并带有引文的回答,以带有 GitHub 风格脚注的 Markdown 格式呈现。

它通过标准输入/输出(默认方式)、可流式传输的 HTTP 或无状态可流式传输的 HTTP 运行,并可选择使用 OAuth 2.1 资源服务端保护 HTTP 传输。如果只需要标准输入/输出,也可以无需安装任何内容即可运行:只需下载仓库 ZIP 文件,并让你的 AI Agent 指向此文件(README.md)。

注意:staan-mcp 与 Staan、European Perspective、Qwant 或 Ecosia 均无关联。这只是一个个人项目,旨在提供高质量的 Python MCP;在发布时,此类项目尚不存在。

重要:answer API 端点仅限受邀用户使用,即使你已获邀,该端点也可能随时临时停用,导致返回 {"message":"Forbidden","statusCode":403} 响应。

staan-mcp 标志

快速开始

你需要一个 Staan API 密钥(在 Staan 控制台中创建,免费套餐每月包含 1,000 次请求)。如果已安装 uv,只需配置你的客户端即可——无需从此仓库下载任何内容,uv 会通过 uvx 命令自动为你下载最新版 MCP。

{
  "mcpServers": {
    "staan": {
      "command": "uvx",
      "args": ["staan-mcp"],
      "env": { "STAAN_API_KEY": "your-staan-api-key" }
    }
  }
}

这份 JSON 配置可在 Claude Desktop 和 Cursor 中直接使用。Claude Code、VS Code 和 Hermes Agent 使用不同的格式:请参阅客户端配置。

如果你是大模型,或者只是需要更多详情,请继续阅读。

详细设置

三种运行方式
  1. 安装包

uvx staan-mcp,或 pip install staan-mcp——会安装多个依赖项:MCP SDK、httpx2、pydantic、anyio(OAuth 网关还需要 pyjwt)。作为交换,它不仅提供 stdio,还提供 Streamable HTTP 和无状态 Streamable HTTP 连接,两种 HTTP 连接均带有 OAuth 网关。

  1. 下载并运行

通过 GitHub 上的 Code->Download ZIP 下载,或通过 git clone 获取。然后,你可以将 MCP 配置为通过 stdio_server.py 运行(参见安装指南中的无需安装任何内容部分)

  1. 通过发布版本

下载最新的发布版本

1. 安装包2. 下载并运行(检出代码)3. 单文件打包
方式uvx staan-mcp,或 pip install staan-mcpgit clone(或下载源代码树的 zip),然后运行 python src/staan_mcp/stdio_server.py从最新发布版本下载 staan-mcp-stdio.pyz,然后运行 python staan-mcp-stdio.pyz
Python3.11 或更新版本3.11 或更新版本,纯解释器,无需安装任何内容3.11 或更新版本,纯解释器,无需安装任何内容
依赖项MCP SDK、httpx2、pydantic、anyio(OAuth 网关还需要 pyjwt)无(仅使用标准库)无(仅使用标准库)
传输方式stdio、Streamable HTTP、无状态 Streamable HTTPstdiostdio
HTTP OAuth 网关有无无
工具、参数、结果、错误消息参考实现完全相同(见下文)完全相同(使用相同代码)

只有安装包提供 HTTP 服务和 OAuth 网关。方式 2 和方式 3 运行的是同一个 stdio_server.py;打包文件包含该文件及其导入的少量标准库模块,压缩成一个文件,可放在任意位置。

客户端配置

所有示例都使用 uvx 启动服务,并通过 STAAN_API_KEY 环境变量传递密钥。请将 your-staan-api-key 替换为你的密钥。

Claude Desktop

编辑 claude_desktop_config.json(Settings、Developer、Edit Config):

{
  "mcpServers": {
    "staan": {
      "command": "uvx",
      "args": ["staan-mcp"],
      "env": { "STAAN_API_KEY": "your-staan-api-key" }
    }
  }
}
Claude Code
claude mcp add staan --env STAAN_API_KEY=your-staan-api-key -- uvx staan-mcp

添加 --scope user,使其在每个项目中都可用;或添加 --scope project,写入可共享的 .mcp.json(不要将密钥放入要提交的文件中;请在其中使用环境变量展开,例如 "STAAN_API_KEY": "${STAAN_API_KEY}")。

VS Code

创建 .vscode/mcp.json。VS Code 会提示你输入一次密钥,并安全地存储它:

{
  "inputs": [
    {
      "type": "promptString",
      "id": "staan-api-key",
      "description": "Staan API key",
      "password": true
    }
  ],
  "servers": {
    "staan": {
      "type": "stdio",
      "command": "uvx",
      "args": ["staan-mcp"],
      "env": { "STAAN_API_KEY": "${input:staan-api-key}" }
    }
  }
}
Cursor

在项目中创建 .cursor/mcp.json,或创建 ~/.cursor/mcp.json 以供所有项目使用。Cursor 会展开 ${env:NAME},因此密钥可以保留在 shell 环境中:

{
  "mcpServers": {
    "staan": {
      "command": "uvx",
      "args": ["staan-mcp"],
      "env": { "STAAN_API_KEY": "${env:STAAN_API_KEY}" }
    }
  }
}

该代码仓库也提供 Cursor 插件(.cursor-plugin/plugin.json 和 mcp.json)。安装为插件后,Cursor 会要求你将 Staan API 密钥作为插件变量提供,而不是从 shell 中读取。

Hermes Agent

Hermes 从 ~/.hermes/config.yaml 中的 mcp_servers 读取 MCP 服务:

mcp_servers:
  staan:
    command: "uvx"
    args: ["staan-mcp"]
    env:
      STAAN_API_KEY: "your-staan-api-key"

Hermes 还提供原生插件,无需启动第二个进程,即可提供同样的两个工具 staan_web_search 和 staan_answer:参见 src/hermes。该插件独立于服务,不属于 staan-mcp 包。

其他客户端

任何能够启动 stdio 命令的客户端都可以使用:在环境中设置 STAAN_API_KEY,然后运行 uvx staan-mcp。有关 HTTP 客户端,请参见传输方式。

无需安装任何内容

方式 2 和方式 3 无需运行 pip install、无需虚拟环境,也无需第三方软件包;只需普通的 Python 3.11 或更新版本,且仅支持 stdio(HTTP 和 OAuth 门控需要已安装的软件包)。

从代码检出目录运行(方式 2)。 下载仓库(使用 git clone 或下载源码树的 zip),将 src/staan_mcp/ 保持在一起,然后让客户端指向一个文件: src/staan_mcp/stdio_server.py:

{
  "mcpServers": {
    "staan": {
      "command": "python",
      "args": ["/path/to/Staan-MCP/src/staan_mcp/stdio_server.py"],
      "env": { "STAAN_API_KEY": "your-staan-api-key" }
    }
  }
}

该脚本会将自己的 src 目录添加到 sys.path,并复用软件包中的标准库模块(市场、领域、结果格式化、错误、配置和工具文本),因此不能单独复制这个文件。若将其复制到仓库之外,它会打印一行说明,然后以状态码 2 退出,不会显示回溯;请改用打包文件。

使用哪个 Python。 两种方式都需要 Python 3.11 或更新版本。在 Windows 上,python 可能不存在,也可能是 Microsoft Store 的占位程序;请使用启动器,并设置 "command": "py", "args": ["-3", "C:\\path\\to\\staan-mcp-stdio.pyz"](JSON 中的反斜杠需要重复),或使用解释器的完整路径。在 macOS 和 Linux 上,命令通常是 python3。如果 Python 版本过旧,文件会立即停止,并显示 staan-mcp needs Python 3.11 or newer; this is X.Y.Z (path),然后以状态码 2 退出。使用 python --version 检查版本。

它是什么。 它不会导入 MCP SDK、httpx2、pydantic、anyio 或 pyjwt;测试会使用 python -I -S 运行它,并阻止所有非标准库导入,以验证这一点;打包文件也会以相同方式运行。它仅通过 stdio 提供与已安装软件包相同的 web_search 和 answer 工具、环境变量、结果和错误消息(测试会使用相同输入对两者进行比较)。它使用自己的简易 urllib 传输,而不是 httpx2,并采用相同的重试策略。它与软件包的其余部分一样,会通过 lint、类型、复杂度和覆盖率检查;只有变异测试会跳过它。

单文件打包文件(方式 3)。 从最新版本下载 staan-mcp-stdio.pyz,将其放在任意位置,然后让客户端指向该文件:

{
  "mcpServers": {
    "staan": {
      "command": "python",
      "args": ["/path/to/staan-mcp-stdio.pyz"],
      "env": { "STAAN_API_KEY": "your-staan-api-key" }
    }
  }
}

该文件是一个 Python zip 应用:运行 python staan-mcp-stdio.pyz --version 会打印 staan-stdio X.Y.Z。它仅包含 stdio_server.py 导入的软件包标准库模块,不含其他内容。每个版本都会将它与 wheel 和 sdist 一起发布,并附有已签名的构建来源证明(gh attestation verify staan-mcp-stdio.pyz --repo MarcinWolnyEU/Staan-MCP)。此构建可复现:在检出对应标签的仓库中运行 python scripts/build_zipapp.py,会生成字节完全相同的文件并打印其 SHA-256。

工具参考

这两个工具均为只读且面向开放世界(它们会访问外部服务,不会更改任何内容)。失败时会以 MCP 工具错误的形式报告简短且安全的消息,绝不会包含你的密钥。

web_search

通过 POST /v2/search/web 搜索网页。每次调用会返回一页结果,每个市场最多 10 条。

参数类型默认值描述
query字符串,1 到 400 个字符必填搜索查询。支持 site: 和 -site: 运算符。
market字符串或字符串列表"en-gb"一个或多个 Staan 市场。支持通配符,见下文。
full_content"none"、"markdown" 或 "html""none"以指定格式返回每个结果页面的文本。默认值 "none" 会省略此内容,从而减小响应(也减少令牌用量)。费用和延迟几乎不变,见下文说明。
offset0、10、20 或 300分页时的结果偏移量。每一页都是单独计费的请求。
include_domains字符串列表,最多 10 个无将结果限制为这些域名。域名也涵盖其子域名。
exclude_domains字符串列表,最多 10 个无排除这些域名的结果,包括其子域名。不能与 include_domains 同时使用。
simple_response布尔值truetrue 返回扁平、精简的列表。false 返回原始上游响应。

说明:

  • count 不是参数。Staan 只接受 10 条的页面大小,且服务器始终会发送该值。使用 offset 翻页。
  • 域名会自动规范化:https://www.Example.com:443/a?b 会变为 example.com,而 en.wikipedia.org 会保持为 en.wikipedia.org。
  • 服务器始终会请求额外摘要。如果你的密钥所属套餐包含这些摘要,它们会合并到每条结果的 snippet 中。如果不包含,上游响应中就不会有此字段,也不会造成问题。
  • 列出的域名也涵盖其子域名。于 2026-10-05 实时验证:include_domains: ["python.org"] 返回了 discuss.python.org、docs.python.org、wiki.python.org 和 www.python.org;exclude_domains: ["python.org"] 则移除了所有这些域名。你无需逐个列出子域名。
  • full_content 会改变响应大小,不会改变费用。 服务器始终会向 Staan 请求额外摘要,Staan 正是据此抓取并排序页面文本,因此 "none" 请求和 "markdown" 请求按相同档位计费,耗时也大致相同(2026-10-05 测量结果:相差约 0.1 到 0.5 秒)。不同之处在于文本量:一次 en-gb 调用使用 "markdown" 时,在单个市场中返回了约 130,000 个字符的页面文本(约 30,000 到 40,000 个令牌),而 "html" 的内容量会大上数倍。默认值 "none" 会将相同调用的响应缩减到一小部分(该测试中约为 20 KB)。仅在需要阅读页面时才请求 "markdown"。
  • 使用 full_content: "markdown" 或 "html" 时,输出大小默认不受限制。许多客户端会限制工具结果(Claude Code 的默认上限约为 25,000 个令牌),因此此类调用可能会在这些客户端中被截断或拒绝;market: "*" 则会将输出量最多扩大 12 倍。操作者可以使用 STAAN_MCP_MAX_CONTENT_CHARS(见配置)限制每个页面的内容长度:较长的页面会在段落边界处截断,并以 [... truncated: N of M characters shown ...] 结尾。摘要和额外摘要永远不会被截断。此限制默认关闭;如果你的客户端会限制工具输出且你需要页面文本,可从 STAAN_MCP_MAX_CONTENT_CHARS=8000 开始设置。
市场和通配符

Staan 支持 12 个市场:fr-fr、de-de、en-us、en-gb、en-fr、en-ca、en-au、 en-in、en-ie、en-nz、en-za 和 en-sg。代码不区分大小写。

market 可接受单个代码、代码列表,或使用 shell 风格通配符的模式 (*、? 和 [...],用于匹配这 12 个代码):

值展开为
"en-gb"en-gb
["en-us", "en-gb"]en-us、en-gb
"en-*" 或 "en*"10 个 en- 市场
"*-fr"fr-fr、en-fr
"*"全部 12 个市场

重复项会被移除,并保留首次出现的顺序。空值、未知代码或未匹配任何内容的模式都会报错,并列出有效市场。

警告:每个市场都会产生一笔计费请求,每一页也同样如此。 market: "*" 每次调用会产生 12 笔请求, market: "en-*" 会产生 10 笔。Staan 还会实施每秒 20 次请求的速率限制,服务端会通过延迟请求来遵守该限制(请参阅速率限制)。 建议使用明确且精简的市场列表。请查看 Staan 的定价,了解当前费率(撰写本文时,每月前 1,000 次请求免费,普通网页搜索每 1,000 次请求收费 EUR 1,包含额外摘要或完整内容的 请求每 1,000 次收费 EUR 2)。

选择多个市场时,请求会并发运行。如果部分市场失败,仍会返回其他市场的结果,并在 stderr 上记录一条警告,指出失败的市场。只有当所有市场都失败,或密钥本身被拒绝(无效、已禁用或不被允许)时,工具才会失败,因为这些错误会在每个市场中重复出现。

simple_response=true 时的输出(默认)

以文本内容形式返回单个 JSON 数组。选择多个市场时,结果按排名轮流交错排列(第一个市场的排名 0、第二个市场的排名 0,……然后是排名 1,以此类推),并按 URL 去重,因此保留首次出现的结果。

[
  {
    "title": "Top 7 open source LLMs for 2026",
    "url": "https://www.example.com/open-source-llms-2026",
    "snippet": "Top open source LLMs in 2026 (...) Open source LLMs are fully accessible for anyone to use, modify and distribute.",
    "full_content": "# Top 7 open source LLMs for 2026\n\nLarge Language Models are ..."
  },
  {
    "title": "A list of open LLMs available for commercial use",
    "url": "https://github.com/example/open-llms",
    "snippet": "A list of open LLMs available for commercial use."
  }
]
  • snippet 是上游摘要,后接以 (...) 拼接的所有额外摘要。
  • 仅当你请求了 full_content 且 Staan 返回了非空文本时,才会出现 full_content。无法抓取的页面不会包含 full_content 键。示例展示了第一个结果,其中 full_content: "markdown";默认情况下,没有结果包含此键。
simple_response=false 时的输出

返回一个 JSON 数组,每个请求的市场对应一项,顺序与市场顺序一致。成功的市场包含原始上游响应(此处已删节)。失败的市场则包含一个带有市场和错误消息的对象。

[
  {
    "search_id": "0d920ee2fc05ffd5fc834ba3ed346b02",
    "query": { "q": "open source llms", "market": "en-gb", "count": 10, "offset": 0 },
    "web": {
      "results": [
        {
          "title": "Top 7 open source LLMs for 2026",
          "url": "https://www.example.com/open-source-llms-2026",
          "snippet": "Top open source LLMs in 2026 ...",
          "display_url": "https://www.example.com/open-source-llms-2026",
          "hostname": "www.example.com",
          "extra_snippets": [{ "chunk": "Open source LLMs are ...", "score": 0.99 }],
          "full_content": { "text": "# Top 7 ...", "format": "markdown", "length": 5120 }
        }
      ]
    }
  },
  { "market": "de-de", "error": "Staan rate limit reached (HTTP 429). Try again shortly." }
]

工具调用示例:

{ "query": "python 3.14 release notes", "full_content": "markdown" }
{ "query": "bundesliga tabelle", "market": "de-de", "include_domains": ["kicker.de"] }
{ "query": "oat milk prices", "market": ["en-gb", "en-ie"], "offset": 10, "simple_response": false }
answer

向 POST /v2/answer 请求基于网络来源(或你提供的来源)生成的答案。服务器始终要求返回非流式 Markdown 答案,且不会发送对话历史。

参数类型默认值描述
query字符串,1 到 2,000 个字符必填问题。
filter字符串无将搜索限制在一个域名内,例如 frandroid.com。也接受 URL,并会将其简化为纯域名。空字符串表示不进行筛选。设置了 sources 时,Staan 会忽略此项。
language"en" 或 "fr""en"答案的语言。(Staan 自身的默认语言为法语。)
mode"short" 或 "long""short"答案的长度。
sources来源对象列表,最多 10 个无使用你自己的来源作为答案依据,而不是进行网络搜索。
sources_mode"search" 或 "context""search""search" 会获取并重新排序来源;"context" 会直接注入其 content。仅在设置了 sources 时发送。
simple_response布尔值truetrue 仅返回带引用的答案文本。false 返回原始上游对象。

来源对象可包含可选的 url、title(最多 500 个字符)和 content(最多 50,000 个字符)。每个来源都必须至少包含 url 或 content。

Staan 会获取你提供的 url 值,因此会先验证这些值:必须是带有主机名的绝对 http 或 https URL,长度不超过 2,048 个字符,且不含空白字符或控制字符,也不能是字面上的私有、环回、链路本地或保留 IP 地址(localhost 也不行)。其他 URL 会在发送请求前被拒绝。这项检查针对的是 URL 字面值,并不保证主机名解析到的地址符合要求。

simple_response=true(默认值)时的输出

包含一个键的对象。引用会转换为 GitHub 风格的 Markdown 脚注:在每个引用位置插入类似 [^1] 的标记,并在文末附上包含来源 URL 的脚注列表。脚注按首次出现的顺序编号,且只列出被引用的来源。

{
  "answer": "The Eiffel Tower is about 330 metres tall.[^1] It was completed in 1889 for the World Fair.[^2] Millions of people visit it every year.\n\n[^1]: https://example.org/eiffel-tower\n[^2]: https://example.org/world-fair-1889"
}

如果响应中没有可用的引用,则原样返回答案。若响应中没有文本类型的 answer 字段,则会报告工具错误(无论 simple_response 的值是什么)。

脚注渲染的详细信息:

  • 根据 source_id 将引用与其来源匹配。对于编号而非命名的来源(完全没有 ID),按位置匹配("2" 表示第二个来源)。如果来源带有 ID,则无法识别的 ID 会被丢弃,而不会猜测对应关系,因为错误引用比不引用更糟。
  • 标记不会出现在代码围栏或表格内部,也不会紧接在其末尾,因为这会破坏代码块并吞掉脚注列表。标记会移到该块之前的文本末尾(如果答案开头就是代码块,则会丢弃该块的引用)。
  • 只列出没有空白字符或控制字符、长度不超过 2,048 个字符的 http 和 https 来源 URL。其他 URL 会被忽略,以防上游文本在脚注后注入 Markdown。

该工具声明了输出架构:包含必需的 answer 字符串的对象(当 simple_response 为 false 时,还包含其他上游字段)。文本内容与该 JSON 相同,但格式紧凑。

simple_response=false 时的输出

Staan 文档中所述的原始上游对象(经删节):

{
  "request_id": "01938fc2-1b3a-7e4d-9c12-a4f8e3c20d91",
  "answer": "The Eiffel Tower is about 330 metres tall. It was completed in 1889 ...",
  "citations": [{ "position": 42, "source_id": "1" }, { "position": 87, "source_id": "3" }],
  "sources": [
    { "id": "1", "url": "https://example.org/eiffel-tower", "title": "Eiffel Tower facts" },
    { "id": "3", "url": "https://example.org/world-fair-1889", "title": "The 1889 World Fair" }
  ],
  "related_queries": ["How was the Eiffel Tower built?"],
  "usages": [{ "step": "answer", "input_tokens": 2840, "output_tokens": 194 }]
}

工具调用示例:

{ "query": "What changed in the latest EU AI Act guidance?", "mode": "long" }
{ "query": "meilleur smartphone 2026", "language": "fr", "filter": "frandroid.com" }
{
  "query": "Summarise our release policy",
  "sources_mode": "context",
  "sources": [{ "title": "Policy", "content": "Releases happen on the first Monday of the month ..." }]
}
错误响应
情况工具报告的内容
未设置 STAAN_API_KEY指明该变量的配置错误。
密钥被拒绝(401)身份验证错误。请检查密钥。
密钥已禁用(403,KEY_DISABLED)密钥已禁用错误。
未获许可(403)对于 answer:此 API 密钥未获启用(仅限受邀用户,请联系 [email protected]),或 Staan 暂时禁用了该功能。
参数无效(400、422)Staan 的验证消息,以 ; 连接。
请求过多(429)、服务器错误(5xx)、连接失败请求会重试(参见 STAAN_MAX_RETRIES),然后报告速率限制、服务器或传输错误。绝不回显上游的服务器错误文本。
读取超时(在 STAAN_TIMEOUT 内没有收到答案)立即报告,且不重试:请求已经发送,重试会导致重复计费。
请求因速率限制而必须等待到 STAAN_DEADLINE 之后才能发送立即报告相同的截止时间错误,且不发送该请求。
调用耗时超过 STAAN_DEADLINE默认在 50 秒后报告传输错误,其中包括重试和等待时间,以便模型能在典型的 60 秒客户端超时前收到明确的错误。
STAAN_BASE_URL 或 STAAN_API_KEY 无法用于 HTTP 请求配置错误会指出这两个变量(绝不会指出密钥)。无论市场数量是多少,整个调用都会立即终止。
响应超过 32 MiB报告响应错误;丢弃响应正文。
速率限制

Staan 将每个 API 密钥的请求速率限制为每秒 20 个。market: "*" 会同时发送 12 个请求,因此同时进行两次这样的调用(24 个请求)就会超出限制,而且每次重试也算作一个请求。因此,服务会统计自己发送的请求,并对超出限额的请求延迟处理;绝不会拒绝请求,延迟只会让调用稍微耗时更长。

  • 计数规则。 一个服务发送给 Staan 的每个请求:web_search 和 answer、每次调用的每个市场,以及每次重试。任意一秒窗口内(采用滑动窗口,因此不会在两秒内突发 40 个请求),最多启动 STAAN_RATE_LIMIT 个请求(默认值为 20),并在每个窗口增加 50 毫秒的安全余量,以吸收时序抖动:默认情况下,每 1.05 秒最多 20 个请求。请求按发出顺序处理。
  • 示例。 同时发起两次 market: "*" 调用:24 个请求中的 20 个立即启动,其余 4 个在 1.05 秒后启动。两次调用都会返回完整结果。
  • 设置。 STAAN_RATE_LIMIT 表示每秒请求数。从 1 开始的小数会向下取整(2.5 表示每秒允许 2 个请求);低于 1 时,请求会按 1 / rate 秒的间隔发送(0.5 表示每两秒发送一个)。0 表示关闭限流器。允许使用大于 20 的值(你的套餐可能支持更高上限),但 Staan 的默认上限是 20,只有确定自己的上限更高时才应调高。
  • 截止时间。 等待时间属于调用耗时,因此会计入 STAAN_DEADLINE(但不计入 STAAN_TIMEOUT,后者用于限制单个请求在线路上的耗时)。如果请求必须等到截止时间之后才能发送,就会立即以截止时间错误失败,且不会发送。调用在等待期间取消时,尚未发送的请求不会发出,其占用的时段也会释放给其他请求。
  • 范围。 限额按单个服务进程计算。共享一个 API 密钥的多个服务进程(两个客户端、一个 Docker 容器和一个本地服务)互不知情,因此它们的限额会累加:使用 STAAN_RATE_LIMIT 在它们之间分配限额。
  • 日志。 延迟的请求会以 DEBUG 级别记录,并注明延迟时长(下载并运行版本会向 stderr 写入一行);未超出限额的请求不会产生日志。

配置

每个选项都可以通过标志或环境变量设置。标志优先于环境变量。 空环境变量值视为未设置。

下载并运行的变体会读取下方的 Staan API 变量(不会读取其他变量),且只接受 --version 和 --help:服务和 OAuth 表适用于已安装的软件包。

Staan API
变量默认值描述
STAAN_API_KEY无Staan API 密钥,会作为 Authorization: Bearer 发送到上游。调用工具时必需。必须是没有空格的可见 ASCII 字符(如果密钥中粘贴了弯引号,启动时会拒绝,但不会回显该密钥)。
STAAN_BASE_URLhttps://api.staan.ai/v2API 基础 URL。必须使用 https,除非主机是环回地址;必须包含有效端口,且不得包含凭据(user:password@)。
STAAN_TIMEOUT30单次尝试的超时时间,以秒为单位(必须大于 0)。
STAAN_DEADLINE50单次工具调用的总时限,以秒为单位,包括重试和等待(必须大于 0)。请将其设为低于客户端自身的工具超时时间(通常为 60 s)。
STAAN_MAX_RETRIES2对 429、500、502、503、504 和连接错误的重试次数(0 到 10);读取和写入超时绝不重试。退避时间为 min(0.5 * 2**attempt, 8) 秒;如果存在 Retry-After,还会额外等待其指定时间(最长 30 s)。
STAAN_RATE_LIMIT20服务每秒启动的最多请求数,包含重试,所有工具和市场合并计算;超出的请求会等待(参见速率限制)。数值:大于等于 1 时向下取整,小于 1 时按 1 / rate 秒的间隔发送请求。0 表示关闭限制。Staan 的默认上限为 20。
STAAN_MCP_MAX_CONTENT_CHARS0(无限制)每个搜索结果保留的最长 full_content 文本长度(0 或至少为 200)。较长页面会在段落边界处截断,并标记为 [... truncated ...]。适用于两种 simple_response 模式。
服务
标志环境变量默认值描述
--transport {stdio,http,stateless}STAAN_MCP_TRANSPORTstdio提供服务的传输方式。
--host HOSTSTAAN_MCP_HOST127.0.0.1绑定地址(HTTP 传输方式)。
--port PORTSTAAN_MCP_PORT8000绑定端口(HTTP 传输方式)。
--path PATHSTAAN_MCP_PATH/mcpMCP 端点的 URL 路径。
--allowed-host HOSTSTAAN_MCP_ALLOWED_HOSTS无接受的 Host 标头值。可重复使用此标志,或使用逗号分隔的变量值。
--allowed-origin ORIGINSTAAN_MCP_ALLOWED_ORIGINS无接受的 Origin 标头值。语法相同。
--insecure-no-auth无关闭允许在非环回 HTTP 地址上绑定,而不启用 OAuth 门禁。仅可在受信任且经过身份验证的代理之后使用。
--log-level LEVELSTAAN_MCP_LOG_LEVELWARNING日志级别,也应用于 HTTP 服务(WARNING 会隐藏其逐请求访问日志)。日志仅输出到 stderr。
--version无无打印版本并退出。
OAuth 网关

设置任何 STAAN_MCP_AUTH_* 变量都会启用网关(仅限 HTTP 传输)。配置不完整或不一致会导致启动错误。

变量必需描述
STAAN_MCP_AUTH_ISSUER_URL是授权服务器的颁发者 URL。令牌必须包含此 iss。
STAAN_MCP_AUTH_RESOURCE_URL是此 MCP 端点的规范公共 URL,例如 https://mcp.example.com/mcp。令牌的受众必须为此值。
STAAN_MCP_AUTH_SCOPES否每个令牌都必须包含的作用域,以空格或逗号分隔。
STAAN_MCP_AUTH_JWKS_URL二选一用于验证 JWT 访问令牌的 JWKS URL。需要 oauth extra。
STAAN_MCP_AUTH_INTROSPECTION_URL二选一RFC 7662 令牌内省端点。需要客户端 ID 和密钥。
STAAN_MCP_AUTH_CLIENT_ID使用内省时必需用于内省的客户端 ID(HTTP Basic)。
STAAN_MCP_AUTH_CLIENT_SECRET使用内省时必需用于内省的客户端密钥。
STAAN_MCP_AUTH_ALGORITHMS否接受的 JWT 算法。默认值为 RS256,ES256。始终拒绝 none。

除主机为环回地址外,所有 URL 都必须使用 https,且不得包含凭据(user:password@)。JWKS URL 和内省 URL 必须且只能设置一个。

仅包含源站的 URL(无路径,例如 https://auth.example.com)会在受保护资源元数据中以尾随斜杠形式发布,因为 SDK 会对其进行规范化。因此,校验会接受仅包含源站的颁发者和资源 URL 的两种写法:aud 或 iss 为 https://host 或 https://host/ 的令牌均可通过。带路径的 URL(https://host/mcp)则必须完全匹配。

JWT 验证会以 10 s 超时获取 JWKS,并且每 30 s 最多刷新一次,因此包含未知 kid 的令牌无法让服务器不断请求你的身份提供方。这需要 PyJWT 2.14 或更高版本,而 oauth extra 会要求此版本。内省会通过一个共享连接池,向你的授权服务器针对每个令牌发送一次带凭据的请求。超过 4,096 个字符或包含非可见 ASCII 字符的令牌会在本地被拒绝,不会发出请求。

安全:OAuth 网关与威胁模型

凭据
  • 上游:Staan 不使用 OAuth。唯一的上游凭据是 STAAN_API_KEY 中的 API 密钥,由服务器持有,并作为 Authorization: Bearer 发送。该密钥绝不会写入日志或错误消息,也不会显示在设置对象的 repr() 中。
  • 入站:服务器绝不会将入站访问令牌转发给 Staan(MCP 规范禁止“令牌直通”)。令牌仅用于判定谁可以使用该服务器。没有按请求提供的客户端密钥标头,也没有静态共享 Bearer 令牌。
OAuth 网关

配置后,HTTP 传输会作为 OAuth 2.1 资源服务器运行,具体说明见 MCP 授权规范。授权服务器由你运行(任何能够颁发 JWT 或响应 RFC 7662 内省请求的服务器均可)。网关会:

  • 发布 RFC 9728 受保护资源元数据,并对未经身份验证的请求返回 401,同时附带指向该元数据的 WWW-Authenticate 标头;
  • 验证每个令牌:签名(JWKS)或 active(内省)、exp、颁发者、与 STAAN_MCP_AUTH_RESOURCE_URL 相等的受众,以及所需作用域;
  • 仅接受已配置的算法,绝不接受 none;
  • 将任何验证失败视为无效令牌,并在调试级别记录原因,但不记录令牌。
威胁模型
威胁缓解措施残余风险
他人通过暴露的 HTTP 端口消耗你的 Staan 配额默认绑定到环回地址。非环回绑定需要启用 OAuth 门控或显式使用 --insecure-no-auth,并指定 --allowed-host。是否使用 --insecure-no-auth 由你决定:请在它前面配置身份验证。
DNS 重绑定或恶意网页调用本地服务验证 Host 和 Origin(环回地址上由 SDK 提供保护,其他情况则由你配置允许列表)。错误设置的通配符允许列表会削弱此保护。
为其他服务签发的令牌在此处被重放受众必须与规范资源 URL 一致。系统会检查颁发者、过期时间和范围。取决于你的授权服务器是否签发了正确的受众。
访问令牌被盗短期令牌、范围检查,以及在你的代理上使用 TLS。有效令牌在过期前就代表有效用户。请在授权服务器上撤销它。
密钥通过日志或错误泄露日志仅写入 stderr。密钥、Authorization 标头和令牌绝不会被记录或回显。不会转发上游 5xx 文本。你自己的客户端或代理可能会记录参数或标头。
搜索结果(full_content、摘要片段)中的提示词注入两个工具均为只读且无副作用:它们无法写入文件、运行代码或调用其他服务。返回的网页文本是不可信的第三方内容。你的 MCP 客户端和模型必须据此处理;在未经审查的情况下,不应向读取不可信网页的 Agent 提供其他强大工具。
成本放大(market: "*"、多页、重试)限制市场和域名,设置重试上限,不重试已发送的请求(读取超时),设置整体调用期限,并提供计费说明。单次调用仍可能产生最多 12 次计费请求。请使用 Staan 的用量控制。
经提示词注入的模型让 Staan 通过 answer 来源获取 file://、内部或超大的 URL来源 URL 必须是看起来公开、绝对、长度不超过 2,048 个字符的 http(s) URL,且不得包含字面形式的私有、环回或链路本地 IP。检查针对的是 URL 字面内容:解析到内部地址的主机名无法在此处被拦截。抓取操作由 Staan 执行。
匿名调用者让服务不断请求身份提供方(伪造 JWT、无效的 bearer 令牌)JWKS 刷新冷却时间(PyJWT 2.14+)、10 s 超时、共享的 introspection 连接池,以及对不合理令牌的本地拒绝。对每个看似合理的未知令牌,仍会向你的授权服务器发送一次 introspection 请求;请在代理上设置速率限制。
网络上传输明文流量所有上游流量均使用 https(除环回地址外,若基础 URL 为 http 则会被拒绝),OAuth URL 也使用 https。服务自身的 HTTP 监听器使用明文 HTTP:请在其前面终止 TLS。

不在范围内:运行授权服务器、按用户设置配额,以及多租户密钥隔离。如需报告漏洞,请使用私密安全公告,不要提交公开议题。

故障排除

症状原因和修复方法
工具提示 STAAN_API_KEY is not set将密钥添加到客户端的 env 块中,或在启动服务前导出该密钥。
身份验证错误密钥错误或已撤销。请在 Staan 控制台中创建新密钥。
answer 提示此 API 密钥未启用该功能Answer API 仅限受邀用户使用,Staan 也可能暂时禁用该功能。如果问题持续存在,请联系 [email protected]。
market 报错并列出有效市场使用 12 个代码之一,或使用 en-* 这样的通配符。
服务启动时以状态码 2 退出查看 stderr 中的消息:通常是因为使用了非回环主机,但未配置 OAuth 或 --allowed-host。
staan-mcp: cannot import the staan_mcp package(单行,状态码 2)stdio_server.py 被复制到了仓库之外。请将它保留在 src/staan_mcp/ 中,与其他模块放在一起;也可以使用单文件 staan-mcp-stdio.pyz 程序包,或已安装的程序包。
staan-mcp needs Python 3.11 or newer; this is ...(状态码 2)客户端的 command 启动的是较旧版本的 Python。请将其指向 Python 3.11 或更高版本的解释器,必要时使用完整路径。
客户端无法启动 python在 Windows 上使用 "command": "py", "args": ["-3", "C:\\path\\to\\staan-mcp-stdio.pyz"](或解释器的完整路径);在 macOS 和 Linux 上,命令通常是 python3。
HTTP 客户端收到 421 Misdirected RequestHost 标头不在允许列表中。请使用 --allowed-host 添加它。
工具结果被截断或客户端拒绝了结果你请求了 full_content: "markdown" 或 "html",这会生成较大的内容(每次调用数万个 token,如果包含多个市场,内容会更多)。请使用默认值 "none"、减少市场数量,或设置 STAAN_MCP_MAX_CONTENT_CHARS。
响应缓慢多个市场会并行处理,因此一次调用的耗时取决于最慢的那个市场。full_content 只会略微增加延迟;默认值 "none" 主要是缩小答案。调用会在 STAAN_DEADLINE 秒后终止。每秒请求超过 20 次(即两次 market: "*" 调用)时,会受到速率限制的影响。
The request to Staan could not be builtSTAAN_BASE_URL 中的端口无效,或者 STAAN_API_KEY 包含无法在标头中发送的字符。请修复消息中指出的变量。
客户端中没有任何内容显示在终端中运行服务并查看 stderr:uvx staan-mcp --log-level DEBUG,或 python staan-mcp-stdio.pyz(下载并运行的变体不接受任何标志,但会将问题记录到 stderr)。在 stdio 模式下,切勿向 stdout 写入内容。

开发

你需要 uv。它会安装合适的 Python 和所有工具。

git clone https://github.com/MarcinWolnyEU/Staan-MCP.git
cd Staan-MCP
uv sync                          # creates .venv with all dependency groups (use UV_LINK_MODE=copy on Windows across drives)
uv run staan-mcp --version

以下质量门禁均为 CI 必须通过的检查:

uv run --frozen ruff check .                  # every rule enabled, documented opt-outs only
uv run --frozen ruff format --check .
uv run --frozen mypy                          # --strict
uv run --frozen pylint src tests scripts      # 10.00/10
uv run --frozen xenon --max-absolute B --max-modules A --max-average A src
uv run --frozen pytest                        # 100% line and branch coverage required
uv build && uv run --frozen --only-group dist twine check --strict dist/* && uv run --frozen --only-group dist check-wheel-contents dist/*.whl
uv run python scripts/build_zipapp.py         # builds dist-bundle/staan-mcp-stdio.pyz; tests/repo run it

本项目使用 OMP(Opus 5.5、Sonnet 5.5、Mimo V2.6 Pro)、 VSC、git 以及 pyproject.toml 中列出的依赖构建。

  • 变异测试使用 mutmut,需要 Linux、macOS 或 WSL(无法在原生 Windows 上运行;请在 Windows 上使用 Docker)。 运行 uv run mutmut run,然后运行 uv run mutmut export-cicd-stats。CI 会检查 mutants/mutmut-cicd-stats.json。只有 stdio_server.py 模块会被跳过。
  • 实时测试会调用真正的 Staan API。除非设置了 STAAN_API_KEY 并传入 --run-live,否则会跳过这些测试:uv run pytest tests/live --run-live --no-cov。CI 从不运行这些测试。
  • 钩子:运行 uv tool install prek && prek install(或使用 pre-commit)会执行 ruff、mypy、 pylint、xenon、zizmor、actionlint、typos、针对 server.json、工作流和 Dependabot 的架构检查、机密扫描(detect-private-key 和 gitleaks),以及 Conventional Commits 检查。CI 会对每个文件运行 相同的钩子(并检查拉取请求标题),还会使用 gitleaks 扫描整个 git 历史记录。尽可能将密钥文件放在仓库目录之外;.gitignore 也涵盖 *key*.txt、*.pem、*.key 和 credentials*。
  • 关于 Staan API、MCP SDK 和 OAuth 的研究笔记位于 docs/research/。请参阅 变更日志,了解变更内容。

贡献

创建一个 PR,确保流水线全部通过 (这意味着代码覆盖率达到 100%、lint 和类型检查通过、项目保持当前兼容性等)。请说明实施变更的原因, 以及变更带来的风险。我会亲自审查你的 PR。

所有拉取请求都会在 2 周内得到回复。

提供反馈和报告错误

请通过 问题 选项卡进行报告。安全 问题请使用专用表单。

所有问题都会在 2 周内得到回复,安全公告会在 48 小时内得到回复。

许可证

MIT (c) 2026 Marcin Wolny。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。