mcpField
D
droidmap

droidmap

本地服务
github.com开发工具01 天前更新

关于 droidmap

droidmap 为 Android 项目构建包含 Room、Navigation、依赖注入和 Gradle 模块关系的图,适用于开发者、代码审查流程和编码 Agent。它可分析变更影响、定位规则问题并筛选受影响的测试,读取源文件但不会自行编译代码。

主要功能

  1. 01分析 Android 组件与模块依赖图
  2. 02检查 Room、Navigation 和依赖注入规则
  3. 03评估代码变更影响及受影响测试
  4. 04生成项目报告与流程图
  5. 05为拉取请求生成影响摘要

适用场景

  1. 01提交前检查数据库迁移和路由配置
  2. 02评估变更影响并选择需运行的测试
  3. 03在拉取请求中展示受影响模块与组件
  4. 04让编码 Agent 查询项目依赖关系

MCP 相关

droidmap 可通过 stdio MCP 服务向客户端提供影响分析、变更差异、规则检查、节点查询、搜索、报告和规则说明工具。文件变化时会更新图,以便 Agent 获取与代码编辑同步的分析结果。

配置

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

mcp.json · 通用配置
{
"mcpServers": {
"hussainshirazi-droidmap": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"ghcr.io/hussainshirazi/droidmap:0.3.0"
]
}
}
}
启动命令docker run -i --rm ghcr.io/hussainshirazi/droidmap:0.3.0

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

安装包
ocighcr.io/hussainshirazi/droidmap:0.3.0

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

droidmap

CI Release License MCP Registry

一张了解项目的 Android 专属地图,为你、你的拉取请求和你的 AI 编码 Agent 而打造。

通用代码图能识别类和调用,却看不到这些问题:

  • Room 数据库的版本 4 没有迁移到版本 5 的迁移脚本,因此使用版本 4 的用户更新时会崩溃;
  • 实体新增了一个列,但数据库版本没有递增,更新时也会崩溃;
  • 迁移测试名为 migrationFrom1To4,实际却运行 1→2→3→4,而使用版本 1 的用户会触发 MIGRATION_1_4,却没有任何测试覆盖它;
  • navigate("settings") 指向的路由没有在任何 NavHost 中注册;
  • 某个 ViewModel 注入了一个没有任何绑定的仓库,无论使用 Hilt 还是 Koin 都如此(使用 Koin 时会在运行时崩溃);
  • 某个库在合并后的清单中添加了 ACCESS_NETWORK_STATE;
  • 某个功能模块通过 build-logic 中的约定插件获取了 :core:data,违反了你的模块规则。

droidmap 直接读取 Android 的连接关系(Gradle、Room、Navigation、Hilt、Koin、清单;支持 Kotlin 和 Java),并将其转换为一张图。你可以在 CI 中运行其规则,在每个拉取请求上获取说明变更影响范围的评论,在修改某个类前询问哪些内容依赖它,还可以通过 MCP 将它交给 AI Agent。

它只读取源文件:绝不会运行 Gradle、编译代码或向任何地方发送数据。(GitHub Action 只会在你要求时运行一个 Gradle 任务,用于获取合并后的清单。)

终端中的 droidmap check 和 droidmap diff

在真实应用中的表现

在 Now in Android(35 个模块,使用 Hilt、Room 和 Navigation 3)上,笔记本电脑每次运行耗时约 1.5 秒:

$ droidmap check
warning room-migration-untested      core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/NiaDatabase.kt:66
        NiaDatabase: 13 of 13 migration steps have no MigrationTestHelper test (1->2, 2->3, 3->4,
        4->5, 5->6, 6->7, 7->8, 8->9, 9->10, 10->11, 11->12, 12->13, 13->14)

1 warning

对 OfflineFirstTopicsRepository.syncWith 做一行修改后(输出已缩短):

$ droidmap diff
Changed (1): OfflineFirstTopicsRepository (injectable)

Affected (what depends on the changed code):

  Screens: ForYouScreen, InterestsScreen, TopicScreen
  Navigation destinations: ForYouNavKey, InterestsNavKey, TopicNavKey
  ViewModels: ForYouViewModel, InterestsViewModel, TopicViewModel
  Dependency injection: DataModule.bindsTopicRepository, GetFollowableTopicsUseCase, SyncWorker
  Modules: :app, :app-nia-catalog, :core:data, :core:data-test, :core:domain, ... and 12 more

Tests that cover it (1): OfflineFirstTopicsRepositoryTest

$ droidmap diff --format tests
:core:data:test --tests com.google.samples.apps.nowinandroid.core.data.repository.OfflineFirstTopicsRepositoryTest

最后一行可直接传给 CI 中的 ./gradlew:只运行一个测试类,而不是整个测试套件(参见仅运行变更可能影响的测试)。

安装

macOS 或 Linux:

curl -fsSL https://raw.githubusercontent.com/hussainshirazi/droidmap/main/install.sh | sh

Windows(PowerShell):

irm https://raw.githubusercontent.com/hussainshirazi/droidmap/main/install.ps1 | iex

Homebrew:运行 brew tap hussainshirazi/droidmap https://github.com/hussainshirazi/droidmap,然后运行 brew install droidmap。Scoop:运行 scoop bucket add droidmap https://github.com/hussainshirazi/droidmap,然后运行 scoop install droidmap/droidmap。

Docker,适用于 GitHub Actions 以外的 CI 系统(GitLab、Bitbucket、Jenkins、Bitrise):运行 docker run --rm -v "$PWD":/project ghcr.io/hussainshirazi/droidmap check(从版本 0.2.0 起)。

Gradle(从版本 0.3.0 起,发布到 Gradle Plugin Portal 后可用),在根目录的 build.gradle.kts 中添加配置;然后运行 ./gradlew droidmapCheck,有错误时构建会失败:

plugins { id("io.github.hussainshirazi.droidmap") version "0.3.0" }

droidmap {
    failOn.set("warning")       // error (the default), warning, info or never
    mergedManifest.set(true)    // run each processReleaseMainManifest first: library permissions are checked too
}

或者从最新版本下载 droidmap.jar,并运行 java -jar droidmap.jar。droidmap 需要 Java 17 或更高版本。droidmap 命令会使用 JAVA_HOME、PATH 中的 java,或者 Android Studio 自带的 Java,因此安装了 Android Studio 就无需进行任何配置。该 jar 文件约为 65 MB,因为其中包含 Kotlin 编译器的解析器。

快速开始

在项目内任意位置运行:

droidmap init                     # once: the GitHub workflow and the MCP config for AI agents
droidmap check                    # run the rules; exit code 1 on errors
droidmap impact TopicEntity       # what depends on it, transitively
droidmap diff                     # what your uncommitted changes touch
droidmap diff --base main         # what your branch touches
droidmap report -o REPORT.md      # an overview, with module and screen-flow diagrams

在拉取请求中(GitHub Action)

droidmap init 会将此工作流写入 .github/workflows/droidmap.yml:

name: droidmap
on: [push, pull_request]

jobs:
  droidmap:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write   # findings as code scanning alerts
      pull-requests: write     # the comment that says what the pull request touches
    steps:
      - uses: actions/checkout@v7
      - uses: hussainshirazi/droidmap@v0

每次拉取请求中,Action 都会评论本次更改涉及的内容,并在每次推送时更新这条评论:

droidmap:本次更改涉及的内容

与 origin/main 相比:更改了 1 个文件,其中 1 个由 droidmap 读取。

已更改(1):ItemEntity room_entity

受影响(依赖于已更改代码的内容):

屏幕HomeScreen
ViewModelHomeViewModel
RoomAppDatabase、ItemDao
依赖注入DataModule.bindItems、OfflineItemRepository
模块:app、:core:data、:core:database、:feature:detail-screen、:feature:home

当更改移除某个声明(目标位置、绑定或 DAO 方法)时,评论会在 已移除 下列出该声明,并在 受影响 下列出仍依赖它的内容。

检查结果会显示为代码扫描警报、已更改行上的注释以及作业摘要;遇到错误时,此步骤会失败。Action 无需设置 Java:它使用 GitHub 运行器已有的 JDK。若要同时检查库添加到合并后清单中的权限,请设置 merge-manifest: true:它会先运行 ./gradlew processReleaseMainManifest,因此该作业需要你构建所用的 JDK(actions/setup-java)。

输入默认值
path.Android 项目的文件夹(当它不是仓库根目录时)
fail-onerror使用 warning 或 info 可提高严格程度;使用 never 仅报告问题
commenttrue使用 false 可避免在拉取请求评论中显示影响摘要(摘要仍会保留在作业摘要中)
upload-sariftrue对于没有 GitHub Advanced Security 的私有仓库,设为 false(公开仓库可免费使用代码扫描)
args传递给 droidmap check 的额外参数,例如 --config tools/droidmap.toml
baseauto用于比较的 git ref:auto 表示拉取请求的目标分支,none 表示关闭此功能;其他 ref 必须先获取(例如使用 fetch-depth: 0)
merge-manifestofftrue 会先运行 processReleaseMainManifest,也会检查库添加的权限;有产品风味时,使用对应变体的任务,例如 processProdReleaseMainManifest。此操作会运行你的构建,因此绝不要在 pull_request_target 工作流中使用
version固定的发布版本droidmap 发布版本,例如 0.2.0
仅运行更改可能影响的测试

droidmap diff --format tasks 会列出更改可能影响的每个模块的测试任务:包含有已更改文件的模块,以及所有依赖这些模块的模块(如果某个模块仅在测试中使用它们,则只会运行该模块的测试)。更改构建设置(settings、根构建文件、gradle/、build-logic)时,会列出所有模块。它仅读取 Gradle 文件,因此执行很快,也不会运行 Gradle:

      - uses: actions/checkout@v7
        with:
          fetch-depth: 0   # the base branch, to compare with
      - run: curl -fsSL https://raw.githubusercontent.com/hussainshirazi/droidmap/main/install.sh | sh
      - run: |
          tasks=$(~/.local/bin/droidmap diff --base origin/${{ github.base_ref }} --format tasks --task testDebugUnitTest)
          if [ -n "$tasks" ]; then ./gradlew $tasks; else echo "No module affected"; fi

--format modules 则会改为逐行输出模块路径。

--format tests 会进一步筛选:仅列出调用链能够到达已更改函数的测试类,并将它们作为 Gradle 参数输出( :core:data:testDebugUnitTest --tests com.example.OfflineRepositoryTest ...),因此 ./gradlew $(droidmap diff --base origin/main --format tests --task testDebugUnitTest) 只会运行这些测试。调用链会根据代码声明的内容进行追踪(属性或参数的类型、构造函数、函数的返回类型);接口上的调用会关联到所有实现,因此它倾向于运行更多测试。文本报告和 impact 会在“覆盖此项的测试”下列出相同的测试。

使用 AI Agent(MCP)

droidmap mcp 是一个通过 stdio 通信的 Model Context Protocol 服务。文件发生变化时,它会重建图(仅解析发生变化的文件),因此 Agent 编辑代码时,它的回答也会与代码保持同步。

Claude Code 插件(包含服务和一个技能,告诉 Claude 何时使用它:更改实体、路由或绑定前先分析影响,并在之后运行检查和测试)。除 Java 外,无需安装任何内容;它会找到 Android Studio 自带的 Java。首次使用时,它会下载与插件匹配的 droidmap 版本,并校验其 SHA-256。

/plugin install droidmap --marketplace hussainshirazi/droidmap

在 2.1.275 之前的 Claude Code 版本中,请先添加市场:/plugin marketplace add hussainshirazi/droidmap,然后运行 /plugin install droidmap@droidmap。该技能是一个普通的 SKILL.md 文件;其他能够读取技能的 Agent 也可以使用同一个文件。

任何 MCP 客户端。 droidmap init 会写入 .mcp.json(Claude Code),并在相应文件夹存在时写入 .cursor/mcp.json 或 .vscode/mcp.json;将这些文件提交后,团队中的每个人都会获得该服务。它绝不会修改已有文件:而是会打印需要添加的内容。手动配置:

claude mcp add droidmap -- droidmap mcp .
{ "mcpServers": { "droidmap": { "command": "droidmap", "args": ["mcp", "."] } } }

除 Java 外无需安装任何内容:"command": "java", "args": ["-jar", "/path/to/droidmap.jar", "mcp", "."]。使用 Docker:"command": "docker", "args": ["run", "--rm", "-i", "-v", "/path/to/project:/project", "ghcr.io/hussainshirazi/droidmap", "mcp"]。droidmap 也已列入 MCP Registry,名称为 io.github.hussainshirazi/droidmap(从 0.2.0 版本起)。

工具用途
droidmap_impact更改实体、路由、ViewModel、绑定或模块前,查看依赖于它的内容
droidmap_diff查看未提交的更改(或某个分支)涉及哪些内容,以及哪些测试覆盖了这些内容
droidmap_check更改后运行规则检查
droidmap_show查看单个节点:其属性、依赖项和使用方
droidmap_find按名称搜索节点
droidmap_report获取项目的 Markdown 概览
droidmap_explain了解规则的重要性,以及如何修复发现的问题

droidmap 能理解什么

领域可读取的内容
Gradle来自 settings.gradle(.kts) 的模块;Kotlin DSL 和 Groovy 中的 project(":x") 和类型安全的 projects.x.y 依赖;版本目录中的插件;包含式构建(build-logic)中的约定插件为每个应用这些插件的模块添加的项目依赖;以及 Kotlin Multiplatform 源集(commonTest 中的依赖属于测试依赖)。
Room数据库、实体、DAO(每个 @Query 使用的表,以及每个 @Insert、@Update、@Delete 或 @Upsert 写入的实体,包括继承的内容)、手动和自动迁移、导出的架构 JSON、每个实体拥有的列与该 JSON 中的列的对比,以及哪个 MigrationTestHelper 测试覆盖哪个迁移步骤。
Navigation使用字符串路由(由常量、模板和辅助函数构建)或类型安全路由的 Compose Navigation、Navigation 3(entry<Key>,以及在对键执行的 when 中使用的 NavEntry(key))、XML 导航图(跨文件,包含深层链接、菜单项和只会弹回的操作),以及 Kotlin 和 Java 中的 navigate(...) 调用:使用路由、R.id 操作、Safe Args 方向(HomeFragmentDirections.actionHomeToDetail(id)),以及通过你自己的辅助函数(例如 navigateToTab(route)),最多深入五层。
Hilt@HiltViewModel、@Inject 构造函数和字段、带限定符的 @Provides 和 @Binds、入口点,以及哪些屏幕、Activity 和 Fragment 使用了哪些 ViewModel。
Koin模块和定义(single、factory、viewModel、singleOf(::X)、bind)、Koin Annotations(支持 JSR-330;带注解的类仅在模块的 @ComponentScan 覆盖的包中才会被计入),以及每个定义通过 get() 获取的内容;koinViewModel、koinInject 和 by inject() 的用法。
Manifest源清单中的权限(包括 tools:node="remove")和组件;如果构建已生成合并清单,还会读取其中的权限及添加每项权限的库。

Kotlin 和 Java 使用 Kotlin 编译器自己的解析器(detekt 和 ktlint 使用的解析器)进行读取,并通过多个线程处理。

命令

droidmap init   [path]                               # write the GitHub workflow and MCP config (never overwrites)
droidmap check  [path] [--format text|json|sarif] [--fail-on error|warning|info|never] [--sarif FILE] [--base REF]
droidmap impact <node> [path] [--depth N] [--json]   # what depends on a node
droidmap diff   [path] [--base REF] [--depth N] [--format text|markdown|json|modules|tasks|tests] [--task NAME]   # what a change touches
droidmap show   <node> [path] [--json]               # a node, its dependencies and its users
droidmap find   <text> [path] [--limit N] [--json]   # search nodes
droidmap report [path] [-o FILE]                     # the Markdown report, with Mermaid diagrams
droidmap build  [path] [-o DIR] [--node-link]        # write droidmap-out/graph.json and REPORT.md
droidmap rules                                       # list the rules and their default severities
droidmap explain <rule>                              # why a rule matters and how to fix what it finds
droidmap mcp    [path]                               # run the MCP server over stdio

如果未提供 [path],droidmap 会处理当前文件夹周围的 Gradle 项目(当前文件夹及其上级目录中最近的 settings.gradle(.kts))。终端中的输出会带颜色;设置 NO_COLOR=1 可关闭颜色,设置 FORCE_COLOR=1 可开启颜色。

<node> 可以是 ID、名称或名称的一部分:TopicEntity、:core:data、"editor?id={id}"、HomeViewModel。check、impact、show、find 和 report 也接受 --config FILE 和 --graph FILE(使用已保存的 graph.json,而不进行扫描);build、diff 和 mcp 接受 --config FILE。

diff 会将工作树(无论是否已提交,包括新文件)与分支从 --base(默认为 HEAD:未提交的更改)分出时的状态进行比较。更改的行会计入其所在的最内层声明,因此编辑一个函数不会将其整个类都标记为有更改。

check --sarif FILE 还会将检查结果写入 SARIF。check --base REF 还会将 Room 架构文件与该提交进行比较;最后一个发布标签是最精确的选择,因为只有已发布的架构必须保持不变。如果使用 build,请将 droidmap-out/ 添加到 .gitignore。

退出代码:完成时为 0;check 发现达到或高于 --fail-on 设定级别的问题(或 find 未匹配到任何内容)时为 1;用法或配置错误时为 2;内部错误时为 3。

规则

规则默认级别检查内容
room-migration-patherror某个已知架构版本没有迁移到当前版本的路径
room-schema-missingwarning当前版本或更早版本没有导出的架构 JSON
room-schema-stalewarning实体与当前版本的架构 JSON 不再匹配:实体变更但未提升版本,更新时会崩溃
room-schema-changedwarning使用 --base REF 时,REF 中已存在版本的架构 JSON 发生了变化;该版本发布后也会导致同样的崩溃
room-migration-untestedwarning迁移步骤没有 MigrationTestHelper 测试运行
room-migration-unusedwarning应用代码从未使用的手动 Migration(未传给 addMigrations?),即使有测试运行它
room-destructive-fallbackwarningfallbackToDestructiveMigration(),它会删除用户数据
room-insert-replaceoff@Insert(onConflict = REPLACE),它会删除并重新插入行
nav-unknown-targeterrornavigate() 跳转到没有任何目标注册的路由或键
nav-unreachablewarning没有任何导航、启动位置或引用指向的目标,且没有深层链接或菜单项可以打开它
nav-deeplink-not-in-manifestwarning某个深层链接(navDeepLink、<deepLink>)的 scheme 和 host 没有任何 activity 的 intent filter 接受,因此该链接无法打开应用
hilt-missing-bindingwarning被注入的项目类型没有 @Inject 构造函数、@Provides 或 @Binds
hilt-duplicate-bindingerror同一 source set 中,同一类型和限定符被绑定了两次
hilt-app-not-registerederror存在 @AndroidEntryPoint 类,但清单中的 <application android:name> 不是 @HiltAndroidApp 类(或不存在该类):这些类打开时会崩溃
hilt-fragment-hosterror@AndroidEntryPoint fragment 由非 @AndroidEntryPoint activity 承载(通过其导航图、布局或 fragment 事务):fragment 附加时会崩溃
workmanager-setupwarning某个 @HiltWorker 没有获得 HiltWorkerFactory,或者 WorkManager 从未请求某个 Configuration.Provider,因为清单中仍有默认初始化器
koin-missing-definitionwarningKoin 被要求提供的类型(get()、inject()、koinInject())没有对应的 Koin 定义:运行时会抛出 NoDefinitionFoundException
gradle-unknown-projecterror某个项目依赖了 settings 未包含的模块
gradle-forbidden-dependencyerror[[gradle.forbid]] 规则禁止的依赖
gradle-cycleerror彼此形成循环依赖的模块
manifest-permissionerror不在 allowed_permissions 中的权限,无论它位于源清单中还是由库添加到合并后的清单中
gson-model-not-keptwarningGson 通过反射读取的类(fromJson、TypeToken、使用 GsonConverterFactory 的 Retrofit API),其字段在压缩发布构建中被 R8 重命名:没有 @SerializedName、@Keep 或 keep 规则。调试构建正常,发布构建读取到的字段为空

配置

在项目根目录放置 .droidmap.toml(所有内容均为可选项):

[rules]
"room-migration-untested" = "error"   # error | warning | info | off
"room-insert-replace" = "error"

[manifest]
allowed_permissions = ["android.permission.INTERNET", "android.permission.POST_NOTIFICATIONS"]

[[gradle.forbid]]
from = ":feature:*"
to = ":feature:*"
reason = "features talk to each other through :core modules"

[ignore]
paths = ["benchmarks/**"]

未知的规则名称或严重级别会导致错误,因此拼写错误不会让规则悄然保持不变。from、to 和 ignore.paths 是 glob 模式,其中 * 也会匹配 : 和 /。禁止规则会跳过测试依赖,除非设置了 tests = true;allow_same_parent = true 则允许同级模块相互依赖,例如 :feature:a:impl 和 :feature:a:api。使用 allowed_permissions 时,如果某个库向合并后的清单添加权限,报告中会标明该库的名称,你可以移除该权限(tools:node="remove")或允许它。

图

droidmap build 会写入 droidmap-out/graph.json(架构为 droidmap/1),其中包含三个列表:

  • nodes:id、kind、label、file、line、module 和 attrs;
  • edges:source、target、kind、file、line 和 attrs;
  • unresolved:静态分析无法追踪的内容及其原因。

边始终表示源依赖于目标,因此影响分析会反向遍历。路径相对于项目根目录,输出具有确定性,因此可以清晰地比较差异。--node-link 还会写入 networkx 的 node-link 格式,graphify 等工具可以读取这种格式。

节点类型:module、room_database、room_entity、room_dao、room_migration、room_schema、test、nav_destination、nav_route、screen、function、viewmodel、injectable、hilt_module、hilt_provider、koin_module、koin_definition、entry_point、component、permission、class。

边类型:depends_on、contains_entity、contains_dao、queries、writes、has_schema、migrates、tests、registers、shows、navigates_to、starts_at、uses_viewmodel、injects、bound_by、requires、declared_in、uses_permission、declares。

限制

droidmap 读取源代码,不会运行 Gradle。这让它只需几秒就能完成分析,也能处理从未构建过的检出版本,并且永远不会运行项目的构建脚本;但这也意味着它无法获取只有构建过程才能生成的内容。此外,它有意采取保守策略:遇到无法解析的内容(存储在变量中的路由、非常量的版本、无法判断类型的 Koin 定义)时,会将其记录在 unresolved 中,而不是猜测,规则也不会对此触发。依赖猜测的规则会报告并不存在的问题。

  • 不读取构建生成的代码(KSP、kapt)。Safe Args 不需要这类代码:HomeFragmentDirections.actionHomeToDetail() 会根据 Safe Args 为其生成的代码所用的名称,与对应操作匹配。
  • 库添加的权限来自已解析的依赖,因此只有构建生成了合并清单时才会读取(<module>/build/intermediates/merged_manifests/,或 Action 的 merge-manifest 输入)。如果合并清单早于构建文件,会对此作出标记。
  • 名称按照 Kotlin 和 Java 的规则解析(导入、包、封闭类及其超类),但不进行类型推断,因为类型推断需要编译器和所有依赖。对变量的调用(例如 repo.load())会在变量类型已声明,或来自构造函数或函数的已声明返回类型时进行追踪;如果需要推断类型,则不会追踪(例如 lambda 中的 it、泛型的类型实参、连续调用库函数)。
  • 不会追踪运行时构造的值:从 var 读取的路由,或在循环中构造的路由。常量、字符串模板、返回这些值的函数,以及最多五层深的辅助函数都会追踪。

贡献

运行 ./gradlew test fatJar 可执行测试并构建 build/libs/droidmap.jar。CONTRIBUTING.md 介绍了代码所在位置、新规则或修复的要求,以及 CI 中针对真实项目的检查。如果发现错误或遗漏,请使用 False positive or missed problem 问题表单提交;安全问题请按照 SECURITY.md 的说明私下报告。

许可证

Apache-2.0。请参阅 LICENSE。

评论

支持 Markdown

第一次接触 MCP?

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

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