[GH-ISSUE #6454] [Bug] rea mcp returns -32603 for invalid prompt, completion, and list parameters #3869

Closed
opened 2026-10-31 00:44:47 +00:00 by bsakaguchi · 1 comment
Owner

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

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

An initialized node scripts/rea.mjs mcp server returns JSON-RPC -32603 (Internal error) for schema-invalid parameters to prompts/get, prompts/list, completion/complete, and tools/list. For example, prompts/get with {"name":"investigate_feature","arguments":{"feature":5}} returns an internal-error code with a serialized validation issue identifying the numeric argument.

The same connection returns -32602 (Invalid params) for tools/call without a name. Both error messages contain multiline validation issues; the inconsistency reported here is the protocol error code. Corrected prompt retrieval, prompt listing and prompt completion succeed afterward.

Reproduction

Start from a fresh source checkout at 9eb829aca34be36ff112826337996f6139f82eff and prepare the runtime:

npm ci && npm run build:cached

From that checkout root, run this inline fixture. It creates an isolated home, initializes the public stdio server, and sends requests sequentially without a target or analysis provider:

WORK=$(mktemp -d)
export WORK
mkdir -p "$WORK/home"
python3 <<'PY'
import json, os, queue, subprocess, threading
p = subprocess.Popen(
    ['node', 'scripts/rea.mjs', 'mcp'],
    stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
    text=True, env=dict(os.environ, HOME=os.path.join(os.environ['WORK'], 'home')))
replies = queue.Queue()
def read():
    for line in p.stdout:
        replies.put(json.loads(line))
threading.Thread(target=read, daemon=True).start()
def send(method, params, request_id=None):
    message = dict(jsonrpc='2.0', method=method, params=params)
    if request_id is not None:
        message['id'] = request_id
    p.stdin.write(json.dumps(message) + '\n')
    p.stdin.flush()
    if request_id is None:
        return
    while True:
        response = replies.get(timeout=30)
        if response.get('id') == request_id:
            print(json.dumps(dict(method=method, params=params,
                error=response.get('error'),
                result_keys=list(response.get('result', {})))))
            return
try:
    send('initialize', dict(protocolVersion='2025-06-18', capabilities={},
        clientInfo=dict(name='params-repro', version='1')), 1)
    send('notifications/initialized', {})
    cases = [
        ('prompts/get', {'name':'investigate_feature','arguments':{'feature':5}}),
        ('prompts/get', {'name':'investigate_feature','arguments':None}),
        ('prompts/list', {'cursor':5}),
        ('completion/complete', {'ref':{'type':'ref/tool','name':'binary_session'},
            'argument':{'name':'foo','value':''}}),
        ('tools/list', {'cursor':5}),
        ('tools/call', {'arguments':{}}),
        ('prompts/get', {'name':'investigate_feature',
            'arguments':{'feature':'offline search'}}),
        ('prompts/list', {}),
        ('completion/complete', {'ref':{'type':'ref/prompt','name':'investigate_feature'},
            'argument':{'name':'provider','value':''}}),
    ]
    for request_id, (method, params) in enumerate(cases, 2):
        send(method, params, request_id)
finally:
    p.stdin.close()
    try:
        p.wait(timeout=10)
    except subprocess.TimeoutExpired:
        p.terminate()
        p.wait(timeout=10)
    print('exit', p.returncode, 'stderr', repr(p.stderr.read()))
PY
rm -rf "$WORK"

Each malformed case reproduced in two fresh server processes. The inline fixture was also replayed against the prepared baseline. Dependency installation and rebuilding above are fresh-checkout setup instructions; they were not rerun during these observations.

Expected behavior

Reject schema-invalid method parameters with JSON-RPC -32602, preserving useful field/type information. Reserve -32603 for internal failures. This follows the JSON-RPC error-code definitions.

REA documents these public prompt discovery/retrieval methods and explicitly limits completion to prompt arguments rather than arbitrary tool arguments. Its prompt argument schema requires strings. Rejecting these inputs is correct; classifying their parameter violations as internal failures is incorrect.

Impact and scope

P2: bounded protocol error-classification failure. Clients receive the server-fault code instead of the caller-parameter code for the tested malformed requests. The behavior was consistent across both fresh sessions, and corrected prompt/list/completion requests worked on the same connections. No target acquisition or external service is required.

Submitting valid parameters is a demonstrated workaround for ordinary use. No particular client's retry policy, disconnect, data loss or analysis failure was tested or observed. No security boundary crossed.

REA version

6.7.0 (source 9eb829aca3)

Runtime and platform

Node.js v26.12.1; macOS 27.0.1; arm64. Locked @modelcontextprotocol/server 2.3.1. Protocol 2025-06-18 was requested and negotiated. The tested Node runtime is supported by the manifest; the development pin v24.18.0 was not used.

Target and artifact context

No binary, artifact, target, engine or external service. Fresh local stdio MCP sessions with an isolated HOME and inline malformed JSON-RPC parameters.

Interface

MCP

Earliest failure stage

Request parsing or validation

Relevant output or evidence

Observed code summary, identical in both initial sessions:

prompts/get {name:investigate_feature, arguments:{feature:5}} -> -32603
prompts/get {name:investigate_feature, arguments:null}       -> -32603
prompts/list {cursor:5}                                    -> -32603
completion/complete {ref:{type:ref/tool,...},argument:...}   -> -32603
tools/list {cursor:5}                                      -> -32603
tools/call {arguments:{}}                                 -> -32602
valid prompts/get, prompts/list, completion/complete      -> success
server exit: 0; stderr: empty

Exact decoded error for the numeric prompt argument:

{"code":-32603,"message":"[\n  {\n    \"expected\": \"string\",\n    \"code\": \"invalid_type\",\n    \"path\": [\n      \"params\",\n      \"arguments\",\n      \"feature\"\n    ],\n    \"message\": \"Invalid input: expected string, received number\"\n  }\n]"}

The tools/call control returns a similar issue array prefixed by Invalid tools/call request: with code -32602.

Typed outcome or recovery expected

Return -32602 for these parameter-schema failures and retain the rejected field/type diagnosis. Preserve successful corrected requests and retain -32603 for genuine internal errors. The latter is a proposed regression control, not an internal failure reproduced here.

Analysis or suspected cause

Source-derived explanation: the locked SDK's generic spec-method request wrapper throws a plain Error when wire-schema validation fails. The protocol dispatcher defaults uncoded exceptions to InternalError. Its special tools/call wrapper instead throws a protocol InvalidParams error for request validation.

REA's error transport connects around the SDK, but its recovery path passes through non-tool JSON-RPC errors, apart from unknown-tool remediation. The observed misclassification therefore reaches the REA caller unchanged. The dependency is pinned in the manifest.

Additional context

No matching issue found in open/closed searches for the method names, Invalid params, Internal error, -32603, and request-validation function names. Related #3221 concerns oversized tool-result diagnostics and #4636 concerns canonical tool-result validation errors; neither concerns this JSON-RPC error-code path.

NOT_ENOUGH_INFO: no last-known-good revision or release was established. Only the stated baseline and stdio transport were exercised; HTTP behavior remains untested.

Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6454 ### 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 An initialized `node scripts/rea.mjs mcp` server returns JSON-RPC `-32603` (Internal error) for schema-invalid parameters to `prompts/get`, `prompts/list`, `completion/complete`, and `tools/list`. For example, `prompts/get` with `{"name":"investigate_feature","arguments":{"feature":5}}` returns an internal-error code with a serialized validation issue identifying the numeric argument. The same connection returns `-32602` (Invalid params) for `tools/call` without a name. Both error messages contain multiline validation issues; the inconsistency reported here is the protocol error code. Corrected prompt retrieval, prompt listing and prompt completion succeed afterward. ### Reproduction Start from a fresh source checkout at `9eb829aca34be36ff112826337996f6139f82eff` and prepare the runtime: ```sh npm ci && npm run build:cached ``` From that checkout root, run this inline fixture. It creates an isolated home, initializes the public stdio server, and sends requests sequentially without a target or analysis provider: ```sh WORK=$(mktemp -d) export WORK mkdir -p "$WORK/home" python3 <<'PY' import json, os, queue, subprocess, threading p = subprocess.Popen( ['node', 'scripts/rea.mjs', 'mcp'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, env=dict(os.environ, HOME=os.path.join(os.environ['WORK'], 'home'))) replies = queue.Queue() def read(): for line in p.stdout: replies.put(json.loads(line)) threading.Thread(target=read, daemon=True).start() def send(method, params, request_id=None): message = dict(jsonrpc='2.0', method=method, params=params) if request_id is not None: message['id'] = request_id p.stdin.write(json.dumps(message) + '\n') p.stdin.flush() if request_id is None: return while True: response = replies.get(timeout=30) if response.get('id') == request_id: print(json.dumps(dict(method=method, params=params, error=response.get('error'), result_keys=list(response.get('result', {}))))) return try: send('initialize', dict(protocolVersion='2025-06-18', capabilities={}, clientInfo=dict(name='params-repro', version='1')), 1) send('notifications/initialized', {}) cases = [ ('prompts/get', {'name':'investigate_feature','arguments':{'feature':5}}), ('prompts/get', {'name':'investigate_feature','arguments':None}), ('prompts/list', {'cursor':5}), ('completion/complete', {'ref':{'type':'ref/tool','name':'binary_session'}, 'argument':{'name':'foo','value':''}}), ('tools/list', {'cursor':5}), ('tools/call', {'arguments':{}}), ('prompts/get', {'name':'investigate_feature', 'arguments':{'feature':'offline search'}}), ('prompts/list', {}), ('completion/complete', {'ref':{'type':'ref/prompt','name':'investigate_feature'}, 'argument':{'name':'provider','value':''}}), ] for request_id, (method, params) in enumerate(cases, 2): send(method, params, request_id) finally: p.stdin.close() try: p.wait(timeout=10) except subprocess.TimeoutExpired: p.terminate() p.wait(timeout=10) print('exit', p.returncode, 'stderr', repr(p.stderr.read())) PY rm -rf "$WORK" ``` Each malformed case reproduced in two fresh server processes. The inline fixture was also replayed against the prepared baseline. Dependency installation and rebuilding above are fresh-checkout setup instructions; they were not rerun during these observations. ### Expected behavior Reject schema-invalid method parameters with JSON-RPC `-32602`, preserving useful field/type information. Reserve `-32603` for internal failures. This follows the [JSON-RPC error-code definitions](https://www.jsonrpc.org/specification#error_object). REA documents these public [prompt discovery/retrieval methods](https://github.com/morluto/rea/blob/9eb829aca34be36ff112826337996f6139f82eff/docs/mcp-prompts.md#L41) and explicitly limits [completion to prompt arguments rather than arbitrary tool arguments](https://github.com/morluto/rea/blob/9eb829aca34be36ff112826337996f6139f82eff/docs/mcp-prompts.md#L66). Its [prompt argument schema requires strings](https://github.com/morluto/rea/blob/9eb829aca34be36ff112826337996f6139f82eff/src/server/promptArguments.ts#L23). Rejecting these inputs is correct; classifying their parameter violations as internal failures is incorrect. ### Impact and scope P2: bounded protocol error-classification failure. Clients receive the server-fault code instead of the caller-parameter code for the tested malformed requests. The behavior was consistent across both fresh sessions, and corrected prompt/list/completion requests worked on the same connections. No target acquisition or external service is required. Submitting valid parameters is a demonstrated workaround for ordinary use. No particular client's retry policy, disconnect, data loss or analysis failure was tested or observed. No security boundary crossed. ### REA version 6.7.0 (source 9eb829aca34be36ff112826337996f6139f82eff) ### Runtime and platform Node.js v26.12.1; macOS 27.0.1; arm64. Locked `@modelcontextprotocol/server` 2.3.1. Protocol 2025-06-18 was requested and negotiated. The tested Node runtime is supported by the manifest; the development pin v24.18.0 was not used. ### Target and artifact context No binary, artifact, target, engine or external service. Fresh local stdio MCP sessions with an isolated HOME and inline malformed JSON-RPC parameters. ### Interface MCP ### Earliest failure stage Request parsing or validation ### Relevant output or evidence Observed code summary, identical in both initial sessions: ```text prompts/get {name:investigate_feature, arguments:{feature:5}} -> -32603 prompts/get {name:investigate_feature, arguments:null} -> -32603 prompts/list {cursor:5} -> -32603 completion/complete {ref:{type:ref/tool,...},argument:...} -> -32603 tools/list {cursor:5} -> -32603 tools/call {arguments:{}} -> -32602 valid prompts/get, prompts/list, completion/complete -> success server exit: 0; stderr: empty ``` Exact decoded error for the numeric prompt argument: ```json {"code":-32603,"message":"[\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"params\",\n \"arguments\",\n \"feature\"\n ],\n \"message\": \"Invalid input: expected string, received number\"\n }\n]"} ``` The `tools/call` control returns a similar issue array prefixed by `Invalid tools/call request:` with code `-32602`. ### Typed outcome or recovery expected Return `-32602` for these parameter-schema failures and retain the rejected field/type diagnosis. Preserve successful corrected requests and retain `-32603` for genuine internal errors. The latter is a proposed regression control, not an internal failure reproduced here. ### Analysis or suspected cause Source-derived explanation: the locked SDK's generic spec-method request wrapper throws a plain `Error` when wire-schema validation fails. The protocol dispatcher defaults uncoded exceptions to InternalError. Its special `tools/call` wrapper instead throws a protocol InvalidParams error for request validation. REA's [error transport connects around the SDK](https://github.com/morluto/rea/blob/9eb829aca34be36ff112826337996f6139f82eff/src/server/EvidenceMcpServerCore.ts#L276), but its [recovery path passes through non-tool JSON-RPC errors](https://github.com/morluto/rea/blob/9eb829aca34be36ff112826337996f6139f82eff/src/server/EvidenceMcpServerCore.ts#L446), apart from [unknown-tool remediation](https://github.com/morluto/rea/blob/9eb829aca34be36ff112826337996f6139f82eff/src/server/EvidenceMcpServerCore.ts#L678). The observed misclassification therefore reaches the REA caller unchanged. The dependency is [pinned in the manifest](https://github.com/morluto/rea/blob/9eb829aca34be36ff112826337996f6139f82eff/package.json#L339). ### Additional context No matching issue found in open/closed searches for the method names, `Invalid params`, `Internal error`, `-32603`, and request-validation function names. Related [#3221](https://github.com/morluto/rea/issues/3221) concerns oversized tool-result diagnostics and [#4636](https://github.com/morluto/rea/issues/4636) concerns canonical tool-result validation errors; neither concerns this JSON-RPC error-code path. NOT_ENOUGH_INFO: no last-known-good revision or release was established. Only the stated baseline and stdio transport were exercised; HTTP behavior remains untested.
Author
Owner

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

Fixed on main in github.com/morluto/rea@6eb9d09268. REA validates named canonical SDK method schemas at the incoming protocol boundary, returning InvalidParams for malformed discovery inputs.

Validation: tests/boundary/mcp/promptMcp.test.ts passed in the retained audit receipt. All five reported malformed request shapes return -32602; corrected methods succeed; injected internal handler failure remains -32603.

Scope: Linked SDK in-memory MCP transport; native clients and HTTP were not rerun. This checks the shared request semantics.

<!-- gh-comment-id:6376017280 --> @morluto commented on GitHub (Oct 23, 2026): Fixed on main in https://github.com/morluto/rea/commit/6eb9d09268b59b28e5c62842ebb4b0effd207936. REA validates named canonical SDK method schemas at the incoming protocol boundary, returning InvalidParams for malformed discovery inputs. Validation: `tests/boundary/mcp/promptMcp.test.ts` passed in the retained audit receipt. All five reported malformed request shapes return -32602; corrected methods succeed; injected internal handler failure remains -32603. Scope: Linked SDK in-memory MCP transport; native clients and HTTP were not rerun. This checks the shared request semantics.
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#3869
No description provided.