diff --git a/CLAUDE.md b/CLAUDE.md index 2f002ba..bce0760 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -107,11 +107,12 @@ When uploading Workers to Cloudflare's dispatch namespace, upload ALL files from ## Jack MCP (for AI Agents) -**CRITICAL:** When Jack MCP is connected, always prefer `mcp__jack__*` tools over CLI commands or wrangler. MCP tools are cloud-aware and work with Jack Cloud mode where wrangler won't. +When Jack MCP is connected, prefer `mcp__jack__*` tools for **cloud operations** (deploy, databases, logs, crons, domains, storage, vectorize) over CLI commands or wrangler. MCP tools are cloud-aware and work with Jack Cloud mode where wrangler won't. -- Check your available tools for anything prefixed with `mcp__jack__` -- Use those instead of `jack` CLI commands or `wrangler` commands +- For deployments, databases, logs, and services: use `mcp__jack__*` tools +- For file editing: always use built-in Read/Edit/Write tools, never remote MCP tools like `stage_file` or `read_deployed_file` - If a capability isn't available via MCP, ask the user to run it via CLI +- The local MCP (`mcp__jack__*`) and remote MCP (`mcp__jack_cloud__*`) are mutually exclusive — never connect both simultaneously ## Deploy Mode: Managed vs BYO @@ -238,3 +239,13 @@ Rules: - `findWranglerConfig()` in `wrangler-config.ts` — wrangler config path resolution (supports .jsonc, .json, .toml) - `parseJsonc()` in `jsonc.ts` — JSONC parsing (never use regex comment stripping) - `readProjectLink()` + `getDeployMode()` in `project-link.ts` — project link and deploy mode + +**After modifying shell integration (`shell-integration.ts`), regenerate the shell file for local testing.** + +`jack update` only regenerates `~/.config/jack/shell.sh` after a successful npm update — it won't pick up local dev changes. After editing `shell-integration.ts`, run: + +```bash +bun -e "import { writeShellFile } from './apps/cli/src/lib/shell-integration.ts'; writeShellFile();" +``` + +Then open a new terminal (or `source ~/.zshrc`) to load the updated shell function. Without this, you'll be testing against the stale shell.sh from the last published version. diff --git a/apps/cli/src/commands/mcp.ts b/apps/cli/src/commands/mcp.ts index 90d1653..25f4a53 100644 --- a/apps/cli/src/commands/mcp.ts +++ b/apps/cli/src/commands/mcp.ts @@ -262,7 +262,7 @@ async function outputProjectContext(): Promise { lines.push("- Status: `mcp__jack__get_project_status` or `jack info`"); lines.push(""); lines.push( - "Prefer `mcp__jack__*` tools or `jack` CLI over raw `wrangler` commands for consistency.", + "For cloud operations, prefer `mcp__jack__*` tools or `jack` CLI over raw `wrangler` commands. For file editing, use built-in Read/Edit/Write tools.", ); } @@ -272,7 +272,7 @@ async function outputProjectContext(): Promise { ); lines.push(""); lines.push( - "**Always prefer `mcp__jack__*` tools over CLI commands or wrangler** — they are cloud-aware and work in all deploy modes.", + "**For cloud operations (deploy, databases, logs, services), prefer `mcp__jack__*` tools over CLI commands or wrangler** — they are cloud-aware and work in all deploy modes. For file editing, always use built-in Read/Edit/Write tools.", ); sections.push(lines.join("\n")); diff --git a/apps/cli/src/lib/mcp-config.ts b/apps/cli/src/lib/mcp-config.ts index ba622fe..dd0a7ce 100644 --- a/apps/cli/src/lib/mcp-config.ts +++ b/apps/cli/src/lib/mcp-config.ts @@ -127,6 +127,45 @@ export function isAppInstalled(appId: string): boolean { return existsSync(configDir); } +/** + * Check if an MCP server entry looks like a remote Jack MCP connection. + */ +function isRemoteJackMcpEntry(entry: unknown): boolean { + if (!entry || typeof entry !== "object") return false; + const obj = entry as Record; + + if (typeof obj.url === "string" && obj.url.includes("getjack.org")) { + return true; + } + + if (typeof obj.command === "string" && obj.command.includes("getjack.org")) { + return true; + } + + return false; +} + +/** + * Scan mcpServers for remote Jack MCP entries and remove them. + * Remote entries conflict with the local stdio-based MCP server. + */ +function removeRemoteJackMcpEntries(mcpServers: Record): void { + const keysToRemove: string[] = []; + + for (const [name, entry] of Object.entries(mcpServers)) { + if (name === "jack-cloud" || isRemoteJackMcpEntry(entry)) { + keysToRemove.push(name); + } + } + + for (const key of keysToRemove) { + console.error( + `\n⚠ Removing remote Jack MCP server ("${key}") — it conflicts with the local server.\n Local MCP covers all remote capabilities when you have filesystem access.\n Remote MCP is only needed in terminal-less environments (claude.ai web).\n`, + ); + delete mcpServers[key]; + } +} + /** * Install MCP config to a single app * Reads existing config, merges jack server, writes back @@ -163,6 +202,9 @@ export async function installMcpConfigToApp(appId: string): Promise { // Get or create mcpServers object const mcpServers = (existingConfig[appConfig.key] as Record) || {}; + // Detect and remove remote Jack MCP server entries that conflict with local + removeRemoteJackMcpEntries(mcpServers); + // Add/update jack MCP server mcpServers.jack = getJackMcpConfig(); diff --git a/apps/cli/src/mcp/resources/index.ts b/apps/cli/src/mcp/resources/index.ts index 93d6603..2ce9f69 100644 --- a/apps/cli/src/mcp/resources/index.ts +++ b/apps/cli/src/mcp/resources/index.ts @@ -119,11 +119,13 @@ This project is managed by jack. ## MCP Tools Available -If connected, prefer \`mcp__jack__*\` tools over CLI: +For cloud operations (deploy, databases, logs, services), prefer \`mcp__jack__*\` tools over CLI: - \`mcp__jack__deploy_project\` - Deploy changes - \`mcp__jack__execute_sql\` - Query databases - \`mcp__jack__get_project_status\` - Check status +For file editing, always use built-in Read/Edit/Write tools — never remote MCP tools. + ## Documentation Full docs: https://docs.getjack.org/llms-full.txt diff --git a/apps/mcp-worker/CLAUDE.md b/apps/mcp-worker/CLAUDE.md index 4b1e509..42d4470 100644 --- a/apps/mcp-worker/CLAUDE.md +++ b/apps/mcp-worker/CLAUDE.md @@ -29,24 +29,36 @@ src/ ├── deploy-template.ts # Deploy prebuilt template via control plane ├── endpoint-test.ts # HTTP request to deployed endpoints ├── projects.ts # list_projects, get_project_status - ├── source.ts # list/read project files, update_file staging + ├── source.ts # browse/read deployed files, stage_file staging ├── logs.ts # get_logs ├── database.ts # create_database, list_databases, execute_sql └── rollback.ts # rollback_project ``` +## Local vs Remote MCP — Mutually Exclusive + +The local MCP (`jack mcp serve`, stdio) and remote MCP (`mcp.getjack.org`, HTTP) serve different environments and should **never be connected simultaneously**: + +| Environment | MCP Server | Why | +|-------------|-----------|-----| +| Claude Code (has terminal + local FS) | Local only (`jack`) | Agent has Read/Edit/Write for files, local MCP for cloud ops | +| claude.ai web (no terminal) | Remote only (`jack-cloud`) | No local FS, must use stage_file + deploy for file changes | +| Claude Desktop | One or the other | Depends on whether shell access is available | + +If both are connected, agents use remote file tools (`stage_file`, `read_deployed_file`) instead of local filesystem tools, causing partial deploys and failures. The `jack mcp install` command detects and removes remote MCP configs to prevent this. + ## Tools (14 total) | Tool | Type | Description | |------|------|-------------| | `deploy` | write | Unified deploy: `files`, `template`, `changes`, or `staged` mode | -| `update_file` | write | Stage a file change for later deployment via `deploy(staged=true)` | -| `list_staged_changes` | read | List files staged via `update_file` pending deployment | +| `stage_file` | write | Stage a file change for later deployment via `deploy(staged=true)` | +| `list_staged_files` | read | List files staged via `stage_file` pending deployment | | `list_projects` | read | List all user's projects with URLs | | `get_project_status` | read | Deployment status, URL, resources for a project | | `test_endpoint` | read | HTTP request to a deployed endpoint, returns status + body | -| `list_project_files` | read | File tree of deployed project's source | -| `read_project_file` | read | Read single source file from deployed project | +| `browse_deployed_source` | read | File tree of deployed project's source | +| `read_deployed_file` | read | Read single source file from deployed project | | `get_logs` | read | Start log session and collect entries | | `create_database` | write | Create D1 database for a project (idempotent) | | `list_databases` | read | List D1 databases for a project | @@ -64,8 +76,8 @@ This is the core workflow the tools enable: → Returns project_id + live URL 2. User: "add a /forecast endpoint" - → LLM calls list_project_files(project_id) → sees current files - → LLM calls read_project_file(project_id, "src/index.ts") → gets source + → LLM calls browse_deployed_source(project_id) → sees current files + → LLM calls read_deployed_file(project_id, "src/index.ts") → gets source → LLM modifies the code → LLM calls deploy(changes: {"src/index.ts": "..."}, project_id) → redeploys @@ -75,8 +87,8 @@ This is the core workflow the tools enable: → LLM calls test_endpoint(project_id, "/api/health") → verifies fix 4. Large file update (>15KB): - → LLM calls update_file(project_id, "src/index.ts", content) → stages file - → LLM calls update_file(project_id, "src/styles.css", content) → stages another + → LLM calls stage_file(project_id, "src/index.ts", content) → stages file + → LLM calls stage_file(project_id, "src/styles.css", content) → stages another → LLM calls deploy(project_id, staged=true) → deploys all staged changes ``` @@ -106,7 +118,7 @@ The remote MCP (this worker) has 10 tools. The local MCP (`apps/cli/src/mcp/`) h | Category | Local MCP | Remote MCP | Notes | |----------|-----------|------------|-------| | Deploy/Projects | create_project, deploy_project, get_project_status, list_projects, rollback_project | deploy, list_projects, get_project_status, rollback_project | Remote merged create+deploy into `deploy` | -| Source | — | list_project_files, read_project_file | Remote-only (new) | +| Source | — | browse_deployed_source, read_deployed_file | Remote-only (new) | | Logs | start_log_session, tail_logs | get_logs | Remote merged into one | | Database | create_database, list_databases, execute_sql | create_database, list_databases, execute_sql | Aligned | | Vectorize | create/list/delete/get_vectorize_index | — | Not yet in remote | diff --git a/apps/mcp-worker/src/server.ts b/apps/mcp-worker/src/server.ts index 63a945f..53e2e00 100644 --- a/apps/mcp-worker/src/server.ts +++ b/apps/mcp-worker/src/server.ts @@ -13,7 +13,7 @@ import type { Bindings } from "./types.ts"; export function createMcpServer(token: string, env: Bindings): McpServer { const server = new McpServer({ - name: "jack", + name: "jack-cloud", version: "1.0.0", }); @@ -26,11 +26,11 @@ export function createMcpServer(token: string, env: Bindings): McpServer { - files: Full file set for a brand-new project. Pass all source files as { "path": "content" }. Only use this for the FIRST deploy of a new custom project. - template: Deploy a prebuilt template (hello, api, miniapp, nextjs, saas). Always creates a new project. - changes: Partial update to an existing project. Pass only changed/added files as { "path": "new content" } or { "path": null } to delete. Requires project_id. -- staged: Deploy files previously staged via update_file. Set staged=true with project_id. Use this when files are too large to pass inline in a single changes call. +- staged: Deploy files previously staged via stage_file. Set staged=true with project_id. Use this when files are too large to pass inline in a single changes call. IMPORTANT: To update an existing project, ALWAYS use changes mode with project_id. Do NOT use files mode for existing projects — it replaces all files and may create a duplicate project. If the user mentions an existing app, call list_projects first to find its project_id, then use changes. -For large files (>15KB): Use update_file to stage files one at a time, then deploy(staged=true, project_id). This avoids output token limits that can truncate large inline content.`, +For large files (>15KB): Use stage_file to stage files one at a time, then deploy(staged=true, project_id). This avoids output token limits that can truncate large inline content.`, { files: z .record(z.string(), z.string()) @@ -52,7 +52,7 @@ For large files (>15KB): Use update_file to stage files one at a time, then depl .boolean() .optional() .describe( - "Deploy files previously staged via update_file calls. Requires project_id. Use when files are too large for inline changes.", + "Deploy files previously staged via stage_file calls. Requires project_id. Use when files are too large for inline changes.", ), project_id: z .string() @@ -73,8 +73,10 @@ For large files (>15KB): Use update_file to stage files one at a time, then depl ); server.tool( - "update_file", - `Stage a file change for later deployment. Use this to build up changes across multiple calls, then deploy them all at once with deploy(staged=true). + "stage_file", + `Stage a file for cloud deployment via deploy(staged=true). Only use this in environments WITHOUT local filesystem access (e.g. claude.ai web, Claude Desktop without terminal). + +If you have built-in Read/Edit/Write tools available, do NOT use this tool — edit files locally and deploy with deploy_project or jack ship instead. Best for: - Large files that exceed output token limits when passed inline via changes @@ -100,8 +102,8 @@ Pass content=null to mark a file for deletion.`, ); server.tool( - "list_staged_changes", - "List files currently staged via update_file that haven't been deployed yet. Use to review pending changes before calling deploy(staged=true).", + "list_staged_files", + "List files currently staged via stage_file that haven't been deployed yet. Use to review pending changes before calling deploy(staged=true).", { project_id: z.string().describe("The project ID"), }, @@ -188,8 +190,12 @@ Pass content=null to mark a file for deletion.`, ); server.tool( - "list_project_files", - "List all source files in a deployed project. Use before read_project_file to see what files exist, or before deploying with changes to understand current project structure.", + "browse_deployed_source", + `List all source files in the DEPLOYED version of a project on Jack Cloud. Shows the file tree as it exists in production, not local files. + +If you have local filesystem access (e.g. Claude Code with Glob/LS tools), read the local project directory instead — it's faster and more accurate. + +Use before read_deployed_file to see what files exist, or before deploying with changes to understand current project structure.`, { project_id: z.string().describe("The project ID"), }, @@ -199,11 +205,15 @@ Pass content=null to mark a file for deletion.`, ); server.tool( - "read_project_file", - "Read the contents of a single source file from a deployed project. Use after list_project_files to inspect specific files before making changes with deploy(changes).", + "read_deployed_file", + `Read the contents of a single source file from the DEPLOYED version on Jack Cloud. Returns the file as it exists in production. + +If you have local filesystem access (e.g. Claude Code with the Read tool), read the local file instead — it's faster and always up-to-date with your working copy. + +Use after browse_deployed_source to inspect specific files before making changes with deploy(changes).`, { project_id: z.string().describe("The project ID"), - path: z.string().describe("File path from list_project_files (e.g. 'src/index.ts')"), + path: z.string().describe("File path from browse_deployed_source (e.g. 'src/index.ts')"), }, async ({ project_id, path }) => { return readProjectFile(client, project_id, path); diff --git a/apps/mcp-worker/src/staging.ts b/apps/mcp-worker/src/staging.ts index 46139a9..1449967 100644 --- a/apps/mcp-worker/src/staging.ts +++ b/apps/mcp-worker/src/staging.ts @@ -1,7 +1,7 @@ /** * File staging for multi-call deploys. * - * Uses KV to accumulate file changes across multiple update_file calls, + * Uses KV to accumulate file changes across multiple stage_file calls, * then deploy reads and clears the staged changes. This works around * LLM output token limits that prevent sending large files in a single * tool call. diff --git a/apps/mcp-worker/src/tools/deploy-code.ts b/apps/mcp-worker/src/tools/deploy-code.ts index 4ccb73b..f497fc1 100644 --- a/apps/mcp-worker/src/tools/deploy-code.ts +++ b/apps/mcp-worker/src/tools/deploy-code.ts @@ -177,7 +177,7 @@ export async function deploy( const { files, template, changes, staged, project_id, project_name, compatibility_flags } = params; - // Staged mode: deploy from accumulated update_file calls + // Staged mode: deploy from accumulated stage_file calls if (staged) { if (files || template || changes) { return err( @@ -202,7 +202,7 @@ export async function deploy( return err( "VALIDATION_ERROR", "No staged changes found for this project.", - "Use update_file to stage file changes before deploying with staged=true.", + "Use stage_file to stage file changes before deploying with staged=true.", ); } @@ -252,7 +252,7 @@ export async function deploy( return err( "VALIDATION_ERROR", "Exactly one of files, template, changes, or staged=true must be provided.", - "Pass files for a full deploy, template for a prebuilt app, changes for a partial update, or staged=true to deploy files from update_file calls.", + "Pass files for a full deploy, template for a prebuilt app, changes for a partial update, or staged=true to deploy files from stage_file calls.", ); } if (modeCount > 1) { diff --git a/apps/mcp-worker/src/tools/source.ts b/apps/mcp-worker/src/tools/source.ts index 32bb5bc..ad95123 100644 --- a/apps/mcp-worker/src/tools/source.ts +++ b/apps/mcp-worker/src/tools/source.ts @@ -34,7 +34,7 @@ export async function readProjectFile( return err( "NOT_FOUND", message, - "Check the file path. Use list_project_files to see available files.", + "Check the file path. Use browse_deployed_source to see available files.", ); } } @@ -93,7 +93,7 @@ export async function listStagedChanges( return ok({ staged_files: 0, files: [], - note: "No staged changes. Use update_file to stage file changes before deploying.", + note: "No staged changes. Use stage_file to stage file changes before deploying.", }); }