Google 现在让一个 MCP Server 同时进 Search 和 Gemini,而审核是按入口分开的

·阅读约10分钟·Evergreen Tools Team

2026 年 10 月 28 日,Google 开放了 Connected Apps Platform 的预览。官宣由 Search 的 Group Product Manager Chips Mistry 和 Gemini App 的 Group Product Manager Mai Lowe 联合署名,措辞很克制:给开发者「一个地方」,去构建和管理同时服务 Search 与 Gemini App 的应用。同一篇公告里还带了一批新伙伴——Adobe、Experian、Localiza、Peloton 的集成已经在 Gemini 里上线,几周内会进 Search,Calm 则两边一起上。真正值得开发者停下来看的是这句话:截至目前,Search 和 Gemini 之间已经上线了 50 多个 Connected App,覆盖生产力、活动票务、健康健身。也就是说,这不是一次普通的 API 发布,而是一个已经跑起来的分发渠道,现在开了门。

笔记本屏幕上的一行行代码

接入一次,覆盖两个入口:Search 和 Gemini App。

一、这次放出来的到底是什么

官方描述是「build once, reach high-intent users across both Search and the Gemini app」——一次接入,同时覆盖两个入口,而这两个入口的用户意图浓度都很高:一个是在搜索框里明确要办事的人,一个是在对话里已经把需求说清楚的人。接入方式写得很具体:开发者对接一个云端托管的 MCP server,或者用 AppFunctions API 走 Android 端 MCP;然后在 Google Connected Console 里注册、测试、提交;在正式审核之前,选择这个应用只进 Search、只进 Gemini App,还是两个都进。上线之后可以在控制台里看流量、延迟和错误率,并保留品牌身份的掌控权,包括经过验证的 Logo 和已认证的账号。开放是分阶段的,未来几个月会增加更多开发者、更多市场、更多 Google 产品。文档在 developers.google.com/connected-apps。

# One MCP tool, written so a model can actually choose it.
# The description is not documentation. It is the routing signal.
{
  "name": "find_available_appointment",
  "description": "Find open appointment slots for a service at a named location. Use when the user asks about availability, openings, or times. Read-only. Returns up to 10 slots.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "service":   { "type": "string", "description": "Service slug, e.g. 'oil-change'" },
      "location":  { "type": "string", "description": "City or store name as the user said it" },
      "after":     { "type": "string", "format": "date", "description": "Earliest date, ISO-8601" },
      "max_results": { "type": "integer", "minimum": 1, "maximum": 10, "default": 5 }
    },
    "required": ["service", "location"]
  },
  "annotations": { "readOnlyHint": true, "idempotentHint": true }
}

# Rules that survived review on our side:
# 1. Verb-first names, no internal codenames.
# 2. Say what it returns AND how many items.
# 3. One tool, one intent. 'do_everything' never gets picked.
# 4. Mark read-only tools read-only so the surface can skip confirmation.

二、接入契约:两条路径,一套后端

两条接入路径的差别不只是 SDK 不同,而是运行时不同。云端 MCP server 是你自己部署、自己运维、自己扩缩容;Android MCP 走 AppFunctions API,能力跑在设备侧,受系统调度和权限模型约束。两条路都通向同一件事:把「能力」以工具的形式暴露给模型,模型根据工具的名字和描述来决定调不调用。这句话的技术后果常被低估——工具描述不是给人看的文档,它是路由信号。描述里写「查询可用预约时段,只读,最多返回 10 条」,模型就知道什么时候该用它;描述里写「核心业务能力封装」,模型就只能猜。所以第一步不是写代码,是把你打算暴露的三到五个工具,用「动词开头的名字 + 它会返回什么 + 返回多少条」重写一遍。

// Android side: the same capability exposed through AppFunctions.
// AppFunctions is how an on-device agent discovers and calls your app.

@AppFunction
suspend fun findAvailableAppointment(
    @AppFunctionParam("service") service: String,
    @AppFunctionParam("location") location: String,
    @AppFunctionParam("after") after: String? = null,
): AppointmentResult {
    // Keep this path free of UI. If the agent has to render a screen,
    // the interaction has already failed.
    val slots = repository.findSlots(
        service = service,
        location = location,
        after = after?.let(LocalDate::parse),
        limit = 5,
    )
    return AppointmentResult(slots = slots, truncated = slots.size == 5)
}

// Two constraints that bite later:
// - Bound every call: an agent may retry quickly and often.
// - Never assume a logged-in UI session; carry identity explicitly.

三、审核是按入口分开的,文案也得分开写

公告里有一句容易被略过:在审核之前,开发者要选择这个应用会在 Search、Gemini App 还是两者同时上线。这句话隐含两层工作量。第一层是产品层面:同一个能力在两个入口的呈现方式不同。Search 侧的交互节奏更短、更不容忍等待,一个四秒的工具调用没人会替你解释;Gemini 侧的对话可以先说一句「正在查」,用户愿意多等一会儿。第二层是合规与品牌层面:控制台明确提到保留品牌身份的控制权,包括验证过的 Logo 和认证过的账号,说明审核确实会查「你是不是你说的那个服务」。对做工具站的团队来说,这两个入口意味着两套措辞、两套示例问法、两套失败兜底,而不是把同一段文案复制两遍。

显示图表和指标的看板

控制台给你流量、延迟和错误率;每次工具调用的成本还得自己埋点。

# Console submission, trimmed to the fields that decide the outcome.
app:
  name: "FleetCharge"
  mcp_endpoint: "https://mcp.example.com/v1"          # cloud-hosted MCP server
  android_mcp: "appfunctions://fleetcharge/v1"        # optional Android MCP path
  auth:
    type: oauth2
    scopes: ["appointments.read", "appointments.write"]
  tools:
    - find_available_appointment    # read-only
    - book_appointment              # confirmation required
    - cancel_appointment            # confirmation required
  surfaces:                            # chosen BEFORE review
    search: true
    gemini_app: true
  brand:
    display_name: "FleetCharge"
    verified_logo: true
    domain_verified: true
  review:
    data_handling_url: "https://example.com/privacy"
    support_contact: "[email protected]"

# Ship Search-only first if your latency budget is tight: the query box
# is less forgiving than a chat turn that can say 'still looking'.

四、控制台给你的指标,和它不给你的

上线后能看到的是流量、延迟、错误率——以入口维度切分。这三个数字足够回答「有没有人在用」和「有没有坏掉」,但回答不了「哪个工具慢」「哪次调用贵」「用户在哪个参数上卡住」。这类问题只能在自己的 MCP server 上埋点:每个工具一个直方图,按 tool、surface、status 三个维度打标签;错误按原因分类计数,而不是只记一个 500。为什么要按 surface 分开看?因为两个入口对延迟的容忍度真的不一样:搜索框里没人会等,聊天里用户至少看到了「正在处理」。同一套 p95 阈值套到两个入口上,一定会误判其中一个。

// The console reports traffic, latency and error rate per surface.
// It does not tell you which tool is slow. Instrument that yourself.

import time
from prometheus_client import Histogram, Counter

TOOL_LATENCY = Histogram(
    "connected_app_tool_latency_seconds",
    "Per-tool latency as seen by the MCP server",
    ["tool", "surface", "status"],
    buckets=(0.05, 0.1, 0.25, 0.5, 1, 2, 5),
)
TOOL_ERRORS = Counter(
    "connected_app_tool_errors_total",
    "Tool failures by reason",
    ["tool", "surface", "reason"],
)

def call_tool(tool: str, surface: str, fn, *args, **kwargs):
    start = time.perf_counter()
    status = "ok"
    try:
        return fn(*args, **kwargs)
    except TimeoutError:
        status = "timeout"
        TOOL_ERRORS.labels(tool, surface, "timeout").inc()
        raise
    except Exception:
        status = "error"
        TOOL_ERRORS.labels(tool, surface, "unhandled").inc()
        raise
    finally:
        TOOL_LATENCY.labels(tool, surface, status).observe(time.perf_counter() - start)

# What to watch in week one: p95 per surface. Search traffic punishes
# slow tools differently than a chat turn does: no 'thinking' message
# covers for a four-second call.

五、先做哪五个工具,别做什么

如果你的服务已经有 REST API,最容易犯的错是把整个 API 目录搬过去。工具不是接口,工具是「用户会说的那句话」的对应物。按这个标准筛,先做五类:查询类(有没有、多少钱、什么时候有空)、列表类(给我几个选项)、状态类(我的单子到哪了)、比较类(A 和 B 哪个更合适)、以及需要用户确认的写操作(下单、改期、取消)。写操作一定要标成需要确认,并且可撤销——这是审核会看、用户会记恨的地方。反过来,不要做的包括:一次调用返回几十条数据(入口渲染不了)、需要多轮澄清才能凑齐参数的(模型会放弃)、以及名字里带内部代号或缩写的(没有人能理解)。分阶段开放意味着你现在提交,可能只拿到部分市场的访问权,这反而是好事:先用小流量把 p95 和错误率压下去,再谈扩大。

团队在会议室讨论产品

按入口分开审核,意味着文案要写两遍,而不是复制粘贴。

# One contract, two surfaces: keep the difference in copy, not in code.
import pytest
from app.contracts import find_available_appointment

SURFACES = ["search", "gemini_app"]

@pytest.mark.parametrize("surface", SURFACES)
def test_same_intent_same_shape(surface):
    result = find_available_appointment(
        service="oil-change", location="Seattle", surface=surface
    )
    assert result["slots"], "empty results waste a surface slot"
    assert len(result["slots"]) <= 5, "cap payloads; the surface renders a list"
    assert result["surface"] == surface

@pytest.mark.parametrize("surface", SURFACES)
def test_never_raises_on_bad_input(surface):
    # Agents pass user language straight through. Swallow and explain.
    out = find_available_appointment(service="", location="", surface=surface)
    assert out["error"]["retryable"] is False
    assert "service" in out["error"]["message"]

# Run this before every submission: the review gate checks behaviour on
# both surfaces, and a crash on one delays both.

📌 常见问题 FAQ

Connected Apps Platform 现在能用吗?

10 月 28 日开放的是预览(preview),采取分阶段开放的方式,官方说明会在未来几个月增加更多开发者、市场和 Google 产品。入口在 developers.google.com/connected-apps,需要先申请预览。

必须做 Android 端才能接入吗?

不用。公告给了两条路径:云端托管的 MCP server,或者用 AppFunctions API 的 Android MCP。前者是你自己的服务,后者是设备侧能力。只做 Web 服务的团队走第一条就行。

AppFunctions 是什么?

公告里的表述是「Android MCP using the AppFunctions APIs」,也就是把应用能力以函数形式暴露出来,供设备侧 agent 发现和调用。对开发者的实际要求是:这条路径不能依赖 UI 交互,必须在没有界面渲染的情况下返回结构化结果。

审核之前要决定什么?

要选择这个 Connected App 上线到 Search、Gemini App,还是两者都上。这个选择在提交审核之前做,并且直接决定两边的呈现方式和延迟预算,所以应该当成产品决策,而不是发布开关。

控制台能看到哪些数据?

官方列出的三项是流量、延迟、错误率。它不提供按工具维度的性能拆分,也不提供单次调用成本,所以工具级监控要自己在 MCP server 上做。