From aad3d88cc018fa7c9175a024cf12f68fead1b559 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sat, 24 Oct 2026 04:15:21 +0800 Subject: [PATCH] docs(mcp): state plist inspection session prerequisite --- docs/apple-application-analysis.md | 4 +++ docs/mcp-contracts.md | 6 +++++ src/contracts/native/nativeToolContracts.ts | 10 ++++--- src/server/BundleResourcesMcp.test.ts | 29 +++++++++++++++++++++ 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/docs/apple-application-analysis.md b/docs/apple-application-analysis.md index 2e675c24ce..ef0fe09ccd 100644 --- a/docs/apple-application-analysis.md +++ b/docs/apple-application-analysis.md @@ -156,6 +156,10 @@ anatomy components. The projection does not read plists. Use `inspect_plist` on a bundle's `info_plist_path` or a launchd plist to read `CFBundleExecutable`, identifiers, `SMPrivilegedExecutables`, `BundleProgram`, URL schemes, and usage descriptions. +MCP calls require an existing `open_binary` or `open_bundle_resources` session, +including when `inspect_plist.path` is explicit. For a bundle without a resolved +executable, open its resource session first. The CLI `rea inspect-plist ` +opens the selected artifact for its one invocation. Use `inspect_signature` on an executable candidate for code-signing claims. ## Dylib load resolution diff --git a/docs/mcp-contracts.md b/docs/mcp-contracts.md index 472b54b50a..2ac744b2ae 100644 --- a/docs/mcp-contracts.md +++ b/docs/mcp-contracts.md @@ -633,6 +633,12 @@ Binary-only operations and binary analysis snapshots do not apply in this session. `open_binary` can switch back to executable analysis, and `close_binary` retires either session kind. +`inspect_plist` requires an open binary or bundle-resource session. An explicit +`path` selects a plist in that context and does not create a session; +`binary_session` reports `target_required` while neither session is open. +Use `open_bundle_resources` for a bundle whose executable cannot be resolved. +The CLI `rea inspect-plist ` opens its selected artifact for one invocation. + Each provider client receives `binary_session.analysis_run.run_id` before any provider startup, including clients whose startup fails. `process_lineage` is `not_observed` until a dynamic provider starts, then becomes `snapshots` with diff --git a/src/contracts/native/nativeToolContracts.ts b/src/contracts/native/nativeToolContracts.ts index 7a7327bd28..ff5d16aa0a 100644 --- a/src/contracts/native/nativeToolContracts.ts +++ b/src/contracts/native/nativeToolContracts.ts @@ -17,7 +17,7 @@ import { nativeCommandTimeoutField } from "../../domain/native/nativeDeadlines.j const examples: Readonly>>> = { inspect_macho: {}, inspect_signature: {}, - inspect_plist: { path: "/Applications/Example.app/Contents/Info.plist" }, + inspect_plist: {}, list_architectures: {}, observe_native_ui: { pid: 123, window_id: 456 }, capture_native_ui_scenario: { @@ -115,9 +115,13 @@ export const NATIVE_TOOL_CONTRACTS = [ ), native( "inspect_plist", - "Parse Info.plist from the active artifact by default, or pass any local plist path. Returns normalized JSON rather than plutil text. native_command_timeout_ms is each file or plutil command's wall clock (omitted means 60000 ms). The 67108864-byte plist input and decode allowances and the 128-level nesting allowance have no runtime override. The 4096-byte markup probe is a probe window, not a document cap, and has no runtime override.", + "Requires an open binary or bundle-resource session; use open_binary or open_bundle_resources first. Parse its Info.plist by default, or select a local plist with path within that session. Supplying path does not open a session. Returns normalized JSON rather than plutil text. native_command_timeout_ms is each file or plutil command's wall clock (omitted means 60000 ms). The 67108864-byte plist input and decode allowances and the 128-level nesting allowance have no runtime override. The 4096-byte markup probe is a probe window, not a document cap, and has no runtime override.", z.object({ - path: localPathStringSchema.optional(), + path: localPathStringSchema + .optional() + .describe( + "Optional plist selection for an already open binary or bundle-resource session; this does not open a session.", + ), native_command_timeout_ms: nativeCommandTimeoutField, }), ), diff --git a/src/server/BundleResourcesMcp.test.ts b/src/server/BundleResourcesMcp.test.ts index be852c1eaf..c63cdda5f2 100644 --- a/src/server/BundleResourcesMcp.test.ts +++ b/src/server/BundleResourcesMcp.test.ts @@ -100,6 +100,35 @@ it("routes bundle resource MCP calls with null executable identity and retained await expectResourceSchemaBranches(client); + const unbound = await client.callTool({ + name: "inspect_plist", + arguments: { path: plistPath }, + }); + expect(unbound.isError).toBe(true); + expect(unbound.structuredContent).toMatchObject({ + error: { code: "target_unavailable", category: "unavailable" }, + }); + const unopened = await client.callTool({ + name: "binary_session", + arguments: {}, + }); + expect( + toolContract("binary_session").outputSchema.parse( + unopened.structuredContent, + ), + ).toMatchObject({ + result: { + open: false, + tool_availability: expect.arrayContaining([ + expect.objectContaining({ + name: "inspect_plist", + available: false, + reason: "target_required", + }), + ]), + }, + }); + const opened = await client.callTool({ name: "open_bundle_resources", arguments: { path: bundle },