Skip to content

Commit bbb2bd8

Browse files
authored
fix(mcp): deconflict local and remote MCP servers to prevent partial deploys (#23)
Rename remote MCP server from "jack" to "jack-cloud" and rename file tools to clearly signal they're for terminal-less environments only: - update_file → stage_file - list_staged_changes → list_staged_files - list_project_files → browse_deployed_source - read_project_file → read_deployed_file Tool descriptions now include negative guidance ("if you have Read/Edit/Write tools, use those instead") to prevent agents from using remote file staging when local filesystem access is available. Add dual-connection detection to jack mcp install — automatically removes remote MCP configs that conflict with the local server. Narrow CLAUDE.md and agents://context guidance from blanket "always prefer MCP tools" to "prefer MCP for cloud operations, use built-in tools for file editing."
1 parent 63a4898 commit bbb2bd8

9 files changed

Lines changed: 112 additions & 35 deletions

File tree

CLAUDE.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,12 @@ When uploading Workers to Cloudflare's dispatch namespace, upload ALL files from
107107

108108
## Jack MCP (for AI Agents)
109109

110-
**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.
110+
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.
111111

112-
- Check your available tools for anything prefixed with `mcp__jack__`
113-
- Use those instead of `jack` CLI commands or `wrangler` commands
112+
- For deployments, databases, logs, and services: use `mcp__jack__*` tools
113+
- For file editing: always use built-in Read/Edit/Write tools, never remote MCP tools like `stage_file` or `read_deployed_file`
114114
- If a capability isn't available via MCP, ask the user to run it via CLI
115+
- The local MCP (`mcp__jack__*`) and remote MCP (`mcp__jack_cloud__*`) are mutually exclusive — never connect both simultaneously
115116

116117
## Deploy Mode: Managed vs BYO
117118

@@ -238,3 +239,13 @@ Rules:
238239
- `findWranglerConfig()` in `wrangler-config.ts` — wrangler config path resolution (supports .jsonc, .json, .toml)
239240
- `parseJsonc()` in `jsonc.ts` — JSONC parsing (never use regex comment stripping)
240241
- `readProjectLink()` + `getDeployMode()` in `project-link.ts` — project link and deploy mode
242+
243+
**After modifying shell integration (`shell-integration.ts`), regenerate the shell file for local testing.**
244+
245+
`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:
246+
247+
```bash
248+
bun -e "import { writeShellFile } from './apps/cli/src/lib/shell-integration.ts'; writeShellFile();"
249+
```
250+
251+
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.

apps/cli/src/commands/mcp.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ async function outputProjectContext(): Promise<void> {
262262
lines.push("- Status: `mcp__jack__get_project_status` or `jack info`");
263263
lines.push("");
264264
lines.push(
265-
"Prefer `mcp__jack__*` tools or `jack` CLI over raw `wrangler` commands for consistency.",
265+
"For cloud operations, prefer `mcp__jack__*` tools or `jack` CLI over raw `wrangler` commands. For file editing, use built-in Read/Edit/Write tools.",
266266
);
267267
}
268268

@@ -272,7 +272,7 @@ async function outputProjectContext(): Promise<void> {
272272
);
273273
lines.push("");
274274
lines.push(
275-
"**Always prefer `mcp__jack__*` tools over CLI commands or wrangler** — they are cloud-aware and work in all deploy modes.",
275+
"**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.",
276276
);
277277
sections.push(lines.join("\n"));
278278

apps/cli/src/lib/mcp-config.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,45 @@ export function isAppInstalled(appId: string): boolean {
127127
return existsSync(configDir);
128128
}
129129

130+
/**
131+
* Check if an MCP server entry looks like a remote Jack MCP connection.
132+
*/
133+
function isRemoteJackMcpEntry(entry: unknown): boolean {
134+
if (!entry || typeof entry !== "object") return false;
135+
const obj = entry as Record<string, unknown>;
136+
137+
if (typeof obj.url === "string" && obj.url.includes("getjack.org")) {
138+
return true;
139+
}
140+
141+
if (typeof obj.command === "string" && obj.command.includes("getjack.org")) {
142+
return true;
143+
}
144+
145+
return false;
146+
}
147+
148+
/**
149+
* Scan mcpServers for remote Jack MCP entries and remove them.
150+
* Remote entries conflict with the local stdio-based MCP server.
151+
*/
152+
function removeRemoteJackMcpEntries(mcpServers: Record<string, unknown>): void {
153+
const keysToRemove: string[] = [];
154+
155+
for (const [name, entry] of Object.entries(mcpServers)) {
156+
if (name === "jack-cloud" || isRemoteJackMcpEntry(entry)) {
157+
keysToRemove.push(name);
158+
}
159+
}
160+
161+
for (const key of keysToRemove) {
162+
console.error(
163+
`\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`,
164+
);
165+
delete mcpServers[key];
166+
}
167+
}
168+
130169
/**
131170
* Install MCP config to a single app
132171
* Reads existing config, merges jack server, writes back
@@ -163,6 +202,9 @@ export async function installMcpConfigToApp(appId: string): Promise<boolean> {
163202
// Get or create mcpServers object
164203
const mcpServers = (existingConfig[appConfig.key] as Record<string, unknown>) || {};
165204

205+
// Detect and remove remote Jack MCP server entries that conflict with local
206+
removeRemoteJackMcpEntries(mcpServers);
207+
166208
// Add/update jack MCP server
167209
mcpServers.jack = getJackMcpConfig();
168210

apps/cli/src/mcp/resources/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,13 @@ This project is managed by jack.
119119
120120
## MCP Tools Available
121121
122-
If connected, prefer \`mcp__jack__*\` tools over CLI:
122+
For cloud operations (deploy, databases, logs, services), prefer \`mcp__jack__*\` tools over CLI:
123123
- \`mcp__jack__deploy_project\` - Deploy changes
124124
- \`mcp__jack__execute_sql\` - Query databases
125125
- \`mcp__jack__get_project_status\` - Check status
126126
127+
For file editing, always use built-in Read/Edit/Write tools — never remote MCP tools.
128+
127129
## Documentation
128130
129131
Full docs: https://docs.getjack.org/llms-full.txt

apps/mcp-worker/CLAUDE.md

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,36 @@ src/
2929
├── deploy-template.ts # Deploy prebuilt template via control plane
3030
├── endpoint-test.ts # HTTP request to deployed endpoints
3131
├── projects.ts # list_projects, get_project_status
32-
├── source.ts # list/read project files, update_file staging
32+
├── source.ts # browse/read deployed files, stage_file staging
3333
├── logs.ts # get_logs
3434
├── database.ts # create_database, list_databases, execute_sql
3535
└── rollback.ts # rollback_project
3636
```
3737

38+
## Local vs Remote MCP — Mutually Exclusive
39+
40+
The local MCP (`jack mcp serve`, stdio) and remote MCP (`mcp.getjack.org`, HTTP) serve different environments and should **never be connected simultaneously**:
41+
42+
| Environment | MCP Server | Why |
43+
|-------------|-----------|-----|
44+
| Claude Code (has terminal + local FS) | Local only (`jack`) | Agent has Read/Edit/Write for files, local MCP for cloud ops |
45+
| claude.ai web (no terminal) | Remote only (`jack-cloud`) | No local FS, must use stage_file + deploy for file changes |
46+
| Claude Desktop | One or the other | Depends on whether shell access is available |
47+
48+
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.
49+
3850
## Tools (14 total)
3951

4052
| Tool | Type | Description |
4153
|------|------|-------------|
4254
| `deploy` | write | Unified deploy: `files`, `template`, `changes`, or `staged` mode |
43-
| `update_file` | write | Stage a file change for later deployment via `deploy(staged=true)` |
44-
| `list_staged_changes` | read | List files staged via `update_file` pending deployment |
55+
| `stage_file` | write | Stage a file change for later deployment via `deploy(staged=true)` |
56+
| `list_staged_files` | read | List files staged via `stage_file` pending deployment |
4557
| `list_projects` | read | List all user's projects with URLs |
4658
| `get_project_status` | read | Deployment status, URL, resources for a project |
4759
| `test_endpoint` | read | HTTP request to a deployed endpoint, returns status + body |
48-
| `list_project_files` | read | File tree of deployed project's source |
49-
| `read_project_file` | read | Read single source file from deployed project |
60+
| `browse_deployed_source` | read | File tree of deployed project's source |
61+
| `read_deployed_file` | read | Read single source file from deployed project |
5062
| `get_logs` | read | Start log session and collect entries |
5163
| `create_database` | write | Create D1 database for a project (idempotent) |
5264
| `list_databases` | read | List D1 databases for a project |
@@ -64,8 +76,8 @@ This is the core workflow the tools enable:
6476
→ Returns project_id + live URL
6577
6678
2. User: "add a /forecast endpoint"
67-
→ LLM calls list_project_files(project_id) → sees current files
68-
→ LLM calls read_project_file(project_id, "src/index.ts") → gets source
79+
→ LLM calls browse_deployed_source(project_id) → sees current files
80+
→ LLM calls read_deployed_file(project_id, "src/index.ts") → gets source
6981
→ LLM modifies the code
7082
→ LLM calls deploy(changes: {"src/index.ts": "..."}, project_id) → redeploys
7183
@@ -75,8 +87,8 @@ This is the core workflow the tools enable:
7587
→ LLM calls test_endpoint(project_id, "/api/health") → verifies fix
7688
7789
4. Large file update (>15KB):
78-
→ LLM calls update_file(project_id, "src/index.ts", content) → stages file
79-
→ LLM calls update_file(project_id, "src/styles.css", content) → stages another
90+
→ LLM calls stage_file(project_id, "src/index.ts", content) → stages file
91+
→ LLM calls stage_file(project_id, "src/styles.css", content) → stages another
8092
→ LLM calls deploy(project_id, staged=true) → deploys all staged changes
8193
```
8294

@@ -106,7 +118,7 @@ The remote MCP (this worker) has 10 tools. The local MCP (`apps/cli/src/mcp/`) h
106118
| Category | Local MCP | Remote MCP | Notes |
107119
|----------|-----------|------------|-------|
108120
| 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` |
109-
| Source || list_project_files, read_project_file | Remote-only (new) |
121+
| Source || browse_deployed_source, read_deployed_file | Remote-only (new) |
110122
| Logs | start_log_session, tail_logs | get_logs | Remote merged into one |
111123
| Database | create_database, list_databases, execute_sql | create_database, list_databases, execute_sql | Aligned |
112124
| Vectorize | create/list/delete/get_vectorize_index || Not yet in remote |

apps/mcp-worker/src/server.ts

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type { Bindings } from "./types.ts";
1313

1414
export function createMcpServer(token: string, env: Bindings): McpServer {
1515
const server = new McpServer({
16-
name: "jack",
16+
name: "jack-cloud",
1717
version: "1.0.0",
1818
});
1919

@@ -26,11 +26,11 @@ export function createMcpServer(token: string, env: Bindings): McpServer {
2626
- 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.
2727
- template: Deploy a prebuilt template (hello, api, miniapp, nextjs, saas). Always creates a new project.
2828
- changes: Partial update to an existing project. Pass only changed/added files as { "path": "new content" } or { "path": null } to delete. Requires project_id.
29-
- 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.
29+
- 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.
3030
3131
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.
3232
33-
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.`,
33+
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.`,
3434
{
3535
files: z
3636
.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
5252
.boolean()
5353
.optional()
5454
.describe(
55-
"Deploy files previously staged via update_file calls. Requires project_id. Use when files are too large for inline changes.",
55+
"Deploy files previously staged via stage_file calls. Requires project_id. Use when files are too large for inline changes.",
5656
),
5757
project_id: z
5858
.string()
@@ -73,8 +73,10 @@ For large files (>15KB): Use update_file to stage files one at a time, then depl
7373
);
7474

7575
server.tool(
76-
"update_file",
77-
`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).
76+
"stage_file",
77+
`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).
78+
79+
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.
7880
7981
Best for:
8082
- 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.`,
100102
);
101103

102104
server.tool(
103-
"list_staged_changes",
104-
"List files currently staged via update_file that haven't been deployed yet. Use to review pending changes before calling deploy(staged=true).",
105+
"list_staged_files",
106+
"List files currently staged via stage_file that haven't been deployed yet. Use to review pending changes before calling deploy(staged=true).",
105107
{
106108
project_id: z.string().describe("The project ID"),
107109
},
@@ -188,8 +190,12 @@ Pass content=null to mark a file for deletion.`,
188190
);
189191

190192
server.tool(
191-
"list_project_files",
192-
"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.",
193+
"browse_deployed_source",
194+
`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.
195+
196+
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.
197+
198+
Use before read_deployed_file to see what files exist, or before deploying with changes to understand current project structure.`,
193199
{
194200
project_id: z.string().describe("The project ID"),
195201
},
@@ -199,11 +205,15 @@ Pass content=null to mark a file for deletion.`,
199205
);
200206

201207
server.tool(
202-
"read_project_file",
203-
"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).",
208+
"read_deployed_file",
209+
`Read the contents of a single source file from the DEPLOYED version on Jack Cloud. Returns the file as it exists in production.
210+
211+
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.
212+
213+
Use after browse_deployed_source to inspect specific files before making changes with deploy(changes).`,
204214
{
205215
project_id: z.string().describe("The project ID"),
206-
path: z.string().describe("File path from list_project_files (e.g. 'src/index.ts')"),
216+
path: z.string().describe("File path from browse_deployed_source (e.g. 'src/index.ts')"),
207217
},
208218
async ({ project_id, path }) => {
209219
return readProjectFile(client, project_id, path);

apps/mcp-worker/src/staging.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* File staging for multi-call deploys.
33
*
4-
* Uses KV to accumulate file changes across multiple update_file calls,
4+
* Uses KV to accumulate file changes across multiple stage_file calls,
55
* then deploy reads and clears the staged changes. This works around
66
* LLM output token limits that prevent sending large files in a single
77
* tool call.

apps/mcp-worker/src/tools/deploy-code.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ export async function deploy(
177177
const { files, template, changes, staged, project_id, project_name, compatibility_flags } =
178178
params;
179179

180-
// Staged mode: deploy from accumulated update_file calls
180+
// Staged mode: deploy from accumulated stage_file calls
181181
if (staged) {
182182
if (files || template || changes) {
183183
return err(
@@ -202,7 +202,7 @@ export async function deploy(
202202
return err(
203203
"VALIDATION_ERROR",
204204
"No staged changes found for this project.",
205-
"Use update_file to stage file changes before deploying with staged=true.",
205+
"Use stage_file to stage file changes before deploying with staged=true.",
206206
);
207207
}
208208

@@ -252,7 +252,7 @@ export async function deploy(
252252
return err(
253253
"VALIDATION_ERROR",
254254
"Exactly one of files, template, changes, or staged=true must be provided.",
255-
"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.",
255+
"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.",
256256
);
257257
}
258258
if (modeCount > 1) {

apps/mcp-worker/src/tools/source.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export async function readProjectFile(
3434
return err(
3535
"NOT_FOUND",
3636
message,
37-
"Check the file path. Use list_project_files to see available files.",
37+
"Check the file path. Use browse_deployed_source to see available files.",
3838
);
3939
}
4040
}
@@ -93,7 +93,7 @@ export async function listStagedChanges(
9393
return ok({
9494
staged_files: 0,
9595
files: [],
96-
note: "No staged changes. Use update_file to stage file changes before deploying.",
96+
note: "No staged changes. Use stage_file to stage file changes before deploying.",
9797
});
9898
}
9999

0 commit comments

Comments
 (0)