Advanced: Custom MCP and Skills
Attach your own capabilities to an agent — register a running MCP server into Bailian (custom MCP, including per-session auth headers), and package the team's workflow for upload (Skills) — including runtime mechanics and versioning.
Overview
The built-in toolkit (getting started) and marketplace MCP services (production) are "platform-given" capabilities. This page goes the other way — attaching what you already have: register your running MCP server into Bailian (custom MCP), and package your team's workflow for upload (Skills).
Scenario: Luming Press's review assistant
"Luming Press" is a fictional book publisher whose content-ops team processes a large volume of submissions monthly. They hold two ready-made assets:
- An editorial-standards knowledge base — long since built on Bailian knowledge indexing (RAG), exposing a standard MCP endpoint. The pain: only application-side code could query it; a Managed Agent could not.
- A manuscript health-check workflow — a senior editor's "three looks": heading hierarchy, paragraph length, absolutist wording. Previously a manual checklist; now wanted as automatic agent behavior.
The ask is clear: the knowledge base's MCP endpoint is ready — let the agent query it; the check workflow is ready — let the agent follow it. These map exactly onto this page's two topics: custom MCP (register an existing service) and custom Skills (upload your own workflow).
The capability map first — an agent's external capabilities come in three tiers:
| Tier | Source | Maintained by | Where covered |
|---|---|---|---|
Built-in toolkit builtin_toolkit | Platform (bash / file I/O / web_search etc.) | Bailian | Getting started |
Marketplace MCP type: official | MCP marketplace services | Bailian marketplace | Production, multi-agent |
Your own assets type: customer | Your MCP server, your Skill packages | You | This page |
Custom MCP: register an existing MCP server into Bailian
Marketplace and custom
Bailian's MCP management has two tabs: marketplace (Bailian-hosted, ready to use — WebSearch lives here) and custom (your own MCP servers or third-party services). Custom supports four registration types: plugin, script deployment, AI gateway, and Alibaba Cloud OpenAPI.
This page's focus: the MCP server is already running (Luming's knowledge-base RAG endpoint) and only needs "one registration entry in Bailian". Choose script deployment with the HTTP deployment form (installation mode http) — note this step only registers (tells Bailian the endpoint address and auth method); it deploys nothing and incurs no deployment fee.
Open Managed Agent → MCP, switch to the custom tab, and click 创建MCP服务 (Create MCP Service) in the top right. In the type selector, choose 脚本部署 (script deployment).
NoteAfter choosing script deployment, the form's installation mode offers npx / uvx / http. npx / uvx genuinely deploy the service to Function Compute and bill by call duration and count; http is pure registration — it registers an existing endpoint only; the deployment-mode and region fields collapse, and no deployment fee accrues.
Select http as the installation mode.
Example: register a Bailian RAG knowledge base MCP endpoint
Luming's editorial-standards knowledge base is built on Bailian knowledge indexing; its MCP endpoint is a fixed workspace-level address. Fill in the service name and description, and register the connection config in the MCP服务配置 (MCP service config) JSON:
{
"mcpServers": {
"rag_mcp": {
"type": "streamableHttp",
"url": "https://${workspaceId}.cn-beijing.maas.aliyuncs.com/api/v1/indices/rag/mcp",
"headers": {
"Authorization": "Bearer ${DASHSCOPE_API_KEY}"
}
}
}
}
Reading the fields:
typetakesstreamableHttp(the MCP standard HTTP transport), a single URL, stateless calls;sseis also supported;${workspaceId}inurlis your Bailian workspace ID (visible top-right in the console, shaped likellm-xxxx);headerscarries auth — the RAG endpoint uses a plain API Key (Bearer). Note: the key entered at registration is stored in Bailian's MCP configuration and attached by the platform when the agent calls it — no need to keep another copy in the agent's system prompt or a vault.
NoteThis example also dispels a common misconception: "custom MCP" does not require writing an MCP server from scratch. Any already-running service exposing an MCP endpoint — your business system's API gateway, a third-party SaaS MCP interface, even another cloud product (like Bailian knowledge indexing) — can be registered this way. Bailian does "registration + proxied calls", not "hosted runtime".
After submitting, the new service appears in the custom list. The detail page shows the service's overview, tools, and external-invocation information.
API mounting: type: customer
Once registered, note the service's service ID (of the form mcp-xxxxxxxx; the ID row on the card in the MCP management list). Fill name with the service ID, not the display name — the name field of type: customer resolves by ID despite its name; filling a display name returns AGENT_010 MCP Server lookup error. The mcp_servers field is the same as marketplace services, just with type: "customer":
curl -s -X POST "$AGENTSTUDIO_URL/agents" \
-H "Authorization: Bearer $DASHSCOPE_API_KEY" -H "Content-Type: application/json" \
-d '{
"name": "luming-editor",
"model": {"id": "qwen3.8-max"},
"mcp_servers": [
{"type": "customer", "name": "mcp-xxxxxxxx"}
]
}'
For $AGENTSTUDIO_URL and $DASHSCOPE_API_KEY see the production page. Enabling tools works exactly as with marketplace services: add an mcp_toolkit to tools, set mcp_server_name to the same service ID (it must match mcp_servers[].name), and enable each tool in configs[ ] (without per-tool enablement, the tool is silently absent at runtime; details in the production page).
One important difference — the two types validate at completely different times:
type: official (marketplace) | type: customer (custom) | |
|---|---|---|
| Validation at declaration | No existence check — a name absent from the marketplace still creates successfully | Mandatory check — an unregistered name fails with 400 at creation |
| Failure mode | Tool silently absent at runtime; the agent says it has no tools | Fails at creation: AGENT_010 "MCP Server 校验失败: 查询MCP详情异常" |
| Troubleshooting difficulty | High (no error; the tool is simply missing and hard to notice) | Low (fails at creation) |
So the order is fixed for custom MCP: register in the console first, then reference it in the agents configuration. Incidentally, registering, renaming, and deleting custom MCP currently happens in the console (there is no API endpoint for it).
Passing MCP auth headers per session
The RAG example above uses fixed auth: replace the key with the real value at registration, and every session shares it. When the same custom MCP server needs different auth per session, use dynamic header placeholders instead. For example, the review assistant must pass a per-session access token (dynamic-header) and tenant ID (X-Tenant-Id) when calling its business MCP. Two steps: declare the dynamic headers when registering the MCP, then pass the session's real values in the POST /sessions call — the platform substitutes them when calling that MCP.
Declare dynamic headers at registration
In the custom MCP's MCP service configuration (JSON), write each header that needs a dynamic value as "Header-Name": "${Header-Name}". Example, a server named business_mcp:
{
"mcpServers": {
"business_mcp": {
"type": "streamableHttp",
"url": "https://mcp.example.com/mcp",
"headers": {
"dynamic-header": "${dynamic-header}",
"X-Tenant-Id": "${X-Tenant-Id}"
}
}
}
}
${dynamic-header} and ${X-Tenant-Id} are placeholders preserved verbatim at registration — do not replace them with one session's real values. A server can declare multiple dynamic headers. Keep the header name, the placeholder name, and the key you pass later identical; the configuration field is headers. After registering, mount the agent and enable tools as in the previous section.
Pass real values via mcp_configs at session creation
Add mcp_configs at the top level of the POST /sessions body, alongside agent and environment_id (field definitions: Create Session API):
curl -s -X POST "$AGENTSTUDIO_URL/sessions" \
-H "Authorization: Bearer $DASHSCOPE_API_KEY" -H "Content-Type: application/json" \
-d '{
"agent": {"id": "agent_xxx"},
"environment_id": "env_xxx",
"title": "Luming review: tenant A",
"mcp_configs": [
{
"mcp_server_name": "mcp-xxxxxxxx",
"headers": {
"dynamic-header": "session-a-token-example",
"X-Tenant-Id": "tenant-a"
}
}
]
}'
| Parameter | Type | Notes |
|---|---|---|
mcp_configs | array | Session-level MCP request headers, independent of agent; empty array when omitted. For multiple MCP servers, add one entry per server |
mcp_configs[].mcp_server_name | string, required | The target MCP server identifier; keep it identical to mcp_servers[].name in the agent config and mcp_server_name in the tool config — all three |
mcp_configs[].headers | object, required | Request header key-value pairs for this session; keys and values are strings |
The outer Authorization header authenticates the Managed Agents API call; mcp_configs[].headers are what the agent sends to the target MCP server. For another tenant, reuse the same agent and MCP registration and only swap the headers values — no re-registration needed.
NoteAn unknown MCP server name does not fail session creation, but the configuration silently has no effect at runtime — verify mcp_server_name against the registered service ID. Also, mcp_configs does not appear in the create-session response; GET /sessions/{id} returns it, so check via GET.
Skills: package the team's workflow for upload
MCP solves "the agent can call your services"; Skills solve "the agent works by your process". A Skill is a zip package: a manual (SKILL.md) plus bundled resources (scripts, templates, reference files) — zipped, uploaded, reviewed, and attached to an agent. The full SKILL.md authoring spec is in Agent Skills.
Packaging
The zip is at most 10 MB and must have SKILL.md at the root. The front matter declares name and description — the agent decides when to invoke the skill from the description.
Luming's "manuscript health check" skill, two files:
SKILL.md (the manual):
---
name: manuscript-check
description: 稿件体检技能。当用户提交稿件要求"体检"、"审读"或"出版前检查"时使用:将稿件保存为文本文件后运行 manuscript_check.py,对稿件做标题层级跳跃、段落超长(>400 字)、绝对化禁用词三类检查,并将 JSON 输出整理为审读意见清单。
---
# 稿件体检
对稿件执行出版前体检时按以下流程操作:
1. 将用户提交的稿件内容保存为 `/tmp/manuscript.txt`
2. 运行 `python3 manuscript_check.py /tmp/manuscript.txt`
3. 将 JSON 输出整理为审读意见:逐项列出问题类型、所在段落与建议
## 检查项
- 标题层级跳跃(如 H1 直接到 H3)
- 段落超长(超过 400 字)
- 绝对化禁用词("最好"、"第一"、"绝对"、"百分之百"、"史无前例"、"独一无二")
manuscript_check.py (bundled resource, excerpt):
#!/usr/bin/env python3
"""Manuscript health check: heading hierarchy, paragraph length, banned words. Outputs a JSON review list."""
import json, re, sys
BANNED = ["最好", "第一", "绝对", "百分之百", "史无前例", "独一无二"]
def check(path):
text = open(path, encoding="utf-8").read()
issues = [ ]
# Heading skips: scan line by line (adjacent headings need no blank line;
# paragraph-splitting misses them)
prev_level = 0
for lineno, line in enumerate(text.splitlines(), 1):
m = re.match(r"^(#{1,6})\s", line)
if m:
level = len(m.group(1))
if prev_level and level > prev_level + 1:
issues.append({"issue": "HEADING_SKIP",
"detail": f"第 {lineno} 行标题从 H{prev_level} 跳到 H{level}"})
prev_level = level
# Paragraph length and banned words: split on blank lines
paras = [p for p in re.split(r"\n\s*\n", text) if p.strip()]
for i, para in enumerate(paras, 1):
if len(para) > 400:
issues.append({"issue": "PARA_TOO_LONG",
"detail": f"第{i}段 {len(para)} 字(上限 400)"})
for w in BANNED:
if w in para:
issues.append({"issue": "BANNED_WORD", "detail": f"第{i}段出现禁用词「{w}」"})
return {"paragraphs": len(paras), "issues": issues}
if __name__ == "__main__":
print(json.dumps(check(sys.argv[1]), ensure_ascii=False, indent=2))
Package it (SKILL.md at the root — no extra directory layer):
zip manuscript-check.zip SKILL.md manuscript_check.py
The upload chain (API)
Step one: upload as a plain file first (POST /files, multipart), take the file_id:
curl -s -X POST "$AGENTSTUDIO_URL/files" \
-H "Authorization: Bearer $DASHSCOPE_API_KEY" \
-F "file=@manuscript-check.zip"
{"id": "file_zq9kyvmkkprtg0b32afk8rto", "filename": "manuscript-check.zip",
"mime_type": "application/octet-stream", "size_bytes": 1695,
"status": "checking", "created_at": "2026-09-02T12:56:45+08:00", ...}
The zip itself enters review (status: checking).
Step two: create the skill with the file_id (POST /skills; the request body has exactly this one field):
curl -s -X POST "$AGENTSTUDIO_URL/skills" \
-H "Authorization: Bearer $DASHSCOPE_API_KEY" -H "Content-Type: application/json" \
-d '{"file_id": "file_zq9kyvmkkprtg0b32afk8rto"}'
{
"id": "skill_MWY3ZTY5ZDE3MGIzNDFkZWI2Zj",
"name": "manuscript-check",
"description": "稿件体检技能。当用户提交稿件要求\"体检\"、\"审读\"或\"出版前检查\"时使用:...",
"source": "customer",
"status": "checking",
"latest_version": "1.0",
"created_at": "2026-09-02T04:57:01Z",
...
}
Two notable points:
nameanddescriptionare parsed server-side from SKILL.md — the request body has neither field. Whatever the front matter says is what the skill is called; SKILL.md'snamebecomes the directory name in the sandbox later.- The first version number is auto-assigned as "1.0".
Step three: wait for review. A reference timeline:
12:57:01 POST /skills → status: checking
12:57:29 ~ 13:00:21 GET poll → checking (5 polls)
13:01:00 approved → status: active ← 3 min 59 s total
State machine: checking → active (mountable) / rejected (not mountable; the version detail lists issues by file path) → deleted.
Review is not second-scale — scripted uploads should not assume "upload then immediately mount": either poll GET /skills/{skill_id} until active, or make upload an async pipeline (upload → notify → mount).
Step four: attach to the agent:
curl -s -X POST "$AGENTSTUDIO_URL/agents" \
-H "Authorization: Bearer $DASHSCOPE_API_KEY" -H "Content-Type: application/json" \
-d '{
"name": "luming-editor",
"model": {"id": "qwen3.8-max"},
"system": "你是鹿鸣出版的内容审读助手。收到稿件时必须使用 manuscript-check 技能执行体检,并将检查结果整理为审读意见。",
"tools": [{"type": "builtin_toolkit", "default_config": {"enabled": true},
"configs": [{"name": "bash", "enabled": true}, {"name": "read", "enabled": true},
{"name": "write", "enabled": true}, {"name": "glob", "enabled": true},
{"name": "grep", "enabled": true}]}],
"skills": [{"type": "customer", "skill_id": "skill_MWY3ZTY5ZDE3MGIzNDFkZWI2Zj", "version": "1.0"}]
}'
Each skills[ ] entry has three fields: type (customer), skill_id, and version (must lock exactly one existing version; latest is not supported). Mount-validation error samples:
- Mounting a
checking-state skill →400 AGENT_010 "Skill 不存在或正在接受安全扫描: skill_xxx@1.0" - Mounting an active skill with version
9.9→ the same 400 (the message joinsskill_id@version, pinpointing the mismatched pair)
Runtime: how a skill gets "used"
Send a problem manuscript (heading skips + banned words + overlong paragraphs) to the agent with the skill attached, and the event history shows the mechanism in full:
[05:03:33] tool_call activate_skill {"name": "manuscript-check"}
[05:03:33] tool_call_output {"name": "manuscript-check", "content": "<full SKILL.md>"}
[05:03:44] tool_call write manuscript written to /tmp/manuscript.txt
[05:03:47] tool_call bash "cd /root/workspace/skills/manuscript-check && python3 manuscript_check.py /tmp/manuscript.txt"
[05:03:47] tool_call_output the script's JSON result (6 issues)
[05:04:01] message assistant the consolidated review
Line by line:
- Skills are not pre-injected into the system prompt. On receiving a message the model itself decides to call a hidden tool
activate_skillwith the skill name — the SKILL.md (the manual) enters the conversation context only then. The benefit is obvious: attach ten skills without bloating the system prompt — loaded on demand. - The zip is unpacked into the sandbox at
/root/workspace/skills/<skill-name>/(directory name = SKILL.md'sname), and scripts referenced by the manual run directly via bash. Paths in SKILL.md's body should be written relative to the skill directory (e.g. plainmanuscript_check.py); the agentcds there itself. - The manual decides behavior; the script decides facts. What the agent does with the script's JSON is "consolidate + judge": the heading "第一章 绪论" (Chapter 1 Introduction) mechanically triggers the banned word "第一" (first); the agent's handling — "a chapter-numbering word triggered the match; recommend confirming with the proofreader whether ordinal uses of '第一' are exempt". The script does the scanning; the model does the judgment — the boundary most worth thinking through when designing a skill.
Versioning
Upload a new version (POST /skills/{skill_id}/versions; the body again has only file_id):
# Edit the BANNED constant in manuscript_check.py (say, add "全网最低价" — the check logic reads the hardcoded list in the script; editing SKILL.md alone has no effect) → repackage → upload
curl -s -X POST "$AGENTSTUDIO_URL/skills/skill_MWY3ZTY5ZDE3MGIzNDFkZWI2Zj/versions" \
-H "Authorization: Bearer $DASHSCOPE_API_KEY" -H "Content-Type: application/json" \
-d '{"file_id": "file_889syqcvxriijfzl5xe9kci9"}'
{"id": "skillver_MTRiNWIwNDNiNWZjNDc5Y2", "version": null,
"status": "checking", "type": "skill_version", "skill_id": "skill_MWY3...", ...}
Behavior:
- Version numbers auto-increment by 0.1:
1.0→1.1→1.2… (the creation response'sversionis null, but the version list shows the new one immediately); - New versions pass the same security scan (checking → active, same order of duration);
- Mount locking: after
1.1becomes active, an agent mounted on1.0is untouched (GET /agents showsskills[0].version == "1.0"). To move the agent to the new version you must explicitly update its configuration — the same philosophy as agent version locking (getting started, Advanced: Prompt Versioning and Rollback): running things are not disturbed by background changes; - Two actual behaviors of the current upgrade window (while the new version is under review, minutes in practice):
latest_versionadvances to1.1as soon as the new version enters checking, not after approval; and during that window, creating an agent mounted on the older active version1.0is rejected (400 "Skill does not exist or is undergoing a security scan: ...@1.0"), and already-mounted agents may error while running. It recovers once the window ends, without any configuration change. When planning upgrades, avoid creating new agents in this window, or wait until the new version is active; - The version list
GET /skills/{id}/versionssorts newest first; GET /skills/{id}/versions/{version}/contentreturns an OSS pre-signed download URL — you can retrieve the exact package you uploaded (an audit asset: skill content is traceable).
Note: stray files in the package (say, an accidentally included SKILL_v2.md) do not block the upload and still pass review — audit your package contents before uploading; the review does not catch them for you.
Operations and cleanup
# Status: the skill list (source filter: custom/official)
curl -s "$AGENTSTUDIO_URL/skills?source=official&limit=10" -H "Authorization: Bearer $DASHSCOPE_API_KEY" # official marketplace skills
curl -s "$AGENTSTUDIO_URL/skills" -H "Authorization: Bearer $DASHSCOPE_API_KEY" # all
# Delete: hard delete, unrecoverable (skills have only DELETE, no archive)
curl -s -X DELETE "$AGENTSTUDIO_URL/skills/skill_xxx" -H "Authorization: Bearer $DASHSCOPE_API_KEY" # 200
- Skill deletion is a hard delete — contrast agents/deployments, which are archive-only. Think before deleting: agents mounted on the old version lose their reference.
- Custom MCP is managed in the console (no API); skills are fully API-managed (this page) — the management entry points are opposite.
- The
GET /skillslist shows the health of every skill in the workspace (marketplace ones included) and supports routine inspection.
Summary
| Want | Use | Key action |
|---|---|---|
| Platform execution capability (bash / file I/O / web) | builtin_toolkit | Getting started |
| Public generic services (search, document processing…) | Marketplace MCP (type: official) | Production |
| Your own MCP server (knowledge base, business system) | Custom MCP (type: customer) | Console registration (script deployment → http mode, registration-only and free) → API mounting |
| Different MCP auth per session | Dynamic MCP headers | Declare ${Header-Name} placeholders at registration → pass real values via mcp_configs at session creation |
| The team's workflow (checklists, scripts, templates) | Custom Skill | Package zip → API upload → review (minutes) → mount with locked version |
Division of labor: MCP determines which services the agent can call; Skills determine which process the agent follows. Luming's review assistant mounts both: the RAG MCP lets it consult the editorial standards, and the manuscript-check skill runs the manuscript review against the defined checklist.
Engineering checklist (in priority order): write SKILL.md's description to say "when to use" (the model routes by it) → budget review wait in the upload pipeline (minutes) → mount with a specific locked version (do not track latest) → write script paths in the manual as relative usage → assess mounted references before deleting.
Next steps
- Agent Skills: the SKILL.md authoring spec and field validation rules.
- Agent MCP: mounting and approval.
- Getting Started: Build a Data Analysis Agent: built-in toolkit usage.