@shipeasy/mcp
OfficialClick on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@@shipeasy/mcplist all feature flags for production"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
@shipeasy/mcp — unified Model Context Protocol server
Replaces packages/mcp-server/. One MCP server, one npm package, covers both subsystems:
Experimentation — gates, configs, experiments, metrics, events (from experiment-platform/10-mcp-server.md).
String manager / i18n — label profiles, keys, drafts, translations, publish, codemods (from string-manager-platform/plan.md § MCP server).
AI assistants (Claude Code, Cursor, Copilot, Windsurf, Claude Desktop, Continue) talk to one server and get the full platform.
Status / migration
New location:
packages/mcp/(this doc).Old location:
packages/mcp-server/— will be deleted once this ships. The npm package rename (@shipeasy/mcp-server→@shipeasy/mcp) is a breaking change; the old name publishes one final version that re-exports the new package as a deprecation shim.Binary rename:
shipeasy-mcp(unchanged — the old binary name is preserved so existing.claude/settings.jsonentries keep working).
Install / run
Two steps: (1) register the server with your AI assistant, (2) authenticate once with shipeasy-mcp install. Step 2 is required before any mutating tool will work — it runs a browser-based PKCE device flow against /auth/device/* and writes the token to ~/.config/shipeasy/config.json (shared with @shipeasy/cli).
Step 1 — register
npx (recommended for AI assistant configs)
// ~/.claude/settings.json | .cursor/mcp.json | .windsurf/mcp.json | .mcp.json (project-local)
{
"mcpServers": {
"shipeasy": {
"command": "npx",
"args": ["-y", "@shipeasy/mcp@latest"]
}
}
}Global install
npm i -g @shipeasy/mcp
# or
pnpm add -g @shipeasy/mcp{ "mcpServers": { "shipeasy": { "command": "shipeasy-mcp" } } }Through the CLI (if @shipeasy/cli is already installed)
shipeasy mcp install # auto-writes ~/.claude/settings.json + .cursor/mcp.json
shipeasy mcp start # run stdio server (same binary, different entry)Step 2 — authenticate
Run this once per machine:
shipeasy-mcp installWhat happens:
The CLI generates a PKCE verifier + challenge.
It calls
POST {api_base}/auth/device/startto open a session; the worker returns astate.Your default browser opens at
{app_base}/cli-auth?state=…&code_challenge=…&source=mcp. Sign in with your existing Shipeasy account (GitHub, Google, or magic link — same as the dashboard).The UI page calls
POST {api_base}/auth/device/completewithproject_id+ PKCE verifier.The CLI polls
GET {api_base}/auth/device/poll?state=…(headerX-Code-Verifier) every ~2 s until it receives{ token, project_id }.Token is written to
~/.config/shipeasy/config.jsonwithchmod 600. The directory is createdchmod 700.
Flags:
Flag | Effect |
| Overwrite an existing session instead of aborting. |
| Print the auth URL; useful on remote / headless machines (paste it into a local browser). |
| Override worker URL. Defaults to |
| Override UI URL. Defaults to |
Other subcommands:
shipeasy-mcp whoami # prints { project_id, user_email, config_path }
shipeasy-mcp logout # deletes ~/.config/shipeasy/config.json
shipeasy-mcp --help # usage
shipeasy-mcp --versionWhy is this a CLI subcommand instead of an MCP tool?
The MCP stdio transport runs inside the AI assistant — it can't block for a browser round-trip, spawn new windows, or receive a browser-delivered callback. Browser-based auth has to run in a terminal the user owns. Once the token is written, every MCP server instance on the machine (Claude Code, Cursor, Windsurf, MCP Inspector, etc.) reads the same ~/.config/shipeasy/config.json — one install, many clients.
The in-process auth_login MCP tool therefore always returns a pointer back to the CLI command rather than trying to launch a browser itself.
Manual stdio invocation (for testing)
npx -y @shipeasy/mcp
# or pipe it through MCP Inspector:
npx @modelcontextprotocol/inspector npx -y @shipeasy/mcpProtocol surface
Transport: stdio (JSON-RPC 2.0 framed by Content-Length headers per MCP spec). Capabilities advertised on initialize:
{
"tools": { "listChanged": true },
"prompts": { "listChanged": false },
"resources": { "subscribe": true, "listChanged": true },
"logging": {}
}Tools — actions the assistant can invoke (the full catalog below).
Prompts — named, parameterized workflow playbooks (see Prompts section).
Resources — read-only views of project state the assistant can pull in as context (see Resources section).
Logging — structured progress and error notifications via
notifications/message.
Authentication
Every mutating tool requires a Shipeasy session. Credentials live in ~/.config/shipeasy/config.json and are shared between @shipeasy/mcp and @shipeasy/cli — whichever tool the user authenticates in first, both pick up the same session.
shipeasy-mcp install(terminal) completes the PKCE device flow and writes the config file (see Step 2 — authenticate above).The MCP stdio server's
auth_checktool reads the file on every call — no cached state in the server process.auth_logininvoked over MCP always returns an actionable error asking the human to runshipeasy-mcp installin a terminal (stdio can't open a browser safely).auth_logoutremoves the file; the CLI equivalent works too.
~/.config/shipeasy/config.json (mode 0600, parent dir 0700)
{
"project_id": "proj_…",
"cli_token": "sdk_admin_…", ← scoped to admin Route Handlers; 90-day rotation
"api_base_url": "https://api.shipeasy.ai",
"app_base_url": "https://app.shipeasy.ai",
"user_email": "you@example.com",
"created_at": "2026-04-16T…Z"
}Tool-level auth policy:
Category | Requires session | Notes |
| No | Pure filesystem inspection, no network. |
| — / triggers it | |
| Yes | Read-only GETs against |
| Yes | Mutations — the CLI enforces |
| No | Local-only AST tools. |
Tool catalog
Tools are namespaced by subsystem: exp_* (experimentation), i18n_* (string manager), unprefixed (shared: auth, project detection, resource listing, SDK snippets).
Shared tools
detect_project
Inspects the working directory and returns the language, framework, package manager, installed shipeasy packages, and i18n presence signals.
Input (all optional):
{ "path": "string (defaults to cwd; sandboxed via realpath to refuse escapes)" }Output:
{
"language": "typescript | javascript | python | ruby | go | java | php | swift | kotlin | unknown",
"frameworks": ["nextjs","react","tailwind", ...],
"package_manager":"npm | pnpm | yarn | bun | pip | poetry | bundler | go | maven | gradle | composer | swiftpm",
"entry_points": ["src/app/layout.tsx", "src/main.tsx"],
"test_files": ["src/app/page.test.tsx"],
"shipeasy": {
"experimentation_sdk": { "installed": true, "version": "^1.3.0", "configured": true, "subentry": "shipeasy/react" },
"i18n_sdk": { "installed": true, "version": "^1.1.0", "configured": true, "profile": "en:prod" },
"loader_script_tag": { "present": true, "data_key": "sdk_client_…", "data_profile": "en:prod" },
"env_keys_detected": ["SHIPEASY_SERVER_KEY","NEXT_PUBLIC_SHIPEASY_CLIENT_KEY"],
"template_warning": "Installed SDK version 0.9.x is incompatible with MCP templates >=1.0.0."
}
}Security: realpath sandbox (see 10-mcp-server.md § detect_project). All reads go through safeRead() which refuses symlinks pointing outside the requested root.
auth_check
// input
{}
// output
{ "authenticated": true, "project_id": "…", "base_url": "…", "user_email": "…" }auth_login
Spawns shipeasy login, which opens the browser and blocks until the device-auth flow completes. Uses spawn (not execSync) so the MCP event loop stays responsive. The AI assistant should surface a "waiting for browser…" message — the CLI session polls for up to 5 minutes.
// input
{}
// output — same shape as auth_check after successauth_logout
Deletes ~/.config/shipeasy/config.json. No network call.
list_resources
Unified listing across both subsystems.
// input
{
"kind": "gates|configs|experiments|events|metrics|universes|attributes|profiles|chunks|keys|drafts|sdk_keys|all",
"limit": 50,
"search": "checkout" // optional name filter
}Hits the matching apps/ui admin Route Handler (e.g. /api/admin/gates, /api/admin/i18n/profiles) and returns a normalized list:
{
"kind": "experiments",
"items": [
{ "id": "…", "name": "checkout_button_color", "universe": "checkout", "status": "running", "allocation": 10 },
...
],
"next_cursor": null
}get_resource
Fetches a single resource by { kind, name_or_id }. Same routing as list_resources.
get_sdk_snippet
Returns ready-to-paste code for the detected language + framework, for either subsystem.
// input
{
"domain": "experiment | i18n",
"language": "typescript | python | ruby | go | java | php | swift | kotlin",
"framework": "nextjs | react | remix | vue | svelte | angular | nuxt | django | rails | laravel | spring | swiftui | compose | ... | null",
"type": "gate | experiment | config | label_load | label_render | loader_script | provider_setup",
"name": "new_checkout",
"params": { "color": "string" },
"success_event": "purchase_completed",
"success_value": true
}Output:
{
"install": "pnpm add shipeasy",
"env_vars": ["SHIPEASY_SERVER_KEY", "NEXT_PUBLIC_SHIPEASY_CLIENT_KEY"],
"init": "…code block…",
"usage": "…code block…",
"tracking": "…code block (only for experiments)…",
"validate_command": "pnpm tsc --noEmit",
"docs_url": "https://docs.shipeasy.ai/sdk/typescript/next"
}Templates are loaded from the installed SDK package (shipeasy/templates/<language>.js), not from this MCP bundle — so they track the customer's SDK version. Falls back to bundled templates if the SDK has no templates/ export or isn't installed yet. See packages/language_sdks/README.md for the source-of-truth template files per language.
list_prompts / get_prompt
Standard MCP — see Prompts section below. These are built into @modelcontextprotocol/sdk.
Experimentation tools (exp_*)
All mutations shell out to @shipeasy/cli via execFile (never exec with shell interpolation) with validated, slugified arguments. Names are auto-slugged before validation (SAFE_NAME_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/) and a warning is logged if auto-slugging changed the input.
Tool | What it does | Shells to |
| Create a feature gate with targeting rules and rollout percentage |
|
| Update rules, rollout, killswitch |
|
|
| |
| Create a static config (sitevar) |
|
| Update the live value |
|
| Create a universe with holdout % |
|
| Create experiment draft with groups, params, targeting gate |
|
| Transition draft → running |
|
| Transition running → stopped, promote winning group |
|
| Attach a metric as goal/guardrail |
|
| Register an event schema |
|
| Create a metric from an event |
|
| Current results + ship/hold/wait verdict |
|
| AST-drop losing branches after shipping | Local codemod via |
Representative input — exp_create_experiment:
{
"name": "checkout_button_color",
"description": "Test green vs. gray on new checkout",
"universe": "checkout",
"allocation": 10,
"groups": [
{ "name": "control", "weight": 5000, "params": { "color": "gray" } },
{ "name": "test", "weight": 5000, "params": { "color": "green" } }
],
"params_schema": { "color": "string" },
"targeting_gate": "new_checkout",
"success_event": "purchase_completed",
"success_aggregation": "count_users"
}Output:
{
"experiment": {
"name": "checkout_button_color",
"id": "exp_…",
"status": "draft",
"universe": "checkout"
},
"metric": { "name": "checkout_button_color_purchase_completed", "status": "created" },
"snippet": {
/* same shape as get_sdk_snippet */
},
"docs_url": "https://docs.shipeasy.ai/experiments/create"
}String manager tools (i18n_*)
Absorbs the packages/mcp-server/src/tools/i18n/ sketch from string-manager-platform/plan.md.
Tool | What it does | Backed by |
| Walk the repo AST, return candidate JSX text / string literals / template strings that look translatable | Local (jscodeshift / ast-grep / Python ast) |
| Fetch a URL, parse |
|
| List profiles, chunks, coverage |
|
| Create a new locale profile, e.g. |
|
| Create a chunk inside a profile |
|
| Create/update a single label key |
|
| Bulk upload a JSON file of keys to a chunk |
|
| Download published strings to local disk |
|
| Clone a source profile into a draft (optionally pre-translated) |
|
| Run Anthropic translation on a draft (operator's API key, zero shipeasy-side AI) |
|
| Edit a single key in a draft |
|
| Publish a chunk or whole profile — rebuilds KV manifest, purges CDN |
|
| Monthly loader request count + per-chunk breakdown |
|
| AST-transform framework code (Next.js / React / Vue / Rails / Django) to wrap strings in | Local codemods |
| Same as preview, but writes the diff (requires | Local codemods |
| Pre-commit check: every code-side key exists server-side |
|
| Emit the | Local |
Representative input — i18n_translate_draft:
{
"draft_id": "draft_…",
"source_profile": "en:prod",
"target_profile": "fr:prod",
"glossary": [
{ "term": "ShipEasy", "policy": "keep" },
{ "term": "Patient", "policy": "translate_as", "fr": "Patient" }
],
"anthropic_api_key_env": "ANTHROPIC_API_KEY",
"max_parallel": 4
}Calls Anthropic from the user's machine (never from shipeasy servers). Writes progress via MCP notifications/progress. Returns { draft_id, translated_key_count, failed_key_count, review_url }.
Representative input — i18n_codemod_preview:
{
"framework": "nextjs",
"files": ["src/app/dashboard/page.tsx"],
"strategy": "jsx_text_literals",
"key_prefix": "dashboard."
}Output is a diff list; the AI shows it to the user and then calls i18n_codemod_apply with confirm: true only once the user approves.
Prompts (workflow skills)
MCP prompts expose named, parameterized playbooks the AI can get_prompt() to load as context. Mirrors the skills bundle in experiment-platform/11-skills.md and string-manager-platform/plan.md § Skills.
Prompt name | Purpose |
| Install the SDK, add env keys, wire a provider, verify with a sample gate |
| Propose → create → inject code → start → monitor |
| Pull results, compute lift + significance, emit ship/hold/wait verdict |
| Remove losing branches + dead gate code after shipping |
| Install SDK + loader script, create |
| Given a URL, discover, add target locale, translate, review, publish |
| Report missing keys, unused keys, drift between profiles |
| Revoke + re-issue client/server keys and update env vars |
Each prompt's body is a short markdown playbook embedded in the server bundle. The assistant fetches it once per conversation with get_prompt({ name }) and follows the steps.
Resources
Read-only project context streamed to the assistant via MCP's resources/read. The server advertises resource templates (URI patterns) so the assistant can pull context on demand without the user needing to paste files.
URI template | Returns |
| Cached |
| Experiment config + latest stats JSON. |
| Gate config + rollout state. |
| Config value + history. |
| Profile metadata + chunk list + coverage %. |
| Published strings for one chunk. |
| Draft metadata + per-key diff vs. source profile. |
| Plan tier + current-month usage + remaining quota. |
| Pre-rendered markdown page from |
resources/subscribe is supported on shipeasy://experiments/{name} — the server pushes notifications/resources/updated when cron finishes a new analysis run (detected via long-poll on /api/admin/experiments/:name/results?since=ts).
Package structure
packages/mcp/
package.json ← name: "@shipeasy/mcp", bin: "shipeasy-mcp"
tsconfig.json
tsup.config.ts ← esm output, single bundle
bin/
mcp.js ← shebang → runs dist/index.js
src/
index.ts ← Server setup, capability advertise, tool routing
rpc/
list-tools.ts
call-tool.ts
list-prompts.ts
get-prompt.ts
list-resources.ts
read-resource.ts
subscribe-resource.ts
tools/
schema.ts ← TOOLS array — MCP tool definitions
shared/
detect.ts ← detect_project (with realpath sandbox)
auth.ts ← auth_check, auth_login, auth_logout
list-resource.ts ← list_resources, get_resource
snippets.ts ← get_sdk_snippet + template loader
exp/
gates.ts
configs.ts
universes.ts
experiments.ts
events.ts
metrics.ts
status.ts
cleanup.ts
i18n/
scan.ts ← i18n_scan_code (ast-grep driver)
discover.ts ← i18n_discover_site
profiles.ts
chunks.ts
keys.ts
drafts.ts
translate.ts ← i18n_translate_draft (Anthropic shell-out)
publish.ts
usage.ts
codemods/
nextjs.ts
react.ts
vue.ts
svelte.ts
angular.ts
rails.ts
django.ts
index.ts ← dispatcher for i18n_codemod_preview/apply
validate.ts
loader.ts ← i18n_install_loader
prompts/
schema.ts ← PROMPTS array
setup_experimentation.md
create_experiment.md
analyze_experiment.md
cleanup_winner.md
setup_i18n.md
translate_site.md
i18n_health.md
rotate_sdk_keys.md
resources/
schema.ts ← RESOURCE_TEMPLATES
project.ts
experiments.ts
gates.ts
configs.ts
i18n.ts
plans.ts
docs.ts
util/
cli.ts ← execFile wrapper with shared error decoding
http.ts ← fetch wrapper w/ cli_token header
slug.ts ← autoSlug + SAFE_NAME_RE
safe-read.ts ← realpath-sandboxed fs reads
progress.ts ← notifications/progress helper
logger.ts ← notifications/message helper (respects client log level)
compat.ts ← semver compatibility check per language
templates/ ← fallback snippets when SDK has no templates/ export
typescript.ts
python.ts
ruby.ts
go.ts
java.ts
php.ts
swift.ts
kotlin.ts
test/
rpc/*.test.ts ← each request handler covered
tools/exp/*.test.ts
tools/i18n/*.test.ts
tools/shared/detect.test.ts ← realpath sandbox edge cases
fixtures/
projects/
nextjs-with-sdk/
django-clean/
rails-with-i18n/
astro-plain/package.json
{
"name": "@shipeasy/mcp",
"version": "1.0.0",
"description": "Model Context Protocol server for the Shipeasy platform (experimentation + i18n)",
"keywords": [
"mcp",
"model-context-protocol",
"shipeasy",
"feature-flags",
"experimentation",
"i18n",
"ai"
],
"type": "module",
"bin": { "shipeasy-mcp": "./bin/mcp.js" },
"files": ["bin/", "dist/", "src/prompts/*.md"],
"engines": { "node": ">=20" },
"scripts": {
"build": "tsup src/index.ts --format esm --dts --clean",
"type-check": "tsc --noEmit",
"test": "vitest",
"prepublishOnly": "pnpm build"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0",
"@shipeasy/sdk": "workspace:*",
"semver": "^7.6.0",
"zod": "^3.23.0",
"conf": "^13.0.0",
"@ast-grep/napi": "^0.22.0"
},
"devDependencies": {
"@types/node": "^20.19.0",
"@types/semver": "^7.5.0",
"tsup": "^8.3.0",
"typescript": "^5.7.0",
"vitest": "^2.0.0"
}
}Why @ast-grep/napi? A single, fast, multi-language AST engine used by every codemod (JS/TS/Vue/Svelte/Python/Ruby) instead of per-framework parsers. Keeps the install footprint small — one native dep, prebuilt binaries for common platforms.
Error handling
Every tool returns one of three shapes:
// Success
{ content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }
// Known validation / domain error (isError: true)
{ content: [{ type: "text", text: "Error: gate 'new_checkout' already exists" }], isError: true }
// Protocol error — JSON-RPC level
{ error: { code: -32602, message: "Invalid params: 'name' required" } }Rules:
Never crash the stdio transport. Any unexpected exception is converted into an
isError: truetool result with a redacted message; a structurednotifications/messagelog is emitted aterrorlevel for the operator.Never include the SDK key, CLI token, or Anthropic key in any response, error, or log line. The logger has a deny-list of env var names that it redacts before emitting.
Never shell out with
exec+ string interpolation. All subprocess calls go throughutil/cli.tswhich usesexecFilewith an argument array and a 60 s default timeout.Validate every path input via
util/safe-read.ts— path traversal attempts throw a specific error that's shown to the user, not silently followed.
Logging / progress
Structured logs are sent via
notifications/message. The assistant honours the client-set log level (debug,info,warning,error) advertised at initialize time.Long-running tools (
i18n_translate_draft,i18n_codemod_applyon large repos,i18n_push_keys) emitnotifications/progresswith{ progressToken, progress, total, message }so the assistant can render a spinner/bar.Every tool call is logged with
{ tool, duration_ms, result_status, error_code }locally to~/.cache/shipeasy/mcp.log(rolling 10 MB, 3 files). No payloads. No secrets.
SDK / template compatibility
Each SDK language declares a compatible template range; the MCP server bundles the template authority and resolves at call time.
const COMPATIBLE_VERSIONS: Record<Lang, string> = {
typescript: ">=1.0.0 <3.0.0",
python: ">=1.0.0",
ruby: ">=1.0.0",
go: ">=1.0.0",
java: ">=1.0.0",
php: ">=1.0.0",
swift: ">=1.0.0",
kotlin: ">=1.0.0",
};detect_project sets template_warning when the installed SDK is outside the range. get_sdk_snippet first tries to import templates from the customer's installed shipeasy/templates/<lang>.js and falls back to this MCP server's bundled templates only if the SDK is not present (e.g. during fresh project setup).
Testing
Unit tests — every tool handler in isolation with a mock
fetch+ mockedexecFile. Target ≥90% coverage onsrc/tools/**.Fixture projects —
test/fixtures/projects/contains minimal Next.js / Django / Rails / Astro projects.detect_projectruns against each one and asserts the returned shape.MCP Inspector smoke test — CI runs
npx @modelcontextprotocol/inspector npx -y ./dist/index.jsand exerciseslist_tools,list_prompts,list_resources, and one success/one error tool call per domain.End-to-end — a Playwright suite under apps/ui/e2e/auth/mcp/ stands up a real worker + UI, spawns the MCP server as a child process, and verifies the full
create_experiment→get_sdk_snippet→experiment_statusloop writes to D1 and KV correctly. Required per CLAUDE.md.Cross-SDK snippet verification — for each language × framework × type tuple, the generated snippet is compiled/parsed (TypeScript →
tsc --noEmit, Python →py_compile, etc.) in a tiny scratch project. Regressions in template strings fail CI instantly.
Versioning
Independent of the CLI and the SDK:
Major — tool rename/removal, input-schema breaking change, prompt rename.
Minor — new tool, new prompt, new resource template, additive optional input field.
Patch — bug fix, template refresh, copy tweak.
initialize advertises serverInfo.version. The assistant may show a nudge when a newer version is available on npm. @shipeasy/cli re-exports @shipeasy/mcp at the matching major so shipeasy mcp start never runs a mismatched server.
Why one MCP server and not two
One auth flow. The CLI token unlocks both
/api/admin/experiments/*and/api/admin/i18n/*. Two servers would each prompt for login.One project detection.
detect_projectneeds to report both experimentation and i18n status; splitting doubles the filesystem walks for every conversation.Cross-subsystem prompts.
translate_sitewants to read experiment config (does the site have anenvariant gated by anew_language_pickerflag?) — a single server canget_resourcefrom both domains without a cross-server handshake.One AI install step. Customers add one entry to
.claude/settings.json, not two.One shared bundle of templates. Language + framework templates are co-located; an
i18n_install_loadercall can consult the same detected stackexp_create_experimentjust used.
Non-negotiables
Every new tool ships with: input schema in
tools/schema.ts, Zod validator at call-time, unit test, and an entry in this README's catalog table.Every new prompt ships with: entry in
prompts/schema.ts+ a markdown file underprompts/*.md+ a one-line description forlist_prompts.Every mutating tool (
create_*,update_*,delete_*,publish_*) requires an authenticated session and re-validates limits by letting theapps/uihandler callcheckLimit()— the MCP server never hand-rolls plan enforcement.All subprocess invocations use
execFile+ argument arrays.All filesystem reads go through
util/safe-read.ts.Secrets (SDK keys, CLI token, Anthropic API key) never appear in tool output, prompt content, resource content, or log lines.
Stdio transport stays healthy: unhandled rejections are captured in
index.tsand converted tonotifications/messageerrors — the process never exits on a per-request failure.Template updates are co-versioned with the SDK (
shipeasy/templates/) and validated in CI withtsc --noEmit/py_compile/ equivalent.
This server cannot be installed
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/shipeasy-ai/mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server