Shared Agents

sa sync · pull team repo

Shared MCPs — design & rollout

Shared MCPs — design & rollout

Status: Draft / planned — installer (install-mcps.py) not implemented yet.
Goal: Team-wide MCP server configuration in the same spirit as skills and learnings: manifest + local overrides + learnings — not a copied mcp.json with SSH/Docker details in Git.

See also: Repository README · Learnings · mcps/README


1. Problem

MCP configuration usually lives per person in ~/.cursor/mcp.json (or the equivalent in other IDEs). That leads to:

ProblemExample
Copy-paste explosionSame Screaming Frog block 15×, only the container name changes
Machine-specific configssh old-infra, Docker names, fixed paths — not portable
Secret leakage riskAPI keys or tokens in the team repo
No “configured?” signalUnclear who can actually use which MCP
Tool fragmentationCursor, Claude Code, Zed — different config paths

Skills and learnings work because they are text and machine-independent. MCP config is runtime wiring — it belongs in an installer + local file, not raw JSON in the remote.


2. Three layers (like team / project / session)

┌─────────────────────────────────────────────────────────────┐
│ LAYER 1 — Team ($SHARED_AGENTS_HOME/mcps/)                  │
│ Manifest: what exists, version, template, detect, generator │
│ Installer writes marked entries into IDE config             │
└───────────────────────────┬─────────────────────────────────┘
                            │
┌───────────────────────────▼─────────────────────────────────┐
│ LAYER 1b — User local (~/.shared-agents/mcps.local.yaml)    │
│ SSH host, spider count, paths — gitignored, never in remote   │
└───────────────────────────┬─────────────────────────────────┘
                            │
┌───────────────────────────▼─────────────────────────────────┐
│ LAYER 2 — Project (optional .cursor/mcp.json in client repo)│
│ Project-specific servers only                               │
└───────────────────────────┬─────────────────────────────────┘
                            │
┌───────────────────────────▼─────────────────────────────────┐
│ LAYER 3 — Session                                           │
│ Agent picks e.g. spider3 for parallel crawl — no persist      │
└─────────────────────────────────────────────────────────────┘
LayerLocationMaintainerContent
Teammcps/manifest.jsonPR / maintainerServer defs, templates, versions
User localmcps.local.yamlEach personHosts, counts, SSH alias — gitignored
Projectproject/.cursor/mcp.jsonProject teamClient MCPs without secrets
SessionIDE chatWhich spider, which crawl_id

Rule: The team manifest contains no secrets, fixed SSH hosts, or client names.


3. Repository artifacts

shared-agents/
├── docs/shared-mcps.md           ← this document
├── mcps/
│   ├── README.md
│   ├── manifest.example.json
│   └── mcps.local.yaml.example
├── scripts/install-mcps.py       ← planned
└── team/learnings/approved/…     ← operational knowledge (when/how to use MCPs)
ArtifactRole
ManifestDeclarative: server ID, template, generator, detect, tier
mcps.local.yamlMachine-specific values — never commit
InstallerIdempotent merge into IDE config; --check like adapters
LearningParallelism, disk, pitfalls — not long arg lists

4. Manifest schema (draft)

Reference: mcps/manifest.example.json

Top level

FieldPurpose
versionManifest version
shared.marker_*Optional documented blocks
shared.local_configPath to gitignored local file
shared.managed_prefixPrefix for team-managed server keys (default: sa-)
hostsPer IDE: config path and format
servers[]MCP server definitions

Server entry

FieldPurpose
idStable ID (e.g. browser-tools-mcp)
nameHuman label for --check
tierteam | optional | infra
domainTags for docs / learnings (seo, frontend, …)
detectPre-install check (binary, SSH, …)
templateStatic MCP block with {{variables}}
generatorRepeated blocks (e.g. N spider instances)
requires_localRequired fields in mcps.local.yaml
varsDefaults + optional from_local

Generator (type: repeat)

Replaces copy-paste like screaming-frog-spider1spider15:

"generator": {
  "type": "repeat",
  "id_pattern": "sa-screaming-frog-spider{{n}}",
  "count_var": "spider_count",
  "template": { "command": "ssh", "args": ["…", "seo-spider-{{n}}", "…"] }
}

Local: spider_count: 6 → installer creates 6 entries. Someone with 15 containers sets 15 — without 15× JSON in Git.


5. Local config

Template: mcps/mcps.local.yaml.example

# ~/.shared-agents/mcps.local.yaml — NEVER commit
ssh_host: infra-host
docker_user: abc
spider_count: 6

Optional profiles (VPN vs laptop without infra):

profile: default

profiles:
  default:
    ssh_host: infra-host
    spider_count: 15
  laptop:
    ssh_host: null   # SF MCP not installed

Onboarding (planned):

cp "$SHARED_AGENTS_HOME/mcps/mcps.local.yaml.example" \
   "$SHARED_AGENTS_HOME/mcps.local.yaml"
# edit
sa install

Keep mcps.local.yaml in .gitignore.


6. Installer behavior (planned)

Same philosophy as install-adapters.py: Python stdlib, idempotent, no network.

Merge strategy (Cursor)

Cursor mcp.json has no comment markers. Use namespacing:

  1. All team servers use prefix sa- (configurable in manifest).
  2. Read ~/.cursor/mcp.json.
  3. Remove only managed keys with the sa- prefix.
  4. Insert newly generated entries.
  5. Leave all other keys untouched (private experiments).

Commands (planned)

CommandDescription
sa installAdapters + MCPs (when manifest + local ok)
sa install --mcps-onlyRewrite MCP block only
sa checkPer MCP: ok / missing_local / detect_fail
sa install --dry-runDiff mcp.json without writing

Check output (example)

MCP                         HOST    CONFIGURED   STATUS
browser-tools-mcp           cursor  yes          ok
screaming-frog-spider       cursor  partial      detect_fail (ssh infra-host)
my-private-server           cursor  n/a          user_managed

detect_fail on an optional server is not an error for people without VPN/infra.


7. Learnings vs manifest

ContentWhere
command, args, version pinManifest
SSH host, spider countmcps.local.yaml
When to run parallel crawls, disk limits, delete_crawlLearning in approved/
Agent workflow (“max 1 crawl per spider”)Skill or learning

Good learning topics: max parallel crawls = spider_count, run storage_summary before large crawls, NDA clients need approval.

Bad learning: 20-line args array — that belongs in generator/manifest.


8. Project layer (client repos)

project/
  .cursor/mcp.json
  .cursor/mcp.json.example   # no secrets, env placeholders
PrefixMeaning
sa-*Managed by Shared Agents — do not duplicate in project
project-* or unprefixedClient-specific, in client repo

Team manifest: no client SSH hosts. Use generic templates + a learning “ask lead for setup”.


9. Security & governance

  1. No secrets in manifest or learnings — document env_from_local: ["API_KEY"], values in mcps.local.yaml or OS keychain.
  2. Manifest changes = PR — not agent-written to approved/.
  3. Repo access ≠ infra access — MCP install does not replace permission boundaries.
  4. Version pins — e.g. @agentdeskai/browser-tools-mcp@1.2.1, not blind @latest.
  5. Privacy — no secrets or NDA content in learnings or manifest args.

10. Example: Screaming Frog spider pool

Setup: Remote Docker on infra host, N containers seo-spider-1seo-spider-N, one MCP per container for parallel crawls.

ApproachProsCons
N× MCP entries (today)True parallelism, simple agent modelMaintenance, many IDE processes at start
Generator + spider_countDRY, scales locallyCount must match infra
1 MCP + queue in serverOne processServer must implement queue
Agent serializesMinimal configSlow

Recommendation: Generator in manifest, team default e.g. spider_count: 6, learning documents limits and disk.

Agent note: screaming-frog-spider3 = instance 3, not “third attempt”. At most one active crawl_site per spider.


11. Migration (when installer exists)

PhaseContent
1 — DocsThis doc + manifest.example.json + mcps.local.yaml.example
2 — Installerinstall-mcps.py, integrate into sa check
3 — LocalRemove old screaming-frog-spider* keys or map via manifest aliasessa-*
4 — TeamExtend onboarding checklist; pilot with 2 people

Alias map (transition):

"aliases": {
  "screaming-frog-spider1": "sa-screaming-frog-spider1"
}

Installer removes old aliases on the next run.


12. Out of scope (for now)

  • Central cloud MCP host (conflicts with local + private Git)
  • Auto-update without PR (npx -y without pin)
  • Replacing SSH keys (~/.ssh/config stays with the user)
  • One MCP format for all IDEs in v1 — Cursor first, others follow under hosts

13. Next implementation steps

  • scripts/install-mcps.py (stdlib, merge, detect, generator)
  • Hook in install.sh / sa install (--mcps-only, sa check JSON field mcps)
  • .gitignore entry for mcps.local.yaml
  • Team learning template after first production use
  • Optional skill shared-agents-mcp for agents before SEO tasks

Appendix: commands (target state)

cp "$SHARED_AGENTS_HOME/mcps/mcps.local.yaml.example" \
   "$SHARED_AGENTS_HOME/mcps.local.yaml"

sa install
sa check
sa install --mcps-only --dry-run