EasyCC
For adminsBundles

Pre-seeding agents, skills, MCP servers, and secrets

The bundle sections that ship a fully-configured workspace on first launch.

A bundle's value is what it puts in front of the user on first launch. This page covers the four sections that do the heavy lifting: agents[], userSkills[], userMcpServers[], and the secret_ref URIs that thread credentials through all of them.

Pre-seed agents

The agents[] array scaffolds one or more agents under the configured agents_root on the user's machine. Each agent can include a starting CLAUDE.md, skills, MCP servers, and arbitrary starter files.

{
  "agents": [
    {
      "id": "acme-marketing",
      "displayName": "Marketing",
      "createMode": "from-template",
      "relativePath": "Marketing",
      "pinned": true,
      "claudeMd": "You are the Marketing agent for Acme. Default tone: warm and direct.\\nReference files in `brand/` before drafting.\\n",
      "skills": [
        { "name": "summarize", "source": "bundled" },
        { "name": "acme-press-release", "content": "...inline markdown..." }
      ],
      "mcpServers": [ /* ... */ ],
      "files": [
        { "relativePath": "brand/voice-guide.md", "content": "..." }
      ]
    }
  ]
}
FieldNotes
id (required)Stable key for re-apply deduplication. Use <org>-<purpose>acme-marketing. Never reuse across distinct agents.
displayName (required)Renamable by the user; for stable identity, use id.
createModefrom-template (default, standard scaffold), empty, sample (curated example files).
relativePathFolder under agents_root. Defaults to displayName if unset.
claudeMdMarkdown string written to <agent>/.claude/CLAUDE.md. Use \n for line breaks in JSON.
pinnedWhen true, appends to AppConfig.pinned_agents so the agent appears at the top of the roster.
skills[]Reference a bundled starter ({ "name": "summarize", "source": "bundled" }) or ship inline with a content field.
mcpServers[]Inline MCP server configs (see below).
files[]Arbitrary files dropped into the agent folder. Each has a relativePath and content. Reserved paths (.easycc/, .claude/CLAUDE.md, .claude/skills/) are blocked.

The disk layout each agent gets: .easycc/config.json, .claude/CLAUDE.md, .claude/skills/<skill>/SKILL.md per skill, .claude/mcp-servers.json, plus your custom files.

Common patterns:

  • One agent + comprehensive skills — solo founders, single-purpose deployments.
  • Multiple agents + role division — a Marketing agent, an Operations agent, a Customer-Support agent for a small business.
  • Agent + starter files — drop templates, brand guides, or boilerplate into files[] so users open EasyCC to a workspace, not a blank folder.
  • Sample + working agent — one createMode: sample agent for onboarding, plus a working agent for real work.

Pre-seed skills

Skills come in two scopes:

ScopeWhere in the bundleVisible in
Per-agentInside an agent's skills[] arrayOnly that agent
User-scopeTop-level userSkills[] arrayEvery agent the user has

Per-agent skills can reference a bundled starter by name + source: "bundled", or ship inline content. User-scope skills are always inline — no bundled-source shortcut.

{
  "userSkills": [
    {
      "name": "acme-house-style",
      "content": "---\\ndescription: Apply Acme house style to the current file.\\n---\\n\\n# Acme house style\\n\\nWhen running this skill...",
      "approved": true
    }
  ]
}

Frontmatter fields supported in skill content: name, description, category, tags, allowed-tools, model. The approved: true field on a bundled skill skips the first-run approval prompt — use carefully; only set for skills you've vetted.

Maintain skills as .md files in source control, then have a build script read each markdown and inject as the content field of the bundle JSON. Hand-editing the JSON with \n escapes gets painful fast.

Re-apply rules: EasyCC hashes the content of each shipped skill. On re-apply with a new bundle, an updated version overwrites only if the user hasn't edited the local copy (their hash still matches the previous shipped hash). User edits are preserved; an "updated" badge appears on the skill row.

Pick per-agent for work-specific skills; pick user-scope for house-style checkers, brand-voice utilities, and other things that apply everywhere. When in doubt, default to user-scope.

Pre-seed MCP servers

Three transports:

TransportFieldsNotes
stdiocommand, args, env, auth (optional)EasyCC spawns the subprocess and talks MCP over stdin/stdout. command is either in PATH or absolute.
httpurl (HTTPS), authRemote MCP server.
sseurl, authSame as http but server-sent events.

Three auth shapes:

// Bearer token in keychain
"auth": {
  "kind": "bearer",
  "secretRef": "keychain:com.easycc.app/mcp.acme-events.token"
}

// Custom headers — each value can be a keychain URI or a literal
"auth": {
  "kind": "headers",
  "headers": {
    "X-Api-Key":   "keychain:com.easycc.app/mcp.acme-events.X-Api-Key",
    "X-Tenant-Id": "acme-prod"
  }
}

// Unauthenticated
"auth": { "kind": "none" }

Per-agent MCP servers live in the agent's mcpServers[] and write to <agent>/.claude/mcp-servers.json. User-scope MCP servers live in the top-level userMcpServers[] and write to ~/.claude/mcp-servers.json.

Trust boundary: stdio runs arbitrary executables. Only ship bundles from trusted channels (MDM, signed installer) — anyone who can write bootstrap.json to a discovery location can run any command on the device.

Re-apply: per-agent servers are keyed by (agentId, mcpName) for clean dedup; user-scope servers by name. If the user disabled a server in EasyCC, re-apply respects that — the server stays disabled.

Secrets and secret_ref URIs

All secrets in a bundle are references, never literal values. EasyCC reads the actual value from the OS keychain at apply time. The URI format:

keychain:com.easycc.app/<slot-name>

Three places secret_ref appears:

LocationFieldWhat it holds
providerapiKeySecretRefClaude API key
mcpServers[].auth (bearer)secretRefBearer token
mcpServers[].auth.headers.<name>(per-header value)Custom header value

Deployment flow:

  1. Your MDM pushes the actual secret values to the OS keychain before the bundle applies. See Secrets for the per-MDM scripts.
  2. The bundle references the slots only.
  3. On apply, EasyCC reads each secret_ref, then re-stores under a canonical slot the runtime expects (auth_claude_api_key for the provider; mcp.<server-name>.<auth-kind> for MCP). The runtime always reads from canonical slots.

If a referenced secret is missing at apply time, EasyCC fails that section, records the failure in bundle-applied.json, and shows the user a friendly "ask your IT admin" error. The next launch retries automatically — once IT pushes the secret, the section applies cleanly.

Rotation: push the new value to the same slot (your existing MDM script handles this). Old values aren't carried forward inside the bundle — they live in the keychain and are owned by your MDM.

v1 limitation: only keychain: URIs are supported. The runtime returns "your administrator configured a credential type this version doesn't support yet" for anything else. Future versions will add env:, oauth:, provision:, and prompt: schemes.

How is this guide?

On this page