From prisme-ai
Find every Prisme.ai App+MCP connector workspace (by the `app-mcp` label) and check each one against the current `/app-mcp-implement` skill templates + best-practice rules, reporting drift and applying approved fixes. The connectors are the workspaces scaffolded by `/app-mcp-implement`; this skill keeps the whole fleet in sync when a template or trap-fix lands. Use when the user says "update all app-mcp connectors", "vérifie que les connecteurs sont à jour", "propage le fix de template à tous les connecteurs", "/app-mcp-fleet-sync", or similar.
How this skill is triggered — by the user, by Claude, or both
Slash command
/prisme-ai:app-mcp-fleet-sync [?connector-slug] [?sandbox|prod][?connector-slug] [?sandbox|prod]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
You maintain the **fleet of App+MCP connector workspaces** scaffolded by the `/app-mcp-implement` skill. When a template gets a new trap-fix or security hardening, the deployed connectors drift out of date one by one. This skill finds them all, diffs each against the current templates **and** a rule checklist, and applies the approved fixes.
You maintain the fleet of App+MCP connector workspaces scaffolded by the /app-mcp-implement skill. When a template gets a new trap-fix or security hardening, the deployed connectors drift out of date one by one. This skill finds them all, diffs each against the current templates and a rule checklist, and applies the approved fixes.
The source of truth is ${CLAUDE_PLUGIN_ROOT}/skills/app-mcp-implement/templates/ plus that skill's Common traps + MCP endpoint security checklist. Read app-mcp-implement/SKILL.md if you are unsure why a given fix exists — every rule in the audit script traces back to a trap documented there.
Default operating mode (confirmed conventions):
app-mcp label, with a structural-fingerprint fallback for bootstrap (see Phase 1).This skill never blind-overwrites a connector file. A "verbatim" template can still carry an intentional local addition (e.g. webdav adds a viewer role to security.yml) — every diff hunk is classified before anything is written.
These carry the app-mcp fingerprint (automations/generateKey.yml + imports/MCP Core.yml): data-galaxy, figma, gitlab, gitlab-oauth, google-docs, google-drive, google-mail, gryzzly, hubspot, monday, sage-x3, salesforce, service-now, sonarqube, storage-client, tableau, webdav (+ any new one since). None carry the app-mcp label yet — Phase 6 retro-tags them, and the /app-mcp-implement skill now stamps the label on creation.
scripts/audit_connector.py maps each template file onto the connector and tags it:
| Class | Files | Diff semantics |
|---|---|---|
| A — verbatim | security.yml, automations/formatToolOutput.yml, automations/generateKey.yml | Post-substitution they should be byte-identical. A DRIFT is either a template-ahead fix to apply or an intentional local addition to preserve — classify per hunk. |
| B — customized | getConfig, handleApiError, routeToolCall, buildAppAuth, executeApiCall, mcp, onInstall, imports/Custom Code.yml | Genuinely per-(auth-model / REST-vs-GraphQL / OAuth). Diff is advisory — never auto-apply; cross-check against the rule audit and the relevant trap before touching. |
| O — OAuth-only | ensureAuthentication, oauthCallback, refreshOAuthToken, initiateOAuth, checkAuthStatus, disconnectOAuth, methodConnect/toolConnect, methodDisconnect/toolDisconnect, connect, disconnect | Compared only when the connector has automations/oauthCallback.yml. Customized per provider — advisory. |
| C — per-workspace | index.yml, swagger.yml, automations/<op>.yml, imports/MCP Core.yml, pages/* | Never diffed against a template. Covered by the rule audit instead. |
⚠️ Do not use shell
diffto compare files in this repo — it is rtk-proxied into a custom format without</>markers, which silently produces false negatives. Always use the audit script's YAML-normalized diff.
scripts/audit_connector.py runs on one connector and prints a compact markdown report (two sections: rule audit + template diff). It is read-only.
python3 ${CLAUDE_PLUGIN_ROOT}/skills/app-mcp-fleet-sync/scripts/audit_connector.py \
--connector prismeai-workspaces/workspaces/<slug> \
--templates ${CLAUDE_PLUGIN_ROOT}/skills/app-mcp-implement/templates
The rule audit codes (all trace to app-mcp-implement/SKILL.md):
| Code | Severity | Checks |
|---|---|---|
R1-label | MAJOR | index.yml labels contains app-mcp |
R2-mcpApiKey | MAJOR | config.value.mcpApiKey absent (else it defeats onInstall's guard) |
R3-array-items | MAJOR | no type: array without items: in index.yml mcpTools |
R4-mcpcore-mirror | MAJOR | imports/MCP Core.yml mcpTools mirror index.yml (runtime source of truth) |
R5-triggerSync | MAJOR | no automations/triggerSync.yml (incompatible with dispatcher pattern) |
R6-cc-array | MAJOR | no type: array in any Custom Code function parameters |
R7-cc-hash | MAJOR | no # comment inside any Custom Code `code: |
R8-ternary | NEED_HUMAN | no ternary ? : inside a DSUL {% %}/{{ }} expression |
R9-raw-body | NEED_HUMAN | handleApiError does not echo raw json(response.body) |
R10-secret | NEED_HUMAN | credential-bearing config.schema fields are secret: true |
R11/R12-oauth | NEED_HUMAN | (OAuth only) getConfig {{-guard + onInstall populates oauthCallbackUrl |
R13-secret-provision | NEED_HUMAN | onInstall auto-wires the per-tenant Secrets-section ↔ app-instance binding for any connection credential — PAT / token / apiKey / password / clientSecret / OAuth client secret, not just OAuth (makeSecretRef/makeConfigRef + PATCH /security/secrets + PATCH /config binding B + binding A in the terminal merge). Absent = relies on direct config-form entry (value visible in the form, no redaction). Canonical: google-docs. See memory feedback_oninstall_secret_provisioning. |
R14-literal-braces | MAJOR | No literal {{ inside a DSUL expression/condition (the matches "{{" binding-literal guard). It crashes InvalidVariableNameError (..) on the unconfigured path when the tested value is itself an unresolved {{…}} literal. Detect the brace in Custom Code instead (cleanCredential helper). Latent in every OAuth getConfig from the old template. See memory feedback-dsul-literal-braces-in-expr. |
R15-public-page | MAJOR | A page in pages/ using accessControl: public (a silently-ignored field) without labels:\n - public. The security.yml rule grants anonymous read via conditions.labels.$in:[public], so the page needs the label — otherwise it stays members-only and any non-owner (e.g. a colleague completing OAuth on connector-callback) hits the pages service 401. Mechanical fix: replace accessControl: public → labels: [public]. See memory feedback_prisme_public_page_label. |
R16-arg-descriptions | NEED_HUMAN | A public App-mode op (<op>.yml) has arguments: entries without a description:. The Builder renders the instruction form from these arguments, so an undescribed arg shows a blank-hint field (the classic bare id complaint). Fix is connector-specific content (API-sourced; id/iid described by their specific resource — project/user/group), so NOT mechanical — surface per connector. Canonical pass: gitlab (script scripts/doc_gitlab_args.py). See memory feedback_app_mcp_arg_descriptions. |
R17-private-endpoints | MAJOR | An internal automation (mcp, generateKey, getConfig, onInstall, the helpers, the method*/tool* dispatchers, OAuth flow webhooks) is missing private: true, so it surfaces in the App's instructions list (a bare <App>.generateKey etc. callable by tenants). Mechanical fix: add private: true (it hides from instructions but does NOT block the endpoint: webhook nor event triggers). The public per-op instructions + connect/disconnect stay non-private. |
R19-camelcase-automations | MAJOR/NEED_HUMAN | Automation slugs and filenames must be camelCase with no hyphens. Known internal dispatcher renames (method-restOp → methodRestOp, tool-connect → toolConnect, etc.) are mechanical if references are updated together. Public App-mode hyphenated slugs are NEED_HUMAN because renaming them changes the tenant-facing <App>.<automation> contract. |
🔴 MAJOR = mechanical, safe to fix and push. 🟠 NEED_HUMAN = surface to the user before touching.
If the rule checklist grows (a new trap lands in /app-mcp-implement), add the rule to the script — keep it the single executable definition of "up to date".
$ARGUMENTS doesn't specify it (default sandbox — the connector fleet is iterated sandbox-first, in line with memory feedback_connector_work_sandbox_first; the production:app label only flags the intended target, not the env you should be writing to). Only audit/push prod when the user asks for it explicitly.search_workspaces({environment, labels: "app-mcp"}).prismeai-workspaces/workspaces/*/automations/generateKey.yml locally, and/or search_workspaces({environment, labels: "MCP"}) then keep only those whose pulled tree has both automations/generateKey.yml and imports/MCP Core.yml. Report which discovery path was used.$ARGUMENTS names a single connector slug, scope the whole run to it (still useful for one-off propagation).The audit compares against the deployed state, so refresh from the env first (don't trust a possibly-stale local checkout). For each connector, pull_workspace into its canonical path prismeai-workspaces/workspaces/<slug>/ (never an orphan dir — see memory feedback_pull_workspace_canonical_path). If the user explicitly wants to audit the local working copy as-is, skip the pull and say so.
Run scripts/audit_connector.py on every connector (parallelize with one Bash call per connector, or a small loop). For a large fleet, dispatch the runs across Agent (Explore) sub-agents to keep the main context lean — each returns only its connector's report.
Collect the raw reports. Do not act yet.
For each connector, turn the script output into a decision list:
json("")/HTTP-204 guard, the action op branch) → 🔴 apply.viewer role, a service-specific verbose message) → preserve; never delete it. If a single file mixes both (gryzzly's formatToolOutput is missing the guard and has a custom create message), apply only the template-ahead hunks by hand.<file> differs from the template — likely a customization, confirm whether a template-ahead fix is buried in it" unless a rule-audit finding pins a concrete bug, in which case fix that specific bug, not the whole file.Produce one consolidated report: a table (connector × number of 🔴 / 🟠 / clean) plus, per non-clean connector, the bulleted 🔴/🟠 list. Reference files by path; never dump full files.
Only after the user approves the report:
mcpApiKey removal, items: addition, type:object swap, #→//, MCP Core re-mirror, template-ahead hunk transplant). Edit in the canonical workspace folder.<<SERVICE_NAME>>, <<SERVICE_SLUG>>, <<WORKSPACE_ID>>, <<BASE_URL>>).index.yml mcpTools, re-mirror imports/MCP Core.yml (Phase 4 step 10 of /app-mcp-implement).validate_automation on the changed automations. Must be clean (webhook "no arguments" warnings are fine).push_workspace per connector to its env (the one discovered in Phase 1). Use a short message (tmpl-sync, add-204-guard, …, ≤15 chars). Re-run the script on that connector to confirm the drift is gone.Push caution: push_workspace re-imports the whole tree and re-runs the App-publish (expect the benign Missing photo only if photo is empty). Any connector carrying a React/bundle artifact in config.value must keep it (none of the current connectors do, but verify — see memory feedback_app_dev_patch_replaces_config / feedback_ui_deploy_resets_bundle). Push connectors one at a time, confirming each before the next — never bulk-push the fleet blind.
app-mcp label (bootstrap, once)The discovery convention only works once the label exists. As part of the first run:
R1-label), add app-mcp to index.yml labels: (keep the existing labels — MCP, production:app, the service name, etc.), then push_workspace. This is itself one of the 🔴 fixes from Phase 5, so fold it into that push rather than a separate one./app-mcp-implement skill has been patched to stamp app-mcp into the labels of every create_workspace call, so new connectors are tagged automatically — no future bootstrap needed.After this run, Phase 1's label-first discovery returns the full fleet on its own.
security.yml (webdav adds roles), formatToolOutput.yml (per-service messages) carry intentional local content. Transplant template-ahead hunks; preserve workspace-ahead ones.app-mcp, add the rule to scripts/audit_connector.py rather than carrying it only in prose — that's how the fleet stays checkable.imports/MCP Core.yml is the runtime source for tools/list, not index.yml. Any mcpTools change must update both, then push (push = sync). Never add triggerSync.yml (see memory feedback_mcp_core_dispatcher_incompatible).type: array parameter or one # in a code: block breaks the entire module silently (every function "not found"). These are R6/R7 MAJORs — fix on sight.app-mcp label is now seeded across the fleet.npx claudepluginhub prismeai/prismeai-mcp --plugin prisme-aiGuides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.