From runbear
Deploy a local Claude Code project to a Runbear Claude Agent SDK agent AND configure the MCPs and skills it needs, so colleagues can use your agent without you doing it on their behalf. Files are uploaded through the Runbear signed-URL flow (`create_project_upload` + `finalize_project_upload`); MCPs are attached with a per-integration choice of shared vs per-user auth; skills are curated from your project and user scope; and the whole configuration is saved to a committed `.runbear/deploy.json` manifest for one-command redeploys. After deploying, if the agent has no Slack channel yet, it offers to connect Slack (via `runbear:connect-slack`) so colleagues can reach it. Use when the user wants to deploy, publish, push, sync, share, or redeploy a local project (its files, CLAUDE.md, skills, MCPs, and code) to a hosted Runbear Agent SDK agent identified by an app UUID, agent URL, or agent name (fuzzy-matched) — or, with `--new <agentName>`, to create a brand-new Claude Agent SDK agent and deploy to it in one step.
How this skill is triggered — by the user, by Claude, or both
Slash command
/runbear:deploy <appId-or-url-or-name> | --new <agentName> [--cwd <project-path>] [--overwrite] [--add-mcp <name>] [--remove-mcp <name>] [--add-skill <path>] [--remove-skill <path>]<appId-or-url-or-name> | --new <agentName> [--cwd <project-path>] [--overwrite] [--add-mcp <name>] [--remove-mcp <name>] [--add-skill <path>] [--remove-skill <path>]This skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Deploy a local Claude Code project to a Runbear Claude Agent SDK agent **and**
Deploy a local Claude Code project to a Runbear Claude Agent SDK agent and configure the MCP integrations and skills it needs, so the hosted agent behaves like your local one and your colleagues can use it directly. The goal is: let your colleagues use your Claude agent without you doing it on their behalf.
A deploy does three things:
CLAUDE.md, skills, subagents, docs, and code..claude/skills/) and your user scope (~/.claude/skills/).Every choice is written to a committed .runbear/deploy.json manifest, so a
later deploy reproduces the exact same agent, or adjusts it with --add-* /
--remove-* flags.
After a successful deploy, if the agent isn't connected to Slack yet, the skill
offers to run runbear:connect-slack so your team can actually reach it.
Unlike skill-uploader:upload (a single SKILL.md), this deploys the whole
project and configures the agent end to end.
create_project_upload, finalize_project_upload,
search_apps, attach_app, attach_custom_mcp, list_agent_tools,
detach_tool, list_slack_installations, list_agents, and get_agent. If
those tools are unavailable, tell the user to connect the Runbear MCP first and
stop.--new "<agentName>". A name is fuzzy-matched via
list_agents. The target agent must be of type Claude Agent SDK (the
backend rejects others with agent_must_be_claude_agent_sdk).claude CLI on PATH (used to enumerate local MCPs via claude mcp list / claude mcp get).git, zip, and curl on PATH.Deploy to an existing agent (first deploy is guided; MCP/skill selection runs):
/runbear:deploy <appId>
/runbear:deploy https://app.runbear.io/agents/<appId>
/runbear:deploy "My Agent"
/runbear:deploy "My Agent" --cwd ./my-project --overwrite
Create a new Claude Agent SDK agent and deploy to it in one step:
/runbear:deploy --new "My Agent"
/runbear:deploy --new "My Agent" --cwd ./my-project
Redeploy with the saved settings (silent — no re-prompting), optionally editing the MCP/skill set:
/runbear:deploy "My Agent"
/runbear:deploy "My Agent" --add-mcp linear --remove-skill .claude/skills/old
/runbear:deploy "My Agent" --add-skill .claude/skills/triage --remove-mcp stripe
Exactly one target is required:
--new <agentName>, which creates a fresh Claude Agent SDK
agent and deploys to it.Plus:
--cwd <path> for the local project folder (defaults to .).--overwrite to replace files that already exist in the agent
workspace (irrelevant with --new, whose workspace starts empty).--add-mcp <name>, --remove-mcp <name>, --add-skill <path>, --remove-skill <path> (each repeatable).If neither a target nor --new is given, ask which agent to deploy to. Do not
guess a UUID, and do not create an agent unless the user passed --new.
.runbear/deploy.json)The manifest records what this project deploys and how. It is committed to the
repo (shareable, versioned) and excluded from the upload (the pack script
filters .runbear/ automatically — it never lands in the agent workspace).
{
"version": 1,
"agentId": "943276d9-a4a6-4d49-8df4-6e0fb05b2ab7",
"agentName": "Support Bot",
"mcps": [
// catalog match → attach_app
{ "localName": "notion", "attach": "app", "runbearKey": "managed:notion", "userType": "user" },
// remote custom, OAuth → attach_custom_mcp (no secret)
{ "localName": "linear", "attach": "custom", "url": "https://mcp.linear.app/mcp",
"transportType": "streamableHttp", "auth": { "type": "oauth" }, "userType": "user" },
// remote custom, static secret → vaulted server-side; NO secret stored here
{ "localName": "heyreach", "attach": "custom", "url": "<vaulted>",
"transportType": "streamableHttp", "auth": { "type": "static" }, "userType": "app" }
],
"skills": [".claude/skills/triage", ".claude/skills/summarize"],
"lastDeployedAt": "2026-07-09T00:00:00Z"
}
Manifest rules (NON-NEGOTIABLE):
"<vaulted>" in place of the URL/credential — the
real secret lives only in the Runbear vault (set via attach_custom_mcp).userType is "app" (shared) or "user" (per-user).attach is "app" (catalog → attach_app) or "custom" (→
attach_custom_mcp)..env, .env.*, and any file containing API keys, tokens,
private keys, bearer tokens, or postgres://user:pass@ URLs..mcp.json, .claude/settings.json,
.claude/settings.local.json, .claude.json..git/, .runbear/, node_modules/, and build/dep output (dist/,
build/, .next/, .turbo/, .omc/, coverage/, .venv/, vendor/,
__pycache__/)..gitignore (via git ls-files).<skill-dir> below is this skill's base directory (the folder containing this
SKILL.md).
Parse into target (first positional — UUID, agent URL, or agent name),
newAgentName (--new), projectPath (--cwd, default .), overwrite
(default false), and the edit flags addMcps / removeMcps / addSkills /
removeSkills (each a list). target and --new are mutually exclusive; if
both are present, ask which they meant and stop.
agentId--new <agentName> — create the agent first with create_agent:
{ "name": "<agentName>", "type": "claude-agent-sdk" }
Use the returned id as agentId; show the returned url. If create_agent
is unavailable (Runbear MCP connected in team mode, where it's hidden), tell
the user to connect the Runbear MCP at user scope and stop. Do not fall back to
an existing agent.
target is a UUID or Runbear agent URL (.../agents/<uuid>) — use it
directly (extract the UUID from a URL). Capture the agent name via get_agent.
target is anything else — treat it as an agent name and resolve with
list_agents, passing the name as query (case-insensitive fuzzy filter):
--new "<name>", and stop.id; confirm by showing name + app ID.createdByMe). If list_agents reports a non-null nextCursor, say
so and offer to narrow the name rather than truncating.Read <projectPath>/.runbear/deploy.json.
mcps / skills, then apply any edit flags (see Redeploy below).
If the saved agentId differs from the resolved agentId, tell the user and
ask which one wins before continuing.claude mcp list, then claude mcp get <name>
for each to read its scope, transport (http/sse/stdio), URL, and auth.plugin:* servers and the claude.ai
built-in remotes (e.g. claude.ai Slack, Gmail, Google Drive) — they're
host-specific and won't map to Runbear.stdio transport with no catalog equivalent → un-deployable (the hosted
agent can't run local commands).http/sse transport → remote (catalog match or custom attach).CLAUDE.md and the project structure, and pre-select
a recommended subset of the eligible MCPs, each with a one-line rationale
(e.g. "Notion — your CLAUDE.md references a Notion knowledge base").AskUserQuestion (multi-select): show the eligible MCPs with
the recommended ones pre-checked; let the user add/remove. Selecting zero is
allowed (deploy proceeds with files/skills only).For the selected MCPs:
MCPs whose auth is a single shared credential (static header token,
secret-in-URL, or none) are inherently shared (userType: "app"). State
this; don't offer a per-user option for them.
For the remaining OAuth-capable MCPs, ask (via AskUserQuestion,
multi-select) which should be shared. Everything not chosen defaults to
per-user (userType: "user").
Say this before they pick "shared" for any OAuth MCP: a deploy cannot
carry your local OAuth login. Claude Code's OAuth tokens live in your OS
keychain, are never returned by claude mcp get, and are bound to Claude
Code's own OAuth client — so the server is attached with no credential. A
shared OAuth MCP must have its org connection authorized once in the
Runbear web UI (/agents/<agentId>/integrations) before its tools work for
anyone. A per-user OAuth MCP instead prompts each colleague to authorize on
first use. Either way the attach step only registers the server — an OAuth
MCP has no callable tools until that authorization happens. (Static-secret and
secret-in-URL MCPs are different: their credential is vaulted during deploy,
see Step 6.)
If the target is a personal agent, note that the backend coerces
everything to app (per-user is meaningless for a single-owner agent) and skip
the shared/per-user question. The shared-OAuth caveat above still applies — a
personal agent's OAuth MCPs must be authorized once in the web UI.
For each selected MCP, resolve an attach path in this order:
Catalog match — call search_apps with the MCP's name (and, if helpful,
its host). If a confident match is found (prefer OAuth variants and the latest
version), attach it with attach_app:
{ "agentId": "<agentId>", "key": "<key from search_apps>", "userType": "<app|user>" }
Remote, no catalog match → attach_custom_mcp using the local URL and
transport:
OAuth or no-auth — attach directly, no secret prompt:
{ "agentId": "<agentId>", "app": "<localName>", "url": "<url>",
"transportType": "<streamableHttp|sse>", "userType": "<app|user>",
"auth": { "type": "oauth" } }
This registers the server but stores no credential — see the shared-OAuth flag at the end of this step.
Secret-bearing (a static header token, or a secret in the URL) — first
prompt the user per-MCP before reading the secret from claude mcp get.
If they decline, skip this MCP and list it in the report with a pointer to
/agents/<agentId>/integrations. If they confirm, vault the secret:
{ "agentId": "<agentId>", "app": "<localName>", "url": "<url>",
"transportType": "streamableHttp", "userType": "app",
"auth": { "type": "static", "headerKey": "Authorization" },
"httpHeaders": { "Authorization": { "type": "secret", "value": "<token>" } } }
(For a secret embedded directly in the URL, pass the full URL and
auth: { "type": "none" }; the secret rides in the URL query. It is still a
secret — gate it behind the same per-MCP prompt.)
stdio with no catalog match → do not attach; add it to the report's
un-deployable list with a link to /agents/<agentId>/integrations.
Flag shared-OAuth attaches for the report. For any OAuth-capable MCP
(Step 5) attached with userType: "app" — whether via attach_app (6.1) or
attach_custom_mcp (6.2) — the attach registers the server but leaves it with
no usable credential: the org OAuth grant must be authorized once before the
tools work. Record every such MCP (keep its integration id from the
attach_custom_mcp / attach_app response) as pending authorization and
list it in the report's needs-oauth: section (Step 10 / Success response).
Static-secret and secret-in-URL shared MCPs are already vaulted during attach and
do not belong in this list.
Do NOT auto-generate connect links. The report only lists the pending
MCPs — it does not mint or print any authUrl. A connect link is an
agent-scoped, one-shot credential-granting URL; surfacing one unprompted (or in
a shared channel) is a leak vector (RB-6505 / RB-6517). Instead, tell the user
they can ask for a link per MCP, and mint it on request (see
Connecting a shared-OAuth MCP below).
Record each attached MCP (localName, attach, runbearKey/url, auth.type,
userType) for the manifest. Store "<vaulted>" instead of any secret-bearing
url/credential.
<projectPath>/.claude/skills/*/SKILL.md) and
user-scope skills (~/.claude/skills/*/SKILL.md).description plus
CLAUDE.md, pre-select a recommended subset. Confirm with AskUserQuestion
(multi-select).--exclude-path list for the pack script so they are not
uploaded.<projectPath>/.claude/skills/<slug>/ so it rides along in the upload zip:
<projectPath>/.claude/skills/<slug> already
exists. If it does (a name collision with a project skill), surface it to
the user and ask whether to keep the project version or overwrite — do NOT
silently clobber.Record the final selected skill set (project-relative paths) for the manifest.
Call create_project_upload:
{ "agentId": "<agent URL or UUID>" }
On status: "ready" you get { uploadId, uploadUrl, maxBytes, expiresAt }. On
status: "blocked", report the reason (e.g. agent_must_be_claude_agent_sdk)
and stop (and remove any staged skills first).
Tip: on a first deploy you can preview with a dry run before requesting the URL:
bash <skill-dir>/scripts/pack-and-upload.sh --cwd <projectPath> --dry-run \
--exclude-path <deselected-skill-dir> ...
It prints {"fileCount":N,"zipBytes":B,"skippedCount":K,"skipped":[...]}.
Pack and upload promptly (the URL is single-use and time-limited). Pass every
--exclude-path from Step 7 and maxBytes through:
bash <skill-dir>/scripts/pack-and-upload.sh \
--cwd <projectPath> --url "<uploadUrl>" --max-bytes <maxBytes> \
--exclude-path <deselected-skill-dir> ...
On success it prints {"uploaded":true,"fileCount":N,...}. On
{"uploaded":false,"error":"..."}, surface the error and stop (do not finalize).
Then finalize:
{ "agentId": "<agent URL or UUID>", "uploadId": "<uploadId>", "overwrite": <overwrite> }
On status: "blocked", show reasons and offending candidates, then help the
user fix them (remove the file, or rerun with --overwrite for
files_already_exist).
<projectPath>/.runbear/deploy.json — only on a successful
finalize. Include version: 1, agentId, agentName, the recorded mcps
(no secrets), the selected skills, and lastDeployedAt.A deployed agent isn't reachable by your colleagues until it's connected to a channel. After a successful finalize, check whether the agent is already on Slack and offer to fix it if not.
list_slack_installations for
agentId.
slack: connected — <workspace / bot name>).AskUserQuestion (the
interactive UI, never a plain-text yes/no prompt) —
"This agent isn't connected to Slack yet, so your team can't reach it.
Connect it to Slack now?" with options Connect now and Not now.
runbear:connect-slack skill for this
agent (invoke it as runbear:connect-slack "<agentName>"). Do not
reimplement the Slack flow here — that skill owns the custom-bot setup link
and channel-joining.next: pointer tells them how to do it
later.connect-slack pointer when it isn't connected.With a valid manifest and no edit flags, run without re-prompting: request an upload URL, pack+upload (staging the saved user-scope skills and excluding deselected ones exactly as before), finalize, and re-attach the saved MCP set.
claude mcp get.needs-oauth: callout for any saved shared-OAuth MCP
(auth.type: "oauth" + userType: "app"). The skill can't verify whether the
connection was ever completed, so it reminds on every redeploy until the user
confirms it's done — and can mint the connect link on request (see
Connecting a shared-OAuth MCP).list_agent_tools before and after to report a diff
of attached tools, plus the skills added/removed.--remove-mcp.Edit flags adjust the saved set, then the manifest is rewritten to match:
--add-mcp <name> — run that MCP through Steps 4–6 (resolve → auth → attach).--remove-mcp <name> — find the integration id via list_agent_tools
(match the manifest's runbearKey) and call detach_tool.--add-skill <path> — stage/include it per Step 7.--remove-skill <path> — add it to the --exclude-path list so it is not
re-uploaded. Note: already-deployed skill files are removed from the workspace
only if the finalize/workspace API supports deletion; if it does not, say so.✅ Project deployed
agent: <agentName>
files: <fileCount> written (<K> filtered out locally)
mcps: <name (shared|per-user)>, ...
un-deployable: <name — reason + web-UI link> (if any)
needs-oauth: <name (shared)>, ... — not yet authorized; these tools return
nothing until connected. Ask me to "connect <name>" and I'll mint a
private one-time link (shared OAuth can't carry your local login). (only if any)
skills: <deployed skill slugs>
slack: connected — <workspace / bot> | not connected
next: <only when Slack isn't connected and the user declined / it was a
silent redeploy> /runbear:connect-slack "<agentName>" — connect it
to Slack for your team
Always print the needs-oauth: section when one or more shared-OAuth MCPs were
attached (Step 6). Do not treat the deploy as fully done while it is non-empty —
those tools return zero callable functions in the agent until the shared account
is connected. List the MCP names only; do not print connect links here.
When the user asks to connect one of the needs-oauth MCPs (e.g. "connect
profound"), mint the link on demand — never preemptively:
id — reuse the id recorded at attach time, or call
list_agent_tools and match the MCP's key (custom:<localName> /
managed:<slug>).generate_integration_connect_link with { agentId, integrationId }.
{ authUrl } → share the link with this user only, here (their own
session). Tell them: open it, sign in to the provider, done — completing it
stores the org-shared credential, so every user and agent run reuses it.
NEVER paste it into a shared Slack channel or any broadcast surface — a
leaked connect link lets anyone holding it authorize into your vault.{ authUrl: null, alreadyConnected: true } → already connected; tell them
it's ready, nothing to do.auth_url_not_applicable → the row is static/no-auth/stdio, or per-user
(userType: "user"); per-user integrations are connected by each end-user
through the agent's own prompt, not a shared link.❌ Project deploy blocked
reasons:
- <backend reason>
candidates:
- <offending path>
Common reasons:
create_project_upload: invalid_agent_id,
agent_not_found_or_not_readable, agent_must_be_claude_agent_sdk,
agent_modify_permission_required, workspace_gcs_bucket_not_configured.finalize_project_upload: upload_not_found, invalid_zip,
zip_too_large, too_many_files, upload_too_large, encrypted_zip_entry,
files_already_exist (rerun with --overwrite), and per-file
secret_detected:<label>:<path> / env_file_not_supported:<path> /
forbidden_directory:<path> / forbidden_config_file:<path> /
invalid_path:<path>.claude mcp get output) and are bound
to Claude Code's own OAuth client, so they can't be transplanted into Runbear's
vault, and jamming the short-lived access token in as a static Authorization
header would break at the first (~1h) refresh. A shared (userType: "app")
OAuth MCP must therefore be connected once — either in the Runbear web UI
(/agents/<agentId>/integrations) or via the on-request connect link the skill
mints with generate_integration_connect_link (see Connecting a shared-OAuth
MCP); a per-user one is authorized by each colleague on first use. Until
then the MCP is attached but exposes zero callable tools — the skill flags
these in the report's needs-oauth: section.Guides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.
Dispatches multiple subagents concurrently for independent tasks without shared state. Use when facing 2+ unrelated failures or subsystems that can be investigated in parallel.
npx claudepluginhub runbear-io/skills --plugin runbear