feat: 新增 registering-services / using-services 两个元技能 (#17)

## 概要

为 Skill-first "应用与服务目录" 路线图新增两个元技能,作为 builtin 同步到用户
~/.desirecore/skills/:

- registering-services — 教 Agent 安全注册外部 HTTP/MCP 服务(riskLevel
选择、secretRef、SSRF、人类审批)
- using-services — 教 Agent 发现并调用目录服务(status/reviewStatus 过滤、HTTP/MCP
两条路径、不绕开治理)

## 改动

- skills/registering-services/{SKILL.md, SKILL.zh-CN.md}
- skills/using-services/{SKILL.md, SKILL.zh-CN.md}
- builtin-skills.json 加 2 个技能 id
- manifest.json totalSkills 22 → 24(已 rebase 到含 #18 tech-diagram 的 main)
- SKILL.zh-CN.md 采用 `<!-- locale: zh-CN -->` body 格式(移除冗余 frontmatter,对齐
docs/I18N.md 约定)

## 配套

主仓库 PR 同步引入:schema 扩展、新存储层、注册/治理路由、service-approval、http-request
治理闸门、per-service Skill 生成器、docker-app 派生 + service-health、前端徽章/治理
UI/派生服务面板。

## CLA

- [x] I have read and agree to the Contributor License Agreement
This commit is contained in:
2026-05-31 15:43:16 +08:00
committed by GitHub
parent f4dfe8e32f
commit e9862ef1ab
6 changed files with 643 additions and 2 deletions

View File

@@ -17,10 +17,12 @@
"pdf",
"pptx",
"python-runtime",
"registering-services",
"s3-storage-operations",
"skill-creator",
"tech-diagram",
"update-agent",
"using-services",
"web-access",
"workflow",
"xiaomi-tts",

View File

@@ -27,8 +27,8 @@
},
"stats": {
"totalAgents": 1,
"totalSkills": 22,
"lastUpdated": "2026-05-30"
"totalSkills": 24,
"lastUpdated": "2026-05-31"
},
"features": [
"verified-only",

View File

@@ -0,0 +1,198 @@
---
name: registering-services
description: >-
Register an external HTTP/MCP service to the global Application & Service
Catalog so that other Agents (and the human user) can discover and call it.
Use when the user mentions adding a new service, registering an API, or
publishing a backend you control to the team's catalog. 新增服务、注册 API、把后端发布到团队目录时使用。
version: 1.0.0
type: meta
risk_level: medium
status: enabled
disable-model-invocation: true
tags:
- service
- registration
- catalog
- meta
metadata:
author: desirecore
updated_at: '2026-05-28'
i18n:
default_locale: en-US
source_locale: zh-CN
locales:
- zh-CN
- en-US
zh-CN:
name: 注册服务
short_desc: 把外部 HTTP/MCP 服务登记到全局应用与服务目录
description: >-
把外部 HTTP/MCP 服务登记到全局应用与服务目录,让其他 Agent 与人类用户都能发现并调用。
Use when 用户提到要新增服务、注册 API 或把你掌握的后端发布到团队目录。
body: ./SKILL.zh-CN.md
translated_by: human
en-US:
name: Register Services
short_desc: Register external HTTP/MCP services into the global catalog
description: >-
Register an external HTTP/MCP service to the global Application & Service Catalog so that other Agents (and the human user) can discover and call it. Use when the user mentions adding a new service, registering an API, or publishing a backend you control to the team's catalog.
body: ./SKILL.md
translated_by: human
market:
category: productivity
maintainer:
name: DesireCore Official
verified: true
channel: latest
---
# registering-services Skill
## L0: One-line Summary
Tell the Agent how to register an external HTTP/MCP service into the global
catalog with human governance.
## L1: Overview
This meta-skill teaches you (the Agent) **how** to add a new service to the
shared "Apps & Services" catalog. It does **not** invoke external services
itself — once registered, use the per-service Skill at `~/.desirecore/skills/svc-<id>/`
or the `using-services` meta-skill to actually call them.
### When to use this skill
- The user asks to register an API or service (e.g. "add the Acme search
endpoint to our catalog")
- You just deployed a backend you want other Agents to be able to reach
- You discover a useful public API and want to make it shareable
### When NOT to use it
- One-off HTTP calls that nobody else needs to repeat — just call `HttpRequest`
directly
- Adding an MCP server to **your own** agent only — POST to
`/api/agents/<your-id>/mcp-servers` instead, no catalog entry needed
- Modifying an existing catalog entry — the user must promote/reject through
the Store UI
## L2: How to register a service
### Step 1 — Gather the required fields
Before calling the API, collect:
| Field | Required | Notes |
|------|----------|-------|
| `id` | yes | kebab-case, globally unique (e.g. `acme-search-api`) |
| `name` | yes | Human-readable display name |
| `description` | yes | One sentence — what it does |
| `protocol` | yes | `'http'` or `'mcp'` |
| `tags` | yes | Searchable list, e.g. `['search', 'web']` |
| `registeredBy` | yes | **Your own agent id** |
| `endpoint` | **required when `protocol='http'`** | Base URL of the API |
| `riskLevel` | recommended | `low` / `medium` / `high` / `critical` |
| `auth` | when API requires auth | `{ type, secretRef }` — see below |
| `operations` | recommended | Operations list for the per-service Skill |
| `capabilities` | optional | What this service can do |
### Step 2 — Choose `riskLevel` honestly
`riskLevel` decides whether **future calls** to this service trigger an
approval gate:
- `low` — read-only data that's safe to invoke without prompting (e.g. public weather API)
- `medium` — default for most APIs; first invoke per session may prompt
- `high` — services that move money, send emails, mutate user data
- `critical` — services that can cause irreversible harm; **every** call requires human approval
**Bias toward higher risk if uncertain.** Under-classifying is worse than over-classifying.
### Step 3 — Reference credentials with `secretRef`, never inline
**Do not put API keys/tokens into the catalog entry.** The catalog is global,
visible in the Store UI, and may be inspected by future people. Instead:
```jsonc
{
"auth": {
"type": "bearer",
"secretRef": "secrets/api-keys/acme" // pointer, not the actual key
}
}
```
`HttpRequest` resolves `secretRef` at call time. If the secret doesn't exist
yet, ask the user to provide it through their own secrets manager — not
through the chat.
### Step 4 — Call the registration API
Use the `HttpRequest` tool to POST to the local Agent Service:
```yaml
tool: HttpRequest
parameters:
url: http://127.0.0.1:<agent-service-port>/api/registry/agent-services
method: POST
body:
id: acme-search-api
name: "Acme Search API"
description: "Full-text search across Acme's product catalog"
protocol: http
endpoint: https://api.acme.example/search
tags: ["search", "products"]
registeredBy: "<your-agent-id>"
riskLevel: medium
auth:
type: bearer
secretRef: secrets/api-keys/acme
operations:
- name: search
method: GET
path: /v1/items
description: "Search products by query string"
- name: getDetail
method: GET
path: /v1/items/{id}
description: "Get a single product by id"
```
(The Agent Service port is available via the runtime; ask `request-help`
skill if you're unsure how to discover it.)
### Step 5 — Wait for human approval
The endpoint triggers a **service-approval** request to the human user. The
HTTP call will block until they approve or reject through the Store UI.
- **Approved** → entry is written to
`~/.desirecore/config/registry-agent-services.json` with
`reviewStatus: 'pending'`. While `pending`, **only you** (the registering
Agent) can call it.
- **Rejected** → the API returns 403; do not retry, ask the user how to
proceed.
After approval, the human can later **promote** the entry from `pending` to
`approved` in the Store UI. Promotion makes the service callable by all
Agents and triggers `service-skill-generator` to write
`~/.desirecore/skills/svc-<id>/SKILL.md` with concrete examples derived
from the `operations` you provided.
### Step 6 — Don't re-register
If `POST /api/registry/agent-services` returns `409 Conflict`, the service
already exists. Do not try alternative ids unless the user explicitly asks
you to register a separate variant — silently shadowing an existing service
is a governance failure.
## Failure modes
- **Endpoint blocked by SSRF guard**: the registration API rejects private
network addresses (10.x / 172.16-31.x / 192.168.x / 169.254.x) except
localhost. Public IPs and DNS names are allowed.
- **Missing `registeredBy`**: the API returns 400. Always include your own
agent id — there is no anonymous registration.
- **Approval timeout**: the broker times out after the user is idle long
enough; treat this as `rejected`.

View File

@@ -0,0 +1,135 @@
<!-- locale: zh-CN -->
# 注册服务registering-services
## L0一句话总结
教会 Agent 如何把一个外部 HTTP/MCP 服务安全地登记进全局"应用与服务目录"
并经人类用户审批。
## L1概述
这是一个 **元技能**告诉你Agent**怎么** 把新服务添加进共享的"应用与服务目录"。
本技能 **不负责** 调用外部服务 —— 注册成功且 approved 后,请用
`~/.desirecore/skills/svc-<id>/` 下的专属 Skill 或者 `using-services`
元技能来真正发起调用。
### 何时使用
- 用户让你"把 X API 注册到目录里"
- 你刚部署了某个后端,希望别的 Agent 能用到
- 你发现一个有用的公共 API想让团队共享
### 何时不要用
- 一次性 HTTP 调用,别人不需要重复用 —— 直接 `HttpRequest` 即可
- 只给 **自己这个 Agent** 加 MCP 服务 —— 直接 POST `/api/agents/<your-id>/mcp-servers`,无需目录条目
- 修改已有目录条目 —— 必须由用户在 Store UI 提升 / 拒绝
## L2如何注册一个服务
### Step 1 — 收集必填字段
调用 API 前,先准备好:
| 字段 | 必填 | 说明 |
|------|------|------|
| `id` | 是 | kebab-case全局唯一`acme-search-api` |
| `name` | 是 | 显示名 |
| `description` | 是 | 一句话功能描述 |
| `protocol` | 是 | `'http'``'mcp'` |
| `tags` | 是 | 搜索标签数组,如 `['search', 'web']` |
| `registeredBy` | 是 | **你自己的 agent id** |
| `endpoint` | `protocol='http'` 时必填 | API 基础 URL |
| `riskLevel` | 推荐 | `low` / `medium` / `high` / `critical` |
| `auth` | 服务需要鉴权时 | `{ type, secretRef }` — 见下文 |
| `operations` | 推荐 | 操作清单,会被 per-service Skill 渲染成示例 |
| `capabilities` | 可选 | 能力标签 |
### Step 2 — 诚实地选择 `riskLevel`
`riskLevel` 决定 **未来调用** 该服务时是否触发审批闸门:
- `low` — 只读数据,调用无副作用(如公开天气 API
- `medium` — 大多数 API 的默认值;同一会话首次调用可能提示
- `high` — 涉及金钱、发邮件、修改用户数据等的服务
- `critical` — 可能造成不可逆后果;**每次** 调用都要人类审批
**不确定时倾向于更高风险等级。** 低估比高估更糟。
### Step 3 — 用 `secretRef` 引用凭据,**不要** 内联明文
**不要把 API key/token 写进目录条目。** 目录是全局可见的Store UI 任何人都能查看,
未来还可能被审计。正确做法:
```jsonc
{
"auth": {
"type": "bearer",
"secretRef": "secrets/api-keys/acme" // 指针,不是真正的 key
}
}
```
`HttpRequest` 在调用时解引用 `secretRef`。如果对应 secret 还不存在,请通过
用户自己的凭据管理流程让他/她填好 —— 不要在聊天里收明文。
### Step 4 — 调用注册 API
`HttpRequest` 工具 POST 到本机 Agent Service
```yaml
tool: HttpRequest
parameters:
url: http://127.0.0.1:<agent-service-port>/api/registry/agent-services
method: POST
body:
id: acme-search-api
name: "Acme 搜索 API"
description: "对 Acme 产品库做全文搜索"
protocol: http
endpoint: https://api.acme.example/search
tags: ["search", "products"]
registeredBy: "<your-agent-id>"
riskLevel: medium
auth:
type: bearer
secretRef: secrets/api-keys/acme
operations:
- name: search
method: GET
path: /v1/items
description: "按 query 搜索商品"
- name: getDetail
method: GET
path: /v1/items/{id}
description: "按 id 获取单个商品"
```
Agent Service 端口由运行时提供;不确定如何获取时用 `request-help` 技能问。)
### Step 5 — 等待人类批准
该端点会触发 **service-approval** 请求HTTP 调用会一直阻塞,直到用户在
Store UI 批准或拒绝。
- **批准** → 条目写入 `~/.desirecore/config/registry-agent-services.json`
状态为 `reviewStatus: 'pending'`。pending 期间 **只有你**(注册者)能调用。
- **拒绝** → API 返回 403不要重试问用户下一步怎么办。
人类后续可以在 Store UI 把条目从 `pending` **提升promote**`approved`
提升后该服务对所有 Agent 可调,且会触发 `service-skill-generator` 生成
`~/.desirecore/skills/svc-<id>/SKILL.md`,里头有基于 `operations` 的示例。
### Step 6 — 不要重复注册
如果 `POST /api/registry/agent-services` 返回 `409 Conflict`,说明该服务已存在。
除非用户明确要求注册一个独立变种,否则不要换个 id 静默注册 —— 偷偷影子覆盖
已有服务是治理失败。
## 常见失败模式
- **SSRF 拦截**:注册 API 拒绝私网地址10.x / 172.16-31.x / 192.168.x / 169.254.x
本机环回除外。公网 IP 与 DNS 域名允许。
- **缺 `registeredBy`**API 返回 400。必须带上你自己的 agent id —— 不允许匿名注册。
- **审批超时**broker 等待用户太久会超时,按 `rejected` 处理。

View File

@@ -0,0 +1,184 @@
---
name: using-services
description: >-
Discover and invoke HTTP/MCP services already registered in the global
catalog. Use when the user asks the Agent to call an API, search for an
existing service, or query a backend that was registered via the
registering-services skill. 调用某个 API、查找已有服务、访问已注册的后端服务时使用。
version: 1.0.0
type: meta
risk_level: low
status: enabled
disable-model-invocation: true
tags:
- service
- catalog
- http
- mcp
- meta
metadata:
author: desirecore
updated_at: '2026-05-28'
i18n:
default_locale: en-US
source_locale: zh-CN
locales:
- zh-CN
- en-US
zh-CN:
name: 使用服务
short_desc: 发现并调用全局目录中已注册的服务
description: >-
发现并调用全局应用与服务目录中已经注册过的 HTTP/MCP 服务。Use when 用户让 Agent 调用某个 API、查找已有服务或访问通过 registering-services 注册的后端。
body: ./SKILL.zh-CN.md
translated_by: human
en-US:
name: Using Services
short_desc: Discover and invoke registered services from the catalog
description: >-
Discover and invoke HTTP/MCP services already registered in the global catalog. Use when the user asks the Agent to call an API, search for an existing service, or query a backend that was registered via the registering-services skill.
body: ./SKILL.md
translated_by: human
market:
category: productivity
maintainer:
name: DesireCore Official
verified: true
channel: latest
---
# using-services Skill
## L0: One-line Summary
Teach the Agent how to look up and call services from the global
"Apps & Services" catalog.
## L1: Overview
The "Apps & Services" catalog stores all registered HTTP/MCP services. This
meta-skill is your manual for **finding** the right service and **invoking**
it correctly. Each non-trivial registered service also has its own
`svc-<id>` Skill auto-generated from its operations — prefer that when it
exists; this meta-skill is the fallback for ad-hoc discovery.
### When to use
- The user asks you to do something that might already have a registered service
(e.g. "search the product database", "send a notification")
- You see `svc-<id>` Skill in your available skills list — activate it directly
- You need to call an HTTP API and want to check if it's been registered (to
benefit from governance, credential injection, and audit)
### When NOT to use
- The user gives you a one-off URL to fetch — use `HttpRequest` directly
- You already have the per-service `svc-<id>` Skill activated — follow it
instead, it's more specific
## L2: How to discover and call
### Step 1 — List the catalog
Fetch the registry:
```yaml
tool: HttpRequest
parameters:
url: http://127.0.0.1:<agent-service-port>/api/registry/services
method: GET
```
Response shape (excerpt):
```json
{
"data": {
"services": [
{
"id": "acme-search-api",
"name": "Acme Search API",
"protocol": "http",
"endpoint": "https://api.acme.example/search",
"tags": ["search", "products"],
"status": "online",
"origin": "agent",
"reviewStatus": "approved",
"riskLevel": "medium",
"operations": [...]
}
],
"total": 1,
"source": "official"
}
}
```
### Step 2 — Pick a candidate
Filter by `tags`, `protocol`, `name`. Important checks **before** invoking:
- `status === 'online'` → safe to call
- `status === 'offline'` / `'error'` → tell the user the service is down,
**don't** retry blindly
- `status === 'degraded'` → degrade gracefully, mention it to the user
- `reviewStatus === 'pending'` (only `origin='agent'`) → you can only call it
if `registeredBy` is you; otherwise ask the user to promote it
- `riskLevel === 'critical'` → expect a human approval prompt on every call
### Step 3 — Build the request (HTTP services)
If the service has `operations`, prefer them — they define stable contract
points. Build the URL as `<endpoint><operation.path>`.
```yaml
tool: HttpRequest
parameters:
url: https://api.acme.example/search/v1/items?q=widget
method: GET
headers:
Authorization: "Bearer ${secrets/api-keys/acme}" # resolved at call time
```
If the service declares `auth.secretRef`, **do not** prompt the user for the
secret value during chat. The `HttpRequest` tool resolves it automatically.
### Step 4 — Build the request (MCP services)
MCP services aren't called via `HttpRequest` — they are tools exposed through
the Agent's `mcp_servers` config:
1. If you haven't added the MCP service to your Agent yet, POST to
`http://127.0.0.1:<agent-service-port>/api/agents/<your-id>/mcp-servers` with `{ serverId: '<service.id>', config: <connection> }`
2. Next query, MCP discovery will auto-expose tools from the server
3. Call the tools by their MCP-declared names directly
### Step 5 — Handle the response
- 2xx → parse and use
- 4xx → likely a client error (bad params, missing auth); fix the request,
then retry once; if it still fails, report to the user
- 5xx → server-side problem, report `status` of the service back so the user
knows
- **Service-approval rejected** → response will contain `"decision":"rejected"`;
do not retry, ask the user how to proceed
### Step 6 — Don't fall through to raw URL fetching
If the catalog has a service for what you need, **use it**. Don't bypass
governance by calling an undeclared URL directly — `HttpRequest` will still
work, but you lose:
- Approval gating for high-risk operations
- Credential injection (you'd need to ask the user for keys yourself)
- Audit trail and `lastUsed` tracking
- Other Agents being able to reuse your call patterns
## Failure modes
- **Catalog read fails**: just fall back to direct `HttpRequest` and warn the
user that governance is bypassed.
- **No matching service**: ask the user if they want to **register** a new
one — activate `registering-services` skill.
- **`status === 'offline'`**: report the situation, suggest checking the
underlying service, **don't** call anyway.

View File

@@ -0,0 +1,122 @@
<!-- locale: zh-CN -->
# 使用服务using-services
## L0一句话总结
教会 Agent 如何从全局"应用与服务目录"里查找并正确调用服务。
## L1概述
"应用与服务目录"存放所有已注册的 HTTP/MCP 服务。本元技能是你的 **使用手册**
帮你 **找到** 合适的服务、**正确** 调用它。每个非平凡的注册服务还会自动生成
专属 `svc-<id>` Skill —— 优先用专属技能;本元技能是临时发现的兜底。
### 何时使用
- 用户让你做某件事,目录里可能有现成服务(如"搜产品库"、"发通知"
- 你看到可用技能里有 `svc-<id>` —— 直接激活它
- 你打算调用一个 HTTP API想先查目录借力治理、凭据注入和审计
### 何时不要用
- 用户给了一次性 URL —— 直接 `HttpRequest` 即可
- 你已激活了 `svc-<id>` 专属 Skill —— 跟着它走,更具体
## L2如何发现并调用
### Step 1 — 列目录
拉取注册表:
```yaml
tool: HttpRequest
parameters:
url: http://127.0.0.1:<agent-service-port>/api/registry/services
method: GET
```
响应(节选):
```json
{
"data": {
"services": [
{
"id": "acme-search-api",
"name": "Acme 搜索 API",
"protocol": "http",
"endpoint": "https://api.acme.example/search",
"tags": ["search", "products"],
"status": "online",
"origin": "agent",
"reviewStatus": "approved",
"riskLevel": "medium",
"operations": [...]
}
],
"total": 1,
"source": "official"
}
}
```
### Step 2 — 选候选
`tags``protocol``name` 过滤。调用 **前** 必须检查:
- `status === 'online'` → 可以调
- `status === 'offline'` / `'error'` → 告诉用户服务下线,**别** 盲目重试
- `status === 'degraded'` → 降级使用,告诉用户
- `reviewStatus === 'pending'`(仅 `origin='agent'`)→ 只有当 `registeredBy`
就是你自己时才能调;否则请用户在 Store UI 提升后再用
- `riskLevel === 'critical'` → 每次调用都会触发人类审批
### Step 3 — 构造请求HTTP 服务)
如果服务声明了 `operations`,优先用 —— 它们是稳定契约点。URL 拼成
`<endpoint><operation.path>`
```yaml
tool: HttpRequest
parameters:
url: https://api.acme.example/search/v1/items?q=widget
method: GET
headers:
Authorization: "Bearer ${secrets/api-keys/acme}" # 调用时解引用
```
如果服务带 `auth.secretRef`**不要** 在聊天里向用户索要明文凭据。
`HttpRequest` 工具会自动解引用。
### Step 4 — 构造请求MCP 服务)
MCP 服务不走 `HttpRequest`,它通过 Agent 的 `mcp_servers` 配置暴露成工具:
1. 若你的 Agent 还没接入该 MCPPOST `http://127.0.0.1:<agent-service-port>/api/agents/<your-id>/mcp-servers`
`{ serverId: '<service.id>', config: <connection> }`
2. 下轮 query 时 MCP discovery 会自动暴露该服务器的工具
3. 直接用 MCP 工具名调用即可
### Step 5 — 处理响应
- 2xx → 解析使用
- 4xx → 多半是客户端错(参数错、缺鉴权);修好后可重试一次,仍失败就告诉用户
- 5xx → 服务端问题,把目录里的 `status` 一并告诉用户
- **service-approval 拒绝** → 响应里会有 `"decision":"rejected"`;不要重试,问用户怎么办
### Step 6 — 不要绕开目录
如果目录里有该服务,**就用目录**。直接调未声明的 URL 虽然 `HttpRequest`
也能成功,但你会失去:
- 高风险操作的审批闸门
- 凭据注入(你得自己问用户要 key
- 审计追踪与 `lastUsed` 统计
- 其他 Agent 复用你的调用模式
## 常见失败模式
- **目录读取失败**:降级直接 `HttpRequest`,并告诉用户已绕开治理。
- **没匹配上**:问用户要不要 **注册** 一个新的 —— 激活 `registering-services`
- **`status === 'offline'`**:汇报情况,建议检查底层服务,**不要** 强行调用。