fw-publish
Publish (Platform 3.0 custom app)
MANDATORY PREREQUISITE: Run fw-review skill before publishing to ensure marketplace compliance. The review checks iparams, frontend files, security patterns, and generates a structured audit report. Do not proceed with publishing until review passes.
Goal: Take any Platform 3.0 app folder (with manifest.json), produce dist/*.zip, and publish it to the Freshworks Marketplace via MCP tools. Currently, only test state is supported.
Sandbox / agent egress: Publish does not work in sandbox (or equivalent) modes that block MCP traffic to https://mcp.freshworks.dev/mcp or outbound HTTPS (including curl PUT to the app-upload URL). Some cloud or CI agent runtimes send HTTPS through a proxy, gateway, or assumed IAM role that is not allowed to PUT to the Marketplace app-upload bucket (fa-*-app-uploads…): S3 returns 403 / AccessDenied even when the presigned URL is valid. Do not run this playbook in a sandboxed agent or restricted shell. If the user’s client offers sandbox vs full network access, choose non-sandbox / allow network for publish. On 403 PUT after following step 8 exactly, ask the user to run the same script command on their local machine (see step 8).
Agent playbook (MCP tools)
Tool names and parameters match Freshworks openai-server (references/openai-server-mcp-tools.md). Hosted MCP: https://mcp.freshworks.dev/mcp.
1. Auth token preflight (MUST be step 1)
Before any MCP tool call, verify that the MCP publish tools are available and authenticated:
- Attempt to call
list_custom_apps(optionally{}or{ "page": 1, "perPage": 10 }). Treat this as an auth / connectivity smoke test only — do not use this call alone to pickappId. At publish time (step 6), you will ask new vs existing again and, for existing, calllist_custom_appsfor developer selection. - If the call succeeds, auth is confirmed — proceed to step 2.
- If tools are not available or the call returns an auth error, determine which case applies:
Case A — MCP server already configured, token missing or expired:
The MCP server is set up but the API key needs to be refreshed or was never set.
- Go to https://developers.freshworks.com/developer/
- Under "API key for Freddy AI Copilot VS Code plugin & AI Developer Tools" → click "View API Key"
- Under "Connect to Freddy AI Copilot MCP server" → select your IDE tab → click Copy
- Update the token in your IDE’s MCP settings and restart/reload the MCP connection
- Re-run the publish command
Case B — MCP server not configured yet:
-
Under "Connect to Freddy AI Copilot MCP server" → select your IDE tab (Cursor or VS Code)
-
Cursor: Click "Install in Cursor" directly, or manually add to
~/.cursor/mcp.json:{ "mcpServers": { "fw-dev-mcp": { "url": "https://mcp.freshworks.dev/mcp", "headers": { "Authorization": "Bearer <your-api-key>" } } } }Replace
<your-api-key>with the key copied in step 2, then restart Cursor.Claude Code (via plugin): The freshworks plugin prompts for the API key at install time. If you skipped it, run
/configand update the plugin settings. The key is stored securely in the system keychain.Claude Code (standalone skill, no plugin): Add the server to
.mcp.jsonat your project root (or add viaclaude mcp addwith--scope userto store it globally in~/.claude.json):{ "mcpServers": { "fw-dev-mcp": { "url": "https://mcp.freshworks.dev/mcp", "headers": { "Authorization": "Bearer <your-api-key>" } } } }Replace
<your-api-key>with the key copied in step 2, then restart Claude Code. -
Re-run the publish command
DO NOT proceed with any publish step until auth is confirmed.
2. Determine app directory
Use the same steps as fw-app-dev /fdk-fix Step 1 — see ../fw-app-dev/commands/fdk-fix.md (Determine app directory):
- Search the workspace for
manifest.jsonfiles. - If multiple folders contain manifest.json: Ask the user which app to publish.
- If one folder: Use that directory.
- If none: Inform the user and stop.
2.5 Pre-publish: confirm API key is for the right product
The Developer API key is product-specific. Ask the user to confirm their configured API key matches the product they are publishing this app to. STOP if they are unsure — they need to verify or update the key before continuing.
3. Check Node.js and FDK versions (before pack)
- Read
engines.nodeandengines.fdkfrommanifest.jsonin the app directory - Check active versions:
node --versionandfdk --version - If
fdkis missing (fdk --versionfails / command not found): STOP. Do not auto-install or assume “latest FDK” without asking. Tell the user the Freshworks CLI is required forfdk validate/fdk pack. Offerfw-setup:/fw-setup-install(default FDK 10.x on Node 24.11). Optional one-shot: “Run/fw-setup-installnow? (y/n)” — only on yes, followskills/fw-setup/SKILL.md; on no, end until the user installs manually. Do not continue to step 4 untilfdkis available (unless the user explicitly overrides with understanding of the risk). - If versions mismatch (but
fdkis present), STOP and inform user:Your app requires Node.js X.Y.Z and FDK A.B.C (from manifest.json engines). Current environment: Node vW.X.Y, FDK vP.Q.R Would you like me to install/switch to the required versions? (yes/no) If yes, I'll use the fw-setup skill to: - Install Node.js X.Y.Z (if not present) and switch to it - Install/upgrade to FDK A.B.C If no, you can manually run: - /fw-setup-use (in app directory) - switches Node version - /fw-setup-install --version A.B.C - installs FDK version - /fw-setup-upgrade --to A.B.C - upgrades FDK version - DO NOT proceed with
fdk packuntil versions match or user explicitly overrides
4. fdk validate (pre-publish)
Run cd <app-directory> && fdk validate and treat the result as the validity gate for upload:
- Required for any upload/submit: zero platform errors and zero lint errors (same bar as fw-app-dev). If either fails, STOP — use the fw-app-dev skill to fix; do not call
create_app_upload_urlor upload a zip. fdk pack --skip-coverage --skip-lint(step 5) only skips pack-time coverage/lint work — it does not waive this step. Never infer “app is valid” from pack alone.
Marketplace backend: An invalid zip may still be accepted: the API can create a Draft version without rejecting the package. Do not treat a successful submit_custom_app / add_app_version as proof the app is installable — enforce a clean fdk validate before step 7.
5. fdk pack
From the app directory (non-interactive; skips pack-time coverage/lint so automation does not block on coverage):
cd <app-directory> && printf 'Y\n' | fdk pack --skip-coverage --skip-lint
Produces dist/*.zip. Reuse an existing zip only if --force-pack is not needed (agent judgment).
Invalid apps: Do not pass invalid builds through the pipeline. If step 4 did not pass with zero platform and zero lint errors, STOP — do not run fdk pack for this publish flow and do not continue to steps 6–13. (--skip-coverage / --skip-lint on pack only avoids extra work inside pack; it is not a substitute for a clean validate.)
Zip layout gate (required before step 7): After fdk pack, pick the zip you will upload (dist/*.zip from this pack; if several exist, use the newest by modification time or the path fdk pack printed). Run:
unzip -l 'dist/<app>.zip'
Inspect the Name column (last column of each file row):
- Pass — continue: At least one archive member is named exactly
manifest.jsonat the root of the zip (not only under a subfolder). - Fail — STOP; do not call
create_app_upload_url:manifest.jsonis missing, or only./manifest.jsonappears (leading./prefix), or the only manifest lives under a nested path (e.g.some-folder/manifest.json) without a rootmanifest.json. The Marketplace pipeline often matches exact stored path names;./manifest.jsonis not treated the same asmanifest.jsonfor those checks.
If the gate fails — remediation:
- Run
fdk packagain from<app-directory>(same as above). - If the listing still fails the gate: unpack to a clean directory and re-zip with explicit top-level members (avoid
zip -r … ., which commonly records./prefixes). Example (adjust folder names to match the unpacked tree):
rm -rf /tmp/fw-repack && mkdir -p /tmp/fw-repack && unzip -q -o 'dist/<app>.zip' -d /tmp/fw-repack
cd /tmp/fw-repack && zip -r '<app-directory>/dist/<app>-resubmit.zip' manifest.json app config server README.md
List only paths that exist after unzip (omit server, README.md, etc. if absent). Add any other top-level files or directories the app needs. Re-run unzip -l until the gate passes, then upload that zip in step 8.
6. Publish-time routing: new listing vs existing app (MCP handover)
Do this at publish time — after you have a valid zip that passes the zip layout gate (steps 4–5) and before create_app_upload_url (step 7). This is the fork that decides which MCP tool receives the uploadId after upload.
Do not read appId from .fdk/app-info.json for routing or MCP calls.
-
Ask explicitly: Is this publish a new Marketplace listing, or an update to an existing app? (Skip only if the user already stated the same in this session.)
-
New listing: No
appIdyet. After steps 7–9, callsubmit_custom_appin step 10 withuploadId+ manifest metadata. MCP handover: new-app payload + presigneduploadIdonly. -
Existing app (update):
a. Call
list_custom_apps(paginate if needed). Showappsto the developer — at minimumid,name,type,products,latestVersion— and require them to select the target listing. Record thatappId.b. Check for stuck latest version: Call
list_app_versionswith the selectedappId. Check only the latest version (most recent byupdatedAt).- If the latest version has
state: "development", STOP and inform the user:Cannot publish — the latest version is stuck in "development" state. Version: [id, version, state] This usually means a previous deployment failed. Please: 1. Go to https://developers.freshworks.com/developer/ 2. Navigate to your app and delete or resolve the stuck version 3. Return here and retry - If the latest version is in any other state, proceed to step 7.
c. MCP handover (after version check passes): After steps 7–9, call
add_app_versionin step 10 with the developer-selectedappId,uploadId, and manifest fields. - If the latest version has
-
If they chose update but the list is empty, no listing exists — offer new listing or cancel.
Optional: if only one app exists and they already chose update, show that row and ask for a one-line confirm before using its appId — still never assume appId from .fdk/app-info.json.
6.5 Support email — mandatory before MCP upload chain (step 7)
Gate: Do not call create_app_upload_url (step 7), submit_custom_app, or start the presigned zip upload until this step passes for the relevant publish path.
Missing supportEmail does not always fail at PUT upload — it fails later when calling submit_custom_app (step 10), after uploadId is consumed and the zip is already on storage. Users often describe that as an “app-upload” / publish failure. Collect supportEmail early so the MCP submit payload is complete before minting uploadId.
| Publish path (from step 6) | Requirement |
|---|---|
| New listing | submit_custom_app requires supportEmail. Prompt the user explicitly for a valid, monitored Marketplace support address before step 7. Do not infer from git config user.email (may be missing, personal, or wrong). If manifest or docs mention a contact, confirm it with the user. Store the confirmed value for step 10. If the user cannot provide supportEmail, STOP — do not proceed to create_app_upload_url. |
| Existing app (update) | add_app_version uses appId, platformVersion, modules, uploadId — supportEmail is not part of the usual add_app_version payload. No mandatory email prompt for this path unless product/API rules change. |
7. Create app-upload URL
Prerequisite: Step 6.5 satisfied for new listing ( supportEmail confirmed before this call).
Call create_app_upload_url — returns uploadId + uploadUrl + expiresInSeconds.
- Retain
uploadIdfor step 10 (submit_custom_app/add_app_version) - Immediately write the entire JSON response to a temp file — do not extract or re-emit individual fields:
Treat the response as an opaque blob. The script will parseecho ‘<full-json-response>’ > /tmp/fw-upload-response.jsonuploadUrlfrom it — the LLM never handles the URL directly.
8. App-upload (PUT zip binary)
Use the bundled upload script with the response file from step 7. The script extracts uploadUrl via jq internally — the LLM never touches the URL. Do not substitute Python (urllib.request, requests, …), Node (fetch / node -e), or any other HTTP client — those environments often hit 403 in managed/cloud runtimes even with a valid URL.
- Use the same
dist/*.zipfile that passed the zip layout gate (step 5), including…-resubmit.zipif you rebuilt it there. - Pass the response file from step 7 — do not read, parse, or echo its contents.
- Prefer running this on the user’s machine (local Terminal / IDE terminal with full network, not a locked-down remote worker).
- The script retries automatically up to 3 times. On final failure, call
create_app_upload_urlagain for a fresh response and re-run the script.
bash <skill-root>/scripts/upload-app.sh dist/<app>.zip /tmp/fw-upload-response.json
<skill-root>— directory wherefw-publishskill is installed (e.g.skills/fw-publishin the repo)/tmp/fw-upload-response.json— the response file written in step 7; script extractsuploadUrlviajq- The script sends
Content-Type: application/zipand printsUpload successful (HTTP 200)on success
Auto-run / sandbox: Restricted sandboxes often cause upload 403 or network failures — use non-sandbox / full network for this step (see Sandbox at top).
Do not base64-encode the zip.
9. Read manifest.json
Read manifest.json in the app directory. Extract:
platform-version(e.g."3.0")moduleskeys (e.g.["common", "support_ticket"])name(if present) forappName
10. Call the appropriate MCP tool (deploy / version handover)
Use the publish-time choice from step 6: new → submit_custom_app; existing → add_app_version with the developer-selected appId.
New app — submit_custom_app:
| Parameter | Source |
|---|---|
appName |
manifest name or directory name |
appDescription |
ask user or default |
appOverview |
ask user or derive from description (max 150 chars) |
supportEmail |
Required for new app — must already be collected in step 6.5 before create_app_upload_url. Never use git config user.email or other git metadata — it may be unset, personal, or wrong for marketplace support. |
alternateEmail |
optional |
platformVersion |
manifest platform-version |
modules |
manifest modules keys (see openai-server tool schema — at least one non-common module may be required) |
uploadId |
from step 7 |
targetState |
"test" (default) |
zipFileName |
optional (e.g. my-app.zip) |
worksWith |
optional; include "ai_actions" if AI Actions app |
Existing app — add_app_version (when available on MCP):
| Parameter | Source |
|---|---|
appId |
Developer-selected appId from list_custom_apps (step 6) — not from .fdk/app-info.json |
platformVersion |
manifest platform-version |
modules |
manifest modules keys |
uploadId |
from step 7 |
targetState |
"test" (default) |
zipFileName |
optional |
worksWith |
optional |
11. Persist app identity (optional, local only)
You may write or update .fdk/app-info.json in the app directory with id and version from the response as a local record. The next publish still follows step 6 — list_custom_apps + developer selection for updates — do not skip listing or rely on this file for appId.
12. Verify status
Call get_app_status with the appId returned from submit/update (or the selected listing id) to confirm app-level state.
Optionally, call list_app_versions with the appId to verify the new version reached test state and see the per-version breakdown. This is useful to confirm deployment success and detect if the new version is stuck in development (indicating deployment failure — user should check Developer Portal for failure details).
13. Report to user
Tell the user: app id, version state, and where to install custom apps in their product (Admin -> Apps or equivalent).
MCP tools reference (fw-dev-mcp)
Supported app states: Currently only test state is supported for publishing.
| Tool | Purpose | When to Use |
|---|---|---|
list_custom_apps |
List all custom apps on developer account. Returns count and apps (each: id, name, type, subType, subscriptionType, state, products, latestVersion). Optional page, perPage. Results sorted by most recently updated first. |
Step 1 (auth preflight), Step 6.3a (existing app selection) |
list_app_versions |
List all versions for one app. Returns array with id, version, platformVersion, state, updatedAt per version. |
Step 6.3b (check latest version for development state before add_app_version), Step 12 (optional verification) |
create_app_upload_url |
Generate presigned S3 upload URL. Returns uploadId, uploadUrl, httpMethod ("PUT"), expiresInSeconds. |
Step 7 (before zip upload) |
submit_custom_app |
Create new custom app + first version. Requires appName, appDescription, appOverview, supportEmail, platformVersion, modules, uploadId. Collect supportEmail before create_app_upload_url (step 6.5). Optional: alternateEmail, zipFileName, worksWith (e.g., ["ai_actions"]). App moves to test state after successful submit. |
Step 10 (new app path) |
add_app_version |
Add new version to existing app. Requires appId (from list_custom_apps + user selection), platformVersion, modules, uploadId. Optional: zipFileName, worksWith. CANNOT proceed if the latest version is in development state (check via list_app_versions first; user must delete the stuck version via Developer Portal). |
Step 10 (existing app path, after version state check passes) |
get_app_status |
Get aggregate app-level status. Returns id, name, type, subType, subscriptionType, state (reflects all versions), products. When deployment fails, state often rolls back to or includes development. |
Step 12 (post-publish verification) |
Other tools on fw-dev-mcp server:
get_developer_docs: Fetch developer documentation. FALLBACK ONLY - use only if fw-app-dev skill fails or when skill explicitly delegates.- DEPRECATED (do NOT use):
implement_app,get_implementation_plan,idea_to_app,fix_app_errors. Always use fw-app-dev skill for app development work.
Error handling
- 401/403 from any MCP tool: STOP immediately and show the auth setup instructions from step 1. The token may be expired, misconfigured, or missing. Do not retry — prompt the user to fix their token and re-run.
- Validation errors (400): Suggest manifest fixes or use fw-app-dev skill. Common: products vs modules mismatch.
- Upload fails after 3 retries (script in step 8): Do not retry the upload script again. Ask the user to verify: (1) running on their local machine (not sandboxed/restricted environment), (2) network access to S3 is not blocked (no proxy or IAM policies). If both confirmed, restart from step 7 — call
create_app_upload_urlfor a fresh response file and re-run the upload script. If it fails again, stop — persistent failures indicate environment or infrastructure issues. - fdk validate / fdk pack failures: Use fw-app-dev skill to fix; check Node/FDK version alignment. Do not upload if validate did not pass (step 4) — draft listings can still be created from bad zips.
- Manifest / package layout errors after upload or submit: Re-run the Zip layout gate (end of step 5). If
./manifest.jsonappears without rootmanifest.json, repack per step 5 remediation beforecreate_app_upload_url.
Preconditions
| Requirement | Notes |
|---|---|
| Upload host | Script must reach S3 for the presigned bucket; cloud/restricted environments may always return 403 — use local terminal or unconstrained network (step 8). |
| Non-sandbox execution | MCP + script upload need outbound HTTPS; sandboxed agents/shells typically break publish — use full network / disable sandbox for this flow. |
manifest.json |
App root; must be Platform 3.0 with modules. |
| Zip member names | After fdk pack, the upload zip must list manifest.json at archive root (not only ./manifest.json). See Zip layout gate at end of step 5. |
fdk on PATH |
fdk validate + fdk pack. |
| MCP tools configured | Claude Code: from root .mcp.json when the marketplace plugin is installed (prompted at install via userConfig). Cursor: merge that file’s server block into ~/.cursor/mcp.json. |
| Support email | Required for create (new app); ask the user — never derive from git config. Updates reuse publisher metadata from the existing marketplace app. |
| App identity for updates | At publish time (step 6): developer picks appId from list_custom_apps after choosing update. Do not use .fdk/app-info.json for routing. |
Optional: list apps
For updates, list_custom_apps is part of step 6 at publish time (developer selects appId before create_app_upload_url). You may also call it anytime to inspect apps on the account without publishing — that browse call is separate from the publish-time selection and appId handover to add_app_version.
Links
-
references/openai-server-mcp-tools.md— MCP tools implemented inmp-openaiopenai-server -
Developer Portal — copy API key: developers.freshworks.com/developer/ (API key for Freddy AI Copilot for VS Code plugin & AI Developer Tools. → Connect to Developer MCP server)
-
Marketplace API overview (public): api.freshworks.com/marketplace/v2