Org-managed plugins have no way to inject secrets into MCP servers
Problem
Org-managed plugins that ship MCP servers requiring API tokens (e.g., Meta Ads, Google Ads) have no viable mechanism to provide secrets in the Cowork cloud sandbox environment.
This is a blocker for teams building internal tooling plugins that connect to authenticated APIs.
What we tried
1. userConfig with sensitive: true
Defined secrets in plugin.json manifest:
{
"user_config": {
"api_token": {
"type": "string",
"sensitive": true,
"required": true
}
}
}
Referenced in .mcp.json as ${user_config.api_token}.
Result: For org-managed plugins, the "Customize" button is greyed out with "managed by organization". End users cannot input values. Admins have no way to set them either.
2. ${ENV_VAR} in plugin .mcp.json
Referenced environment variables directly:
{
"env": {
"META_ACCESS_TOKEN": "${META_ACCESS_TOKEN}"
}
}
Result: Vars are passed as literal strings, not expanded. This was reported in #9427 (closed), but still does not work for org-managed plugin .mcp.json files in our testing (v2.1.87).
3. SessionStart hook fetching from a vault
Added a hook to fetch secrets at session start:
{
"hooks": {
"SessionStart": [{
"command": "gh api repos/org/vault/contents/secrets.env --jq '.content' | base64 -d > $CLAUDE_PLUGIN_DATA/.env"
}]
}
}
Result: Cowork cloud sandbox has no gh CLI, curl access is restricted, and CLAUDE_PLUGIN_ROOT / CLAUDE_PLUGIN_DATA are both empty. The hook cannot fetch or write anything useful.
4. Wrapper script as MCP command
Pointed the MCP server command to a shell script that fetches secrets before launching:
{
"command": "bash",
"args": ["${CLAUDE_PLUGIN_ROOT}/scripts/start-with-secrets.sh"]
}
Result: Same sandbox limitations — no network access to vaults, no credentials bootstrap.
Current workaround
A scheduled GitHub Action fetches tokens from a private vault repo and commits them directly into the plugin's .mcp.json:
# .github/workflows/refresh-secrets.yml
- name: Fetch secrets from vault
run: |
TOKEN=$(gh api repos/org/vault/contents/ads.env --jq '.content' | base64 -d | grep META_ACCESS_TOKEN | cut -d= -f2-)
# Write token directly into .mcp.json
cat > plugins/whizz-meta/.mcp.json << EOF
{ "mcpServers": { "meta-ads": { "env": { "META_ACCESS_TOKEN": "$TOKEN" } } } }
EOF
- name: Commit if changed
run: git add -A && git diff --staged --quiet || git commit -m "Refresh secrets" && git push
Cowork auto-syncs from the repo, so the MCP server picks up the new token. This works, but secrets live in git history (even if the repo is private).
Proposal
An org-level secrets mechanism, similar to GitHub Actions secrets:
- Admin defines secrets in the org plugin settings at claude.ai (e.g.,
META_ACCESS_TOKEN) - Plugin manifest declares required secrets:
``json``
{
"secrets": {
"META_ACCESS_TOKEN": {
"description": "Long-lived Meta API token",
"required": true
}
}
}
- Secrets are injected as env vars into the MCP server process at runtime — never written to disk or committed to git
This would also unblock:
- Plugins connecting to any token-based API (Google Ads, TikTok Ads, LinkedIn Ads, etc.)
- Plugins that need database credentials, internal API keys, etc.
- Teams that can't use OAuth-based connectors because their APIs don't support it
Alternatives considered
- OAuth connectors (like Slack/Asana) — ideal but requires the upstream API to have an MCP server with OAuth support. Most advertising APIs don't.
headersHelperscript — requires network access from the sandbox to a vault, which isn't available.
Environment
- Claude Code v2.1.87
- Claude Team plan
- Plugin installed as org-managed in Cowork (cloud)
Related issues
- #9427 — env var expansion in plugin .mcp.json (closed)
- #25964 — secret vault for plugins (closed as stale)
- #17719 — shell command substitution in env values (closed)
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗