CLAUDE.md · git:20261013.2ed4db0 · 2026-10-13 · sha256 c379394ad3a74e32
CLAUDE.md git:20261013.2ed4db0A
Immutable. This exact content never changes and is served at /api/v1/blob/c379394ad3a74e32.
# CLAUDE.md
This file provides repository guidance for coding agents working in this repository.
## Project Overview
DeployGate MCP (Model Context Protocol) plugin for Claude Code and Codex. Provides tools for uploading mobile apps (iOS IPA, Android APK/AAB), managing distribution pages, team members, and CI/CD setup via the DeployGate REST API.
The plugin runs as a stdio-based MCP server. It is bundled into a single file (`plugin/scripts/bundle.js`) via esbuild for zero-dependency distribution.
## Commands
```bash
npm run build # TypeScript compile only (does not touch plugin/scripts/bundle.js)
npm run bundle # tsc + esbuild bundle to plugin/scripts/bundle.js (release workflow only)
npm test # Run all tests (vitest)
npm run test:watch # Run tests in watch mode
npm run dev # TypeScript watch mode
npm start # Run the MCP server directly
```
CI runs `npm run build && npm test` on Node 24 on every PR and push to main. A separate `bundle-compat` job builds the bundle on Node 24 and runs `node scripts/smoke-bundle.mjs` against a copy of `plugin/` outside the checkout on Node 20.0.0, the exact minimum in `engines`, so the plugin as users install it keeps working there even though dev tooling (vitest 5) needs Node 22+. `plugin/scripts/bundle.js` is regenerated and committed automatically by the release-please workflow when it opens/updates a release PR — do not run `npm run bundle` and commit the result manually. A pre-commit hook in `.githooks/` (installed by `npm install` via `prepare`) blocks accidental commits of the bundle.
## Architecture
**Entry point:** `src/index.ts` — creates `McpServer` and `DeployGateClient`, registers all tool modules, connects via stdio transport.
**API client:** `src/client.ts` — `DeployGateClient` class wrapping the DeployGate REST API (`https://deploygate.com`). Handles Bearer token auth, JSON/form-data requests, file uploads, and structured error responses via `DeployGateApiError`.
**Tool modules** (`src/tools/*.ts`): Each file exports a `register*Tools(server, client)` function that registers MCP tools with Zod parameter schemas. Tools return `ContentBlock[]` (text content, optional `isError` flag).
- `auth.ts` — `login_start`, `login_wait`, `logout`, `get_user_info`
- `upload.ts` — `upload_app` (supports IPA/APK/AAB + optional iOS simulator zip)
- `distributions.ts` — CRUD for distribution pages
- `udids.ts` — iOS device UDID listing
- `notifications.ts` — notification settings URL generation (no client needed)
- `members.ts` — multi-step member management (workspace → project → team → app)
- `shared-teams.ts` — workspace-wide shared team management
**Plugin assets** (`plugin/`): Contains the Codex plugin manifest (`plugin/.codex-plugin/plugin.json`), legacy Claude plugin manifest (`plugin/.claude-plugin/plugin.json`), MCP server configs (`.mcp.json` for Claude, which runs the bundle via `${CLAUDE_PLUGIN_ROOT}`; `.codex-mcp.json` for Codex, which does not expand that variable and runs it relative to the plugin root), the Claude plugin directory listing README (`plugin/README.md`) and icon (`plugin/.claude-plugin/icon.png`), skills (slash commands in `plugin/skills/`), GitHub Actions templates (`plugin/templates/`), and the bundled server script. The `plugin/` subdirectory is the published plugin root.
**Marketplace config** (`.agents/plugins/marketplace.json`): Top-level pointer to the `plugin/` subdirectory for Codex plugin marketplace registration.
## Testing
Tests use Vitest with `globals: true`. Test files are co-located at `src/__tests__/*.test.ts`.
- `client.test.ts` — API client behavior (mocks `fetch` with `vi.fn()`)
- `tools.test.ts` — Tool registration and parameter validation
- `plugin.test.ts` — Plugin manifest integrity (version sync, file existence)
- `skills.test.ts` — Skill YAML frontmatter and content structure
- `templates.test.ts` — GitHub Actions template syntax validation
Tests validate structural invariants like version consistency between `package.json` and `plugin.json`, so keep versions in sync when bumping.
## Key Conventions
- ESM throughout (`"type": "module"` in package.json, `.js` extensions in imports)
- TypeScript strict mode, target ES2022, Node16 module resolution
- The build produces both `dist/` (tsc output) and `plugin/scripts/bundle.js` (esbuild single-file bundle); the bundle is what end users run
- `scripts/build-bundle.mjs` fails when the bundle reaches 1 MiB, the size the Claude plugin directory reads as text; a larger bundle holds every version for a reviewer. Keep the bundle readable: trim dependencies instead of minifying. It already keeps only zod's English locale and leaves out the MCP SDK's ajv validator, which the SDK's server uses only for form elicitation, so the server must not use elicitation (`plugin.test.ts` checks this).
- Authentication uses the device authorization code flow. `login_start` → user approves in browser → `login_wait` stores the token at `~/.config/deploygate/token` (0600). `logout` revokes server-side and deletes the file.
- Skill `allowed-tools` must name each MCP tool with the plugin scope (`mcp__plugin_deploygate_deploygate__<tool>`), never a wildcard. Every entry must be on the `PRE_APPROVABLE_RULES` allowlist in `skills.test.ts`, which leaves out destructive or access-granting MCP tools, build tools (`./gradlew`, `xcodebuild`, `fastlane`), shell commands that write files or run arbitrary code, and unscoped `Write`/`Edit` (use `Edit(<path>)`, which also covers `Write`). The Claude plugin directory holds versions that pre-approve broad access, so extend the allowlist only deliberately.
- The `members.ts` `add_member` tool orchestrates multiple API calls (workspace → project → team) in a single tool invocation, handling "already exists" gracefully