Skip to content

feat: mcp filesystem tools - #342

Open
gmegidish wants to merge 13 commits into
mainfrom
feat/filesystem-tools
Open

gmegidish wants to merge 13 commits into
mainfrom
feat/filesystem-tools

Conversation

@gmegidish

@gmegidish gmegidish commented May 26, 2026

Copy link
Copy Markdown
Member

Cannot be merged until all platforms move to mobilecli

@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown

Walkthrough

This PR adds six filesystem operations and app container path lookup to the mobilecli module. Three new type interfaces define the response shapes for file listings and container paths. The Mobilecli class gains six new methods that execute mobilecli CLI commands and parse JSON responses for list and app-path queries. Six corresponding MCP tools expose these operations to clients with input validation for file transfer operations. Unit tests verify command argument construction, optional flag handling, and response parsing across all new operations.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: mcp filesystem tools' clearly describes the main change: adding new MCP tools for filesystem operations. It is concise, specific, and directly related to the primary purpose of the changeset.
Description check ✅ Passed The description 'Cannot be merged until all platforms move to mobilecli' is related to the changeset as it indicates a dependency/constraint for merging these filesystem tools, which are mobilecli-dependent.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/filesystem-tools

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/server.ts`:
- Around line 490-496: The MCP tool annotation for mobile_pull_file is
incorrect: it is marked readOnlyHint: true but the handler calls
mobilecli.fsPull(device, remotePath, localPath) which writes to the local
filesystem; update the annotation for mobile_pull_file to readOnlyHint: false
and add destructiveHint: true if the operation can overwrite or delete existing
files (otherwise destructiveHint: false); keep the existing handler
(ensureMobilecliAvailable, validateOutputPath, mobilecli.fsPull) unchanged—only
modify the annotation object for the mobile_pull_file tool.

In `@test/mobilecli.test.ts`:
- Around line 248-254: The test calls mobilecli.fsList with three args including
a bundleId, but the current API for fsList is fsList(deviceId: string,
remotePath?: string); update the test to call mobilecli.fsList with the deviceId
and optional remotePath only (e.g., mobilecli.fsList("device1", "/Documents"))
and change the expected call assertion to remove the bundleId from the args
(expect ["fs","ls","/Documents","--device","device1"] or adjust for an undefined
remotePath if testing that case); update references in the test to
createMockMobilecli and the calls[0].args assertion accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 97800ef8-f685-4270-a25d-f458f06be162

📥 Commits

Reviewing files that changed from the base of the PR and between 53f3a3b and e558a18.

📒 Files selected for processing (3)
  • src/mobilecli.ts
  • src/server.ts
  • test/mobilecli.test.ts

Comment thread src/server.ts
Comment on lines +490 to +496
{ readOnlyHint: true },
async ({ device, remotePath, localPath }) => {
ensureMobilecliAvailable();
validateOutputPath(localPath);
mobilecli.fsPull(device, remotePath, localPath);
return `Pulled ${remotePath} to ${localPath}`;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In @modelcontextprotocol/sdk (v1.26.0), should a tool that writes a local file be marked with readOnlyHint or destructiveHint?

💡 Result:

In the Model Context Protocol (MCP), a tool that writes to a local file should be marked with readOnlyHint: false [1][2][3]. Because the tool modifies the environment (i.e., it is not read-only), you must explicitly set readOnlyHint to false [2][3]. When readOnlyHint is set to false, you can further use destructiveHint to characterize the nature of the write operation [1][3]: - Set destructiveHint: true if the operation is destructive (e.g., overwriting an existing file, deleting data, or making irreversible changes) [4][2][3]. - Set destructiveHint: false if the operation is purely additive or safe (e.g., appending to a log file or creating a new, non-conflicting file) [1][3][5]. These annotations are hints used by MCP clients to decide whether to auto-approve a tool call or trigger a user confirmation dialog [2][3]. If you do not provide these annotations, clients may default to assuming a tool is non-read-only and destructive, which often leads to unnecessary confirmation prompts for safe operations, or conversely, may fail to protect against dangerous ones if the defaults are misinterpreted [2][3]. Always set these fields accurately to ensure the correct balance between security and agent autonomy [2][3][5].

Citations:


src/server.ts: Mark mobile_pull_file MCP tool annotation correctly (not read-only)
mobile_pull_file is annotated with readOnlyHint: true but it writes to the local filesystem via mobilecli.fsPull(device, remotePath, localPath). Update the annotation to readOnlyHint: false (and set destructiveHint: true if this operation can overwrite/delete existing files).

Suggested fix

Change { readOnlyHint: true } to { readOnlyHint: false, destructiveHint: ... } (set destructiveHint to true only if overwriting/deleting is possible).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server.ts` around lines 490 - 496, The MCP tool annotation for
mobile_pull_file is incorrect: it is marked readOnlyHint: true but the handler
calls mobilecli.fsPull(device, remotePath, localPath) which writes to the local
filesystem; update the annotation for mobile_pull_file to readOnlyHint: false
and add destructiveHint: true if the operation can overwrite or delete existing
files (otherwise destructiveHint: false); keep the existing handler
(ensureMobilecliAvailable, validateOutputPath, mobilecli.fsPull) unchanged—only
modify the annotation object for the mobile_pull_file tool.

Comment thread test/mobilecli.test.ts
Comment on lines +248 to +254
it("should call fs ls with bundleId and path", () => {
const { mobilecli, calls } = createMockMobilecli(mockResponse);
mobilecli.fsList("device1", "com.example.app", "/Documents");

assert.equal(calls.length, 1);
assert.deepEqual(calls[0].args, ["fs", "ls", "com.example.app", "/Documents", "--device", "device1"]);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify declaration vs call-site arity mismatch for fsList
rg -nP '^\s*fsList\s*\(.*\)' src/mobilecli.ts -n -C2
rg -nP '\bfsList\s*\(' test/mobilecli.test.ts -n -C2

Repository: mobile-next/mobile-mcp

Length of output: 1096


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect fsList implementation
sed -n '240,340p' src/mobilecli.ts

# Show the relevant test cases
sed -n '200,290p' test/mobilecli.test.ts

Repository: mobile-next/mobile-mcp

Length of output: 3541


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for other fsList overloads/usages in the implementation file
rg -n "fsList\s*\(" src/mobilecli.ts

# Search the whole repo for fsList call-sites that pass 3 args
rg -n "\.fsList\s*\(\s*[^,]+,\s*[^,]+,\s*[^)]+\)" -S .

# If tsc is available/configured, run a no-emit typecheck to catch TS2554-style errors
ls -la
ls -la tsconfig*.json 2>/dev/null || true
cat package.json 2>/dev/null || true
if [ -f package.json ]; then
  if rg -q '"typecheck"|tsc' package.json; then
    npm -s run typecheck || npx -s tsc -p tsconfig.json --noEmit --pretty false || true
  else
    npx -s tsc -p tsconfig.json --noEmit --pretty false || true
  fi
fi

Repository: mobile-next/mobile-mcp

Length of output: 5522


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== tsconfig.json ==="
cat tsconfig.json

echo "=== Running tests (ts-node will typecheck test compilation) ==="
npm test --silent

Repository: mobile-next/mobile-mcp

Length of output: 435


🏁 Script executed:

#!/bin/bash
set -euo pipefail

npx mocha --require ts-node/register test/*.ts

Repository: mobile-next/mobile-mcp

Length of output: 2200


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== tooling presence ==="
ls -la node_modules/ts-node 2>/dev/null || echo "missing node_modules/ts-node"
ls -la node_modules/nyc 2>/dev/null || echo "missing node_modules/nyc"
ls -la node_modules/mocha 2>/dev/null || echo "missing node_modules/mocha"

echo "=== test helper + fsList tests ==="
sed -n '1,220p' test/mobilecli.test.ts
sed -n '220,310p' test/mobilecli.test.ts

Repository: mobile-next/mobile-mcp

Length of output: 10525


Fix fsList test to match current API (2-arg signature)

  • src/mobilecli.ts defines fsList(deviceId: string, remotePath?: string), but the test calls mobilecli.fsList("device1", "com.example.app", "/Documents") (unsupported 3rd argument and bundleId isn’t part of the API).
Suggested fix (align tests to current API)
- it("should call fs ls with bundleId and path", () => {
+ it("should call fs ls with remote path", () => {
   const { mobilecli, calls } = createMockMobilecli(mockResponse);
-  mobilecli.fsList("device1", "com.example.app", "/Documents");
+  mobilecli.fsList("device1", "/Documents");

   assert.equal(calls.length, 1);
-  assert.deepEqual(calls[0].args, ["fs", "ls", "com.example.app", "/Documents", "--device", "device1"]);
+  assert.deepEqual(calls[0].args, ["fs", "ls", "/Documents", "--device", "device1"]);
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it("should call fs ls with bundleId and path", () => {
const { mobilecli, calls } = createMockMobilecli(mockResponse);
mobilecli.fsList("device1", "com.example.app", "/Documents");
assert.equal(calls.length, 1);
assert.deepEqual(calls[0].args, ["fs", "ls", "com.example.app", "/Documents", "--device", "device1"]);
});
it("should call fs ls with remote path", () => {
const { mobilecli, calls } = createMockMobilecli(mockResponse);
mobilecli.fsList("device1", "/Documents");
assert.equal(calls.length, 1);
assert.deepEqual(calls[0].args, ["fs", "ls", "/Documents", "--device", "device1"]);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/mobilecli.test.ts` around lines 248 - 254, The test calls
mobilecli.fsList with three args including a bundleId, but the current API for
fsList is fsList(deviceId: string, remotePath?: string); update the test to call
mobilecli.fsList with the deviceId and optional remotePath only (e.g.,
mobilecli.fsList("device1", "/Documents")) and change the expected call
assertion to remove the bundleId from the args (expect
["fs","ls","/Documents","--device","device1"] or adjust for an undefined
remotePath if testing that case); update references in the test to
createMockMobilecli and the calls[0].args assertion accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant