[GH-ISSUE #5092] [Bug] list_frida_processes does not advertise its required device selector #3127

Closed
opened 2026-10-31 00:36:57 +00:00 by bsakaguchi · 2 comments
Owner

Originally created by @coygeek on GitHub (Oct 18, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/5092

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

MCP or agent integration

Observed behavior

The actual tools/list input schema accepts {} for list_frida_processes, but tools/call rejects it with Select a device_id or a remote endpoint before invoking the provider. Both root properties are optional, and no root constraint or explicit description states that a selector is required. An example supplies device_id: "local", but does not state the omission rule.

Observed once through the SDK in-memory MCP transport and REA's production Frida registration, metadata projection and result delivery. This is a mocked provider boundary: no real Frida device or target was accessed. The explicit-local positive control succeeds and a subsequent ping works.

Reproduction

From a source checkout at the revision below with locked dependencies already installed, save this fixture as probe.mts in the checkout root. Verification ran the same fixture from isolated scratch with imports pointing to the checkout.

import { Client, InMemoryTransport } from '@modelcontextprotocol/client';
import { McpServer } from '@modelcontextprotocol/server';
import { Ajv2020 } from 'ajv/dist/2020.js';
import pino from 'pino';
import { registerFridaTools } from './src/server/registerFridaTools.ts';
import { createToolResultDelivery } from './src/server/toolResult.ts';
let calls = 0;
const provider = {
  async listProcesses(input) {
    calls++;
    return { ok: true, value: { deviceId: input.deviceId ?? 'local', processes: [], cleanupError: null } };
  },
};
const server = new McpServer({name:'schema-probe',version:'1'});
registerFridaTools(server, provider as never, pino({level:'silent'}), createToolResultDelivery(undefined));
const client = new Client({name:'schema-probe',version:'1'});
const [ct,st] = InMemoryTransport.createLinkedPair();
try {
  await server.connect(st);
  await client.connect(ct);
  const tool = (await client.listTools()).tools.find(t => t.name === 'list_frida_processes');
  if (!tool) throw new Error('Missing tool');
  console.log('advertised:', JSON.stringify(tool));
  const validate = new Ajv2020({strict:false,validateFormats:false}).compile(tool.inputSchema);
  for (const args of [{}, {device_id:'local'}]) {
    console.log('schema_accepts:',JSON.stringify(args),validate(args));
    const result = await client.callTool({name:tool.name,arguments:args});
    console.log('call:',JSON.stringify(args),JSON.stringify(result),'provider_calls:',calls);
  }
  console.log('ping:',JSON.stringify(await client.ping()));
} finally {
  await client.close();
  await server.close();
}

Run it with an isolated home and cleared inherited configuration:

WORK=$(mktemp -d)
mkdir -p "$WORK/home"
env -i PATH="$PATH" HOME="$WORK/home" node --import tsx ./probe.mts

The provider is a recorder that returns an empty synthetic process list. Inspect the advertised schema, the empty request rejection with zero provider calls, and the explicit-local success with one provider call.

Expected behavior

Make the required selector choice discoverable before invocation, through compatible schema constraints or explicit guidance that callers must provide device_id or remote. Preserve the intentional runtime rejection of omitted/conflicting selectors.

The canonical contract establishes the requirement. The schema advertisement policy describes preserved validation rules and expressing accepted input groups in guidance when model compatibility prevents root union keywords. This report does not require exact runtime/schema equivalence for every semantic predicate or a local default.

Impact and scope

An MCP client validating the empty process-list request against discovery accepts an input that fails before process discovery. The observed failure is bounded and recoverable: device_id: "local" succeeds with the synthetic provider. No evidence loss, target effects or connection failure was observed. Native-provider compatibility and other tools were not tested.

REA version

Source package version 6.7.0 at 3537e58c1ab29309f06eabf50bc52adba9817a0c.

Runtime and platform

Node.js v26.12.0; macOS 27.0.1; arm64. Locked SDK/Ajv/tsx dependencies from the source checkout. Native Frida was not invoked.

Target and artifact context

Synthetic empty process inventory returned by a recorder provider. No binary or process target.

Interface

MCP

Earliest failure stage

Transport or result projection

Relevant output or evidence

schema_accepts: {} true
call: {} isError=true
Input validation error: Invalid arguments for tool list_frida_processes: Select a device_id or a remote endpoint
provider_calls: 0
schema_accepts: {"device_id":"local"} true
call: {"device_id":"local"} structuredContent={"device_id":"local","processes":[],"cleanup_error":null}
provider_calls: 1
ping: {}

The observed root schema contains type: "object", optional device_id and remote properties, additionalProperties: false, and an explicit-local example. It has no root required list, property minimum, alternatives or selector requirement description. The remote object's address is required only when remote is present.

Production registration and projection entry point locate the exercised path.

Typed outcome or recovery expected

Discovery should state the required choice so a caller can submit {"device_id":"local"} or a selected remote endpoint on the first call. The existing rejection message correctly supplies that recovery after a failed call.

Analysis or suspected cause

Source-derived explanation: both selector fields are optional structurally; a Zod refinement supplies the requirement at runtime. The projection does not convert that refinement into a constraint or explicit input-group guidance. CLI/sibling default differences do not establish that this tool should accept an omitted selector.

Additional context

The relevant contract, registration, metadata projection and schema advertisement documentation are unchanged between rea-agents-6.7.0 and the tested source revision. Release runtime coverage is NOT_ENOUGH_INFO: the published package was not executed. No stdio or native-provider reproduction was performed.

Closed issue #1695 covers a different operation's collection/selector relationship; issue #1364 covers dropped comparison validation. Neither covers this required process-discovery selector.

Freshness review against 29910d16b98fe4b607a7aa15928789a73614b0b9: the required-selector contract and process-list description, production registration and advertisement path remain unchanged. The schema advertisement policy also remains unchanged; only its line numbers moved. This is a source freshness check, not a new SDK or native Frida reproduction. The original tested SHA and mocked-provider runtime scope above remain unchanged.

Originally created by @coygeek on GitHub (Oct 18, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5092 ## Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ## Area MCP or agent integration ## Observed behavior The actual `tools/list` input schema accepts `{}` for `list_frida_processes`, but `tools/call` rejects it with `Select a device_id or a remote endpoint` before invoking the provider. Both root properties are optional, and no root constraint or explicit description states that a selector is required. An example supplies `device_id: "local"`, but does not state the omission rule. Observed once through the SDK in-memory MCP transport and REA's production Frida registration, metadata projection and result delivery. This is a mocked provider boundary: no real Frida device or target was accessed. The explicit-local positive control succeeds and a subsequent ping works. ## Reproduction From a source checkout at the revision below with locked dependencies already installed, save this fixture as `probe.mts` in the checkout root. Verification ran the same fixture from isolated scratch with imports pointing to the checkout. ```ts import { Client, InMemoryTransport } from '@modelcontextprotocol/client'; import { McpServer } from '@modelcontextprotocol/server'; import { Ajv2020 } from 'ajv/dist/2020.js'; import pino from 'pino'; import { registerFridaTools } from './src/server/registerFridaTools.ts'; import { createToolResultDelivery } from './src/server/toolResult.ts'; let calls = 0; const provider = { async listProcesses(input) { calls++; return { ok: true, value: { deviceId: input.deviceId ?? 'local', processes: [], cleanupError: null } }; }, }; const server = new McpServer({name:'schema-probe',version:'1'}); registerFridaTools(server, provider as never, pino({level:'silent'}), createToolResultDelivery(undefined)); const client = new Client({name:'schema-probe',version:'1'}); const [ct,st] = InMemoryTransport.createLinkedPair(); try { await server.connect(st); await client.connect(ct); const tool = (await client.listTools()).tools.find(t => t.name === 'list_frida_processes'); if (!tool) throw new Error('Missing tool'); console.log('advertised:', JSON.stringify(tool)); const validate = new Ajv2020({strict:false,validateFormats:false}).compile(tool.inputSchema); for (const args of [{}, {device_id:'local'}]) { console.log('schema_accepts:',JSON.stringify(args),validate(args)); const result = await client.callTool({name:tool.name,arguments:args}); console.log('call:',JSON.stringify(args),JSON.stringify(result),'provider_calls:',calls); } console.log('ping:',JSON.stringify(await client.ping())); } finally { await client.close(); await server.close(); } ``` Run it with an isolated home and cleared inherited configuration: ```sh WORK=$(mktemp -d) mkdir -p "$WORK/home" env -i PATH="$PATH" HOME="$WORK/home" node --import tsx ./probe.mts ``` The provider is a recorder that returns an empty synthetic process list. Inspect the advertised schema, the empty request rejection with zero provider calls, and the explicit-local success with one provider call. ## Expected behavior Make the required selector choice discoverable before invocation, through compatible schema constraints or explicit guidance that callers must provide `device_id` or `remote`. Preserve the intentional runtime rejection of omitted/conflicting selectors. The [canonical contract](https://github.com/morluto/rea/blob/3537e58c1ab29309f06eabf50bc52adba9817a0c/src/contracts/fridaToolContracts.ts#L88-L113) establishes the requirement. The [schema advertisement policy](https://github.com/morluto/rea/blob/3537e58c1ab29309f06eabf50bc52adba9817a0c/docs/mcp-contracts.md#L722-L750) describes preserved validation rules and expressing accepted input groups in guidance when model compatibility prevents root union keywords. This report does not require exact runtime/schema equivalence for every semantic predicate or a local default. ## Impact and scope An MCP client validating the empty process-list request against discovery accepts an input that fails before process discovery. The observed failure is bounded and recoverable: `device_id: "local"` succeeds with the synthetic provider. No evidence loss, target effects or connection failure was observed. Native-provider compatibility and other tools were not tested. ## REA version Source package version 6.7.0 at `3537e58c1ab29309f06eabf50bc52adba9817a0c`. ## Runtime and platform Node.js v26.12.0; macOS 27.0.1; arm64. Locked SDK/Ajv/tsx dependencies from the source checkout. Native Frida was not invoked. ## Target and artifact context Synthetic empty process inventory returned by a recorder provider. No binary or process target. ## Interface MCP ## Earliest failure stage Transport or result projection ## Relevant output or evidence ```text schema_accepts: {} true call: {} isError=true Input validation error: Invalid arguments for tool list_frida_processes: Select a device_id or a remote endpoint provider_calls: 0 schema_accepts: {"device_id":"local"} true call: {"device_id":"local"} structuredContent={"device_id":"local","processes":[],"cleanup_error":null} provider_calls: 1 ping: {} ``` The observed root schema contains `type: "object"`, optional `device_id` and `remote` properties, `additionalProperties: false`, and an explicit-local example. It has no root required list, property minimum, alternatives or selector requirement description. The remote object's `address` is required only when `remote` is present. [Production registration](https://github.com/morluto/rea/blob/3537e58c1ab29309f06eabf50bc52adba9817a0c/src/server/registerFridaTools.ts#L63-L89) and [projection entry point](https://github.com/morluto/rea/blob/3537e58c1ab29309f06eabf50bc52adba9817a0c/src/contracts/toolSchemaMetadata.ts#L62-L90) locate the exercised path. ## Typed outcome or recovery expected Discovery should state the required choice so a caller can submit `{"device_id":"local"}` or a selected remote endpoint on the first call. The existing rejection message correctly supplies that recovery after a failed call. ## Analysis or suspected cause Source-derived explanation: both selector fields are optional structurally; a Zod refinement supplies the requirement at runtime. The projection does not convert that refinement into a constraint or explicit input-group guidance. CLI/sibling default differences do not establish that this tool should accept an omitted selector. ## Additional context The relevant contract, registration, metadata projection and schema advertisement documentation are unchanged between `rea-agents-6.7.0` and the tested source revision. Release runtime coverage is NOT_ENOUGH_INFO: the published package was not executed. No stdio or native-provider reproduction was performed. Closed [issue #1695](https://github.com/morluto/rea/issues/1695) covers a different operation's collection/selector relationship; [issue #1364](https://github.com/morluto/rea/issues/1364) covers dropped comparison validation. Neither covers this required process-discovery selector. Freshness review against `29910d16b98fe4b607a7aa15928789a73614b0b9`: the [required-selector contract and process-list description](https://github.com/morluto/rea/blob/29910d16b98fe4b607a7aa15928789a73614b0b9/src/contracts/fridaToolContracts.ts#L88-L113), production registration and advertisement path remain unchanged. The [schema advertisement policy](https://github.com/morluto/rea/blob/29910d16b98fe4b607a7aa15928789a73614b0b9/docs/mcp-contracts.md#L733-L761) also remains unchanged; only its line numbers moved. This is a source freshness check, not a new SDK or native Frida reproduction. The original tested SHA and mocked-provider runtime scope above remain unchanged.
Author
Owner

@XinwangTao commented on GitHub (Oct 19, 2026):

This is a contract/schema mismatch — the advertised schema makes both selectors optional, so an MCP client has no way to discover that one is actually required without hitting the rejection at call time.

I'd suggest expressing the requirement directly in the schema instead of only in the error message: e.g. an anyOf/oneOf on device_id / remote endpoint (or a required field plus a description stating the omission rule). That keeps tools/list honest and lets clients validate before invoking. If "local" is meant as a safe default, then accepting {} would be consistent too — but then the rejection message should not contradict it.

<!-- gh-comment-id:6262345694 --> @XinwangTao commented on GitHub (Oct 19, 2026): This is a contract/schema mismatch — the advertised schema makes both selectors optional, so an MCP client has no way to discover that one is actually required without hitting the rejection at call time. I'd suggest expressing the requirement directly in the schema instead of only in the error message: e.g. an anyOf/oneOf on device_id / remote endpoint (or a required field plus a description stating the omission rule). That keeps tools/list honest and lets clients validate before invoking. If "local" is meant as a safe default, then accepting {} would be consistent too — but then the rejection message should not contradict it.
Author
Owner

@morluto commented on GitHub (Oct 19, 2026):

Fixed on main in 2ec602b191.

The canonical process-list input schema now explicitly says to provide exactly one of device_id or remote, explains the explicit local selector, and states that remote requires an address. Runtime required/exclusive selector refinements are preserved. No local default was introduced.

The actual SDK tools/list regression failed before the change because root guidance was absent. It now validates the advertised JSON Schema with AJV2020, confirms both explicit selector forms execute, and confirms omission/conflict are rejected by SDK runtime validation with the expected reasons. This does not claim that the flattened advertised JSON Schema encodes the refinements as machine constraints.

124 focused tests/5 files, check and docs:check passed on Node24.18.0; final MCP contract file rerun3/3 passed after extracting the selector helper to satisfy the existing lint limit. Simulated provider results, no real target or model-selection efficiency measurement.

<!-- gh-comment-id:6264956723 --> @morluto commented on GitHub (Oct 19, 2026): Fixed on main in 2ec602b191d3b36dd17733e3e183457c57651c60. The canonical process-list input schema now explicitly says to provide exactly one of device_id or remote, explains the explicit local selector, and states that remote requires an address. Runtime required/exclusive selector refinements are preserved. No local default was introduced. The actual SDK tools/list regression failed before the change because root guidance was absent. It now validates the advertised JSON Schema with AJV2020, confirms both explicit selector forms execute, and confirms omission/conflict are rejected by SDK runtime validation with the expected reasons. This does not claim that the flattened advertised JSON Schema encodes the refinements as machine constraints. 124 focused tests/5 files, check and docs:check passed on Node24.18.0; final MCP contract file rerun3/3 passed after extracting the selector helper to satisfy the existing lint limit. Simulated provider results, no real target or model-selection efficiency measurement.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#3127
No description provided.