Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat(mcp): add 'mcp list' / 'mcp call' general MCP client#1453

Open
shophypelive-cloud wants to merge 1 commit into
larksuite:main from
shophypelive-cloud:feat/mcp-general-client
Open

feat(mcp): add 'mcp list' / 'mcp call' general MCP client #1453
shophypelive-cloud wants to merge 1 commit into
larksuite:main from
shophypelive-cloud:feat/mcp-general-client

Conversation

@shophypelive-cloud

@shophypelive-cloud shophypelive-cloud commented Jun 13, 2026
edited by coderabbitai Bot
Loading

Copy link
Copy Markdown

Summary

Add a general MCP client to lark-cli — mcp list and mcp call talk to any Model Context Protocol server over JSON-RPC 2.0 (Streamable HTTP). This complements the existing Lark-managed MCP path used internally by shortcuts (shortcuts/common/mcp_client.go), which is hardwired to Lark's endpoint and proprietary X-Lark-MCP-* headers and cannot reach a third-party server.

Changes

  • New cmd/mcp command group:
    • lark-cli mcp list --url <server> → JSON-RPC tools/list
    • lark-cli mcp call --url <server> --tool <name> --args '<json>'tools/call
  • Flags: --url (required), --tool (required for call), --args (JSON object, default {}), repeatable --header "Key: Value" for arbitrary server auth. No Lark credentials are sent to the external server.
  • Security: the user-supplied --url is SSRF-guarded via internal/validate — https-only, rejects localhost/private/link-local/CGNAT hosts, and re-checks the resolved IP at dial time to defeat DNS rebinding.
  • Robustness: handles Streamable-HTTP SSE response framing; returns the raw JSON-RPC result under data so it composes with --jq.
  • Errors: typed *output.ExitError throughout (ErrValidation / ErrNetwork / Errorf(ExitAPI, ...)); no bare fmt.Errorf.
  • Placed in cmd/ (not shortcuts/) so it may use net/http directly, per the shortcuts-no-raw-http forbidigo policy. Registered in cmd/build.go.

No new dependencies (reuses github.com/google/uuid, cobra, and internal/*).

Test Plan

  • Unit tests (go test -race ./cmd/mcp/...): httptest-based table tests covering tools/list, tools/call, SSE framing, JSON-RPC error payload, HTTP ≥400, non-JSON body, and --header parse + on-the-wire delivery.
  • Manual local verification: mcp --help, mcp list/call --help, SSRF rejection of http://localhost:..., --args JSON validation — all return the correct JSON error envelope.
  • gofmt -l clean · go vet clean · golangci-lint@v2.1.6 run --new-from-rev=origin/main0 issues · go mod tidy no-op.

Related Issues

  • None

Summary by CodeRabbit

  • New Features
    • Added mcp command group for interacting with MCP servers
    • mcp list subcommand lists available tools from a target MCP server
    • mcp call subcommand invokes specific tools on a target MCP server
    • Both commands support custom authentication headers and comprehensive error handling

Add a top-level `mcp` command group that talks to ANY MCP server over
JSON-RPC 2.0 (Streamable HTTP), complementing the Lark-managed MCP path
that shortcuts use internally (shortcuts/common/mcp_client.go).
- `lark-cli mcp list --url <server>` -> JSON-RPC tools/list
- `lark-cli mcp call --url <server> --tool <name> --args <json>` -> tools/call
- repeatable `--header "Key: Value"` for arbitrary server auth; no Lark
 credentials are sent to the external server
- user-supplied --url is SSRF-guarded via internal/validate (https-only,
 rejects localhost/private/link-local/CGNAT, DNS-rebind re-check at dial)
- handles Streamable-HTTP SSE response framing
- typed *output.ExitError throughout; raw JSON-RPC result returned under
 `data` so it composes with --jq
Lives in cmd/ (not shortcuts/) so it may use net/http directly per the
forbidigo `shortcuts-no-raw-http` policy.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

coderabbitai Bot commented Jun 13, 2026
edited
Loading

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces a new mcp command group to the CLI that provides JSON-RPC 2.0 HTTP client functionality. It registers an mcp root command with two subcommands—mcp list and mcp call—for querying and invoking tools on arbitrary MCP servers via URL and optional HTTP headers.

Changes

MCP JSON-RPC CLI Client

Layer / File(s) Summary
MCP Root Command & CLI Integration
cmd/mcp/mcp.go, cmd/build.go
The mcp command group is defined with --url and repeatable --header support, disables authentication, and is registered as a subcommand in the root lark-cli command tree.
MCP JSON-RPC Client Implementation & Tests
cmd/mcp/client.go, cmd/mcp/client_test.go
Low-level HTTP client handles header parsing, HTTPS-only validation, JSON-RPC 2.0 request/response marshaling with size limits, SSE frame decoding, and error normalization. Tests verify tools/list and tools/call workflows, SSE handling, HTTP/JSON-RPC errors, and header transmission.
MCP List Command
cmd/mcp/list.go
The mcp list subcommand queries available tools from an MCP server via JSON-RPC tools/list and outputs the result as JSON with ok/data structure.
MCP Call Command
cmd/mcp/call.go
The mcp call subcommand invokes a named tool on an MCP server, validates --args as JSON, constructs JSON-RPC tools/call parameters, and outputs the result as JSON with ok/data structure.

Sequence Diagram

sequenceDiagram
 participant User
 participant CLI as mcp list/call
 participant Client as jsonRPC
 participant Server as HTTP Server
 User->>CLI: Invoke with --url, --header, args
 CLI->>Client: parseHeaders, httpClientFor
 Client->>Server: POST JSON-RPC 2.0 request
 Server->>Client: Response (text/event-stream or JSON)
 Client->>Client: decodeJSONRPC (extract SSE if present)
 Client->>Client: Unmarshal result or error
 alt JSON-RPC error or HTTP error
 Client->>User: ExitAPI error
 else Success
 Client->>User: OK with result
 end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A new command hops into view,
Querying MCP servers through and through,
JSON-RPC calls with headers so fine,
Tools listed and invoked in a line,
The CLI now speaks with SSE and grace!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.91% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a general MCP client with 'mcp list' and 'mcp call' commands to the CLI.
Description check ✅ Passed The description comprehensively covers all required sections: summary explains the motivation, changes lists the new command group and flags, test plan details unit and manual testing, and related issues is addressed.
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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions github-actions Bot added the size/L Large or sensitive change across domains or core paths label Jun 13, 2026

@LuuOW LuuOW 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.

Technical audit: Architecture and implementation patterns verified for consistency with MCP ecosystem standards.

@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: 8

🤖 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 `@cmd/mcp/call.go`:
- Around line 52-54: Replace the legacy output.ErrValidation return in the
json.Unmarshal failure path (the block that checks if
json.Unmarshal([]byte(raw), &arguments) returns an error) with a typed
validation error: call errs.NewValidationError(errs.SubtypeInvalidArgument,
"invalid --args (must be a JSON object): %v", err), chain .WithParam("--args")
and preserve the original error as the cause (e.g., .WithCause(err)), and return
that instead of output.ErrValidation.
In `@cmd/mcp/client_test.go`:
- Around line 17-24: Update the error-path tests to assert the typed problem
metadata and preserved causes instead of only inspecting *output.ExitError
fields: where tests currently call asExit (the helper shown) and then assert
ExitError.Code/Detail, change them to use errs.ProblemOf on the returned error
to assert category/subtype/params, and also use errors.As to check for a
*errs.ValidationError when you need to assert Param values; additionally assert
that the original cause is preserved (use errors.Unwrap or errors.As on the err
returned by the operation to verify the underlying cause). Apply these changes
at the other affected test sites (the regions noted at lines 96-103, 109-113,
119-121, 136-141) so each test validates typed metadata via errs.ProblemOf and
cause chaining rather than only the ExitError shape.
- Around line 45-161: Add unit tests in cmd/mcp/client_test.go that directly
exercise the URL validation in the httpClientFor function: create tests that
call httpClientFor with invalid/unsafe URLs (e.g., "file://", "data:", malformed
URLs) and assert it returns an error/ExitValidation, and add a test that calls
httpClientFor with an http:// URL to assert HTTPS-only enforcement (expect
error) and another that toggles any existing "allow HTTP" option/flag (or
context) to verify http:// is accepted when explicitly allowed; place these new
tests alongside the existing TestJSONRPC_* tests and use the httpClientFor
identifier so the test fails if the validation behavior changes.
- Around line 154-155: The test currently ignores the error return from
parseHeaders (used to build h) which can mask setup failures and cause
misleading jsonRPC errors; update the test to capture the error (e.g., h, err :=
parseHeaders(...)) and immediately fail the test if err != nil (use t.Fatalf or
t.Fatal/require for the test framework in use) before calling jsonRPC so that
parseHeaders failures are reported directly.
In `@cmd/mcp/client.go`:
- Line 33: The command code returns legacy output.ErrValidation for bad header
parsing; replace that with a typed errs.* error, e.g.
errs.InvalidUserInput("invalid --header %q (expected \"Key: Value\")",
item).WithParam("--header") and, if this is wrapping a lower-level error, chain
the cause via .WithCause(err); update every other legacy output.* site in
cmd/mcp/client.go (the return at the header parse, and the other occurrences
flagged in the review) to use the appropriate errs.* constructor, include
WithParam("--flag") for user-argument errors and .WithCause(...) when there is
an underlying error so typed metadata and cause propagation are preserved.
- Around line 112-121: The SSE parsing loop in cmd/mcp/client.go currently
overwrites the variable last for each "data:" line, dropping earlier lines;
change the loop that inspects bytes.Split(trimmed, []byte("\n")) to accumulate
each bytes.TrimSpace(rest) into a slice or bytes.Buffer and then set trimmed to
the concatenation of those lines joined with a single '\n' (SSE semantics)
instead of only the last line; keep the existing use of bytes.CutPrefix and
trimming but replace the single-variable last with an append-and-join approach
so multiline `data:` payloads are preserved.
- Around line 101-105: The code currently returns payload["result"] even when
the "result" key is missing; change the flow to treat a response missing both
"error" and "result" as malformed. Specifically, after checking if errObj, ok :=
payload["error"]; ok { return nil, mcpErrorFrom(errObj) }, add an explicit check
for the presence of "result" (e.g. if res, ok := payload["result"]; ok { return
res, nil }) and otherwise return nil and an error (e.g. fmt.Errorf or a suitable
MCP error) indicating "malformed JSON-RPC response: missing result and error".
Keep references to payload, "error", "result", and mcpErrorFrom when
implementing the fix.
In `@cmd/mcp/list.go`:
- Around line 39-50: runList currently returns lower-layer errors directly but
must convert legacy output.Err*/output.Errorf errors into the new typed errs.*
before returning while leaving already-typed errs.* untouched; update runList
(and its error returns from parseHeaders, httpClientFor, jsonRPC) to detect
legacy output errors and wrap/translate them into the appropriate errs.* variant
(preserving message and metadata) and pass through any errs.* errors unchanged;
use a small converter helper (e.g., translateLegacyError(err) used inside
runList) to centralize the mapping logic so future calls
(parseHeaders/httpClientFor/jsonRPC) get normalized typed errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

i️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 657f7227-060e-455e-ae82-834bde4c512c

📥 Commits

Reviewing files that changed from the base of the PR and between c0730b4 and 70fd77a.

📒 Files selected for processing (6)
  • cmd/build.go
  • cmd/mcp/call.go
  • cmd/mcp/client.go
  • cmd/mcp/client_test.go
  • cmd/mcp/list.go
  • cmd/mcp/mcp.go

Comment thread cmd/mcp/call.go
Comment on lines +52 to +54
if err := json.Unmarshal([]byte(raw), &arguments); err != nil {
return output.ErrValidation("invalid --args (must be a JSON object): %v", err)
}

@coderabbitai coderabbitai Bot Jun 13, 2026

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

Use typed validation error for --args parsing failures.

Line 53 returns legacy output.ErrValidation, but --args is a user flag validation failure and should return a typed invalid-argument error with param="--args" and preserved cause.

Suggested fix
-	if err := json.Unmarshal([]byte(raw), &arguments); err != nil {
-		return output.ErrValidation("invalid --args (must be a JSON object): %v", err)
-	}
+	if err := json.Unmarshal([]byte(raw), &arguments); err != nil {
+		return errs.NewValidationError(errs.SubtypeInvalidArgument, "invalid --args (must be a JSON object)").
+			WithParam("--args").
+			WithCause(err)
+	}

As per coding guidelines, cmd/**/*.go must use errs.NewValidationError(errs.SubtypeInvalidArgument, ...) with .WithParam("--flag") for user argument validation failures.

🤖 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 `@cmd/mcp/call.go` around lines 52 - 54, Replace the legacy
output.ErrValidation return in the json.Unmarshal failure path (the block that
checks if json.Unmarshal([]byte(raw), &arguments) returns an error) with a typed
validation error: call errs.NewValidationError(errs.SubtypeInvalidArgument,
"invalid --args (must be a JSON object): %v", err), chain .WithParam("--args")
and preserve the original error as the cause (e.g., .WithCause(err)), and return
that instead of output.ErrValidation.

Source: Coding guidelines

Comment thread cmd/mcp/client_test.go
Comment on lines +17 to +24
func asExit(t *testing.T, err error) *output.ExitError {
t.Helper()
var ee *output.ExitError
if !errors.As(err, &ee) {
t.Fatalf("expected *output.ExitError, got %T: %v", err, err)
}
return ee
}

@coderabbitai coderabbitai Bot Jun 13, 2026

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

Update error-path tests to assert typed metadata (errs.ProblemOf) and preserved causes.

Current assertions focus on *output.ExitError code/detail shape, which won’t catch regressions in typed category/subtype/param or cause chaining required by the test contract.

As per coding guidelines, *_test.go error-path tests must assert typed metadata using errs.ProblemOf and cause preservation; based on learnings, use errors.As(*errs.ValidationError) when asserting Param because ProblemOf does not expose it.

Also applies to: 96-103, 109-113, 119-121, 136-141

🤖 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 `@cmd/mcp/client_test.go` around lines 17 - 24, Update the error-path tests to
assert the typed problem metadata and preserved causes instead of only
inspecting *output.ExitError fields: where tests currently call asExit (the
helper shown) and then assert ExitError.Code/Detail, change them to use
errs.ProblemOf on the returned error to assert category/subtype/params, and also
use errors.As to check for a *errs.ValidationError when you need to assert Param
values; additionally assert that the original cause is preserved (use
errors.Unwrap or errors.As on the err returned by the operation to verify the
underlying cause). Apply these changes at the other affected test sites (the
regions noted at lines 96-103, 109-113, 119-121, 136-141) so each test validates
typed metadata via errs.ProblemOf and cause chaining rather than only the
ExitError shape.

Sources: Coding guidelines, Learnings

Comment thread cmd/mcp/client_test.go
Comment on lines +45 to +161
func TestJSONRPC_ToolsList(t *testing.T) {
srv := newServer(t, 200, "application/json",
`{"jsonrpc":"2.0","id":"1","result":{"tools":[{"name":"search"},{"name":"get_item"}]}}`)

got, err := jsonRPC(context.Background(), srv.Client(), srv.URL, "tools/list", map[string]any{}, nil)
if err != nil {
t.Fatalf("jsonRPC: %v", err)
}
res, ok := got.(map[string]interface{})
if !ok {
t.Fatalf("result type = %T, want map", got)
}
tools, ok := res["tools"].([]interface{})
if !ok || len(tools) != 2 {
t.Fatalf("tools = %v, want 2 entries", res["tools"])
}
}

func TestJSONRPC_ToolsCall(t *testing.T) {
srv := newServer(t, 200, "application/json",
`{"jsonrpc":"2.0","id":"1","result":{"content":[{"type":"text","text":"hi"}],"isError":false}}`)

got, err := jsonRPC(context.Background(), srv.Client(), srv.URL, "tools/call",
map[string]any{"name": "search", "arguments": map[string]any{"q": "x"}}, nil)
if err != nil {
t.Fatalf("jsonRPC: %v", err)
}
if _, ok := got.(map[string]interface{})["content"]; !ok {
t.Fatalf("result missing content: %v", got)
}
}

func TestJSONRPC_SSEFraming(t *testing.T) {
// Streamable HTTP may frame the JSON-RPC payload as a Server-Sent Event.
body := "event: message\ndata: {\"jsonrpc\":\"2.0\",\"id\":\"1\",\"result\":{\"ok\":true}}\n\n"
srv := newServer(t, 200, "text/event-stream", body)

got, err := jsonRPC(context.Background(), srv.Client(), srv.URL, "tools/list", map[string]any{}, nil)
if err != nil {
t.Fatalf("jsonRPC (SSE): %v", err)
}
if got.(map[string]interface{})["ok"] != true {
t.Fatalf("SSE result = %v, want ok:true", got)
}
}

func TestJSONRPC_ErrorPayload(t *testing.T) {
srv := newServer(t, 200, "application/json",
`{"jsonrpc":"2.0","id":"1","error":{"code":-32601,"message":"Method not found"}}`)

_, err := jsonRPC(context.Background(), srv.Client(), srv.URL, "tools/list", map[string]any{}, nil)
ee := asExit(t, err)
if ee.Code != output.ExitAPI {
t.Errorf("exit code = %d, want ExitAPI(%d)", ee.Code, output.ExitAPI)
}
if ee.Detail == nil || ee.Detail.Type != "mcp_error" {
t.Errorf("error type = %v, want mcp_error", ee.Detail)
}
}

func TestJSONRPC_HTTPError(t *testing.T) {
srv := newServer(t, http.StatusInternalServerError, "text/plain", "boom")

_, err := jsonRPC(context.Background(), srv.Client(), srv.URL, "tools/list", map[string]any{}, nil)
ee := asExit(t, err)
if ee.Code != output.ExitAPI {
t.Errorf("exit code = %d, want ExitAPI(%d)", ee.Code, output.ExitAPI)
}
}

func TestJSONRPC_NonJSON(t *testing.T) {
srv := newServer(t, 200, "application/json", "<html>not json</html>")

_, err := jsonRPC(context.Background(), srv.Client(), srv.URL, "tools/list", map[string]any{}, nil)
if asExit(t, err).Code != output.ExitAPI {
t.Errorf("want ExitAPI for non-JSON body")
}
}

func TestParseHeaders(t *testing.T) {
h, err := parseHeaders([]string{"Authorization: Bearer abc", "X-Trace: t1 "})
if err != nil {
t.Fatalf("parseHeaders: %v", err)
}
if got := h.Get("Authorization"); got != "Bearer abc" {
t.Errorf("Authorization = %q, want %q", got, "Bearer abc")
}
if got := h.Get("X-Trace"); got != "t1" {
t.Errorf("X-Trace = %q (whitespace not trimmed)", got)
}

for _, bad := range []string{"no-colon", ": empty-key"} {
if _, err := parseHeaders([]string{bad}); err == nil {
t.Errorf("parseHeaders(%q) = nil error, want validation error", bad)
} else if asExit(t, err).Code != output.ExitValidation {
t.Errorf("parseHeaders(%q) wrong exit code", bad)
}
}
}

func TestParseHeaders_SentOnWire(t *testing.T) {
var gotAuth string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotAuth = r.Header.Get("Authorization")
w.Header().Set("Content-Type", "application/json")
_, _ = io.WriteString(w, `{"jsonrpc":"2.0","id":"1","result":{}}`)
}))
t.Cleanup(srv.Close)

h, _ := parseHeaders([]string{"Authorization: Bearer xyz"})
if _, err := jsonRPC(context.Background(), srv.Client(), srv.URL, "tools/list", map[string]any{}, h); err != nil {
t.Fatalf("jsonRPC: %v", err)
}
if gotAuth != "Bearer xyz" {
t.Errorf("server saw Authorization = %q, want %q", gotAuth, "Bearer xyz")
}
}

@coderabbitai coderabbitai Bot Jun 13, 2026

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

Add tests for httpClientFor URL validation and HTTPS-only enforcement.

cmd/mcp/client.go adds security-critical URL validation/client wrapping, but this file currently exercises only jsonRPC/parseHeaders. Please add direct tests for rejecting invalid/unsafe URLs and enforcing HTTPS-only behavior.

As per coding guidelines, every behavior change should be covered by tests alongside the change.

🤖 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 `@cmd/mcp/client_test.go` around lines 45 - 161, Add unit tests in
cmd/mcp/client_test.go that directly exercise the URL validation in the
httpClientFor function: create tests that call httpClientFor with invalid/unsafe
URLs (e.g., "file://", "data:", malformed URLs) and assert it returns an
error/ExitValidation, and add a test that calls httpClientFor with an http://
URL to assert HTTPS-only enforcement (expect error) and another that toggles any
existing "allow HTTP" option/flag (or context) to verify http:// is accepted
when explicitly allowed; place these new tests alongside the existing
TestJSONRPC_* tests and use the httpClientFor identifier so the test fails if
the validation behavior changes.

Source: Coding guidelines

Comment thread cmd/mcp/client_test.go
Comment on lines +154 to +155
h, _ := parseHeaders([]string{"Authorization: Bearer xyz"})
if _, err := jsonRPC(context.Background(), srv.Client(), srv.URL, "tools/list", map[string]any{}, h); err != nil {

@coderabbitai coderabbitai Bot Jun 13, 2026

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 | 🟡 Minor | ⚡ Quick win

Do not ignore parseHeaders error in test setup.

Line 154 drops the returned error; if parsing fails, this test can produce misleading failures later in jsonRPC.

Suggested fix
- h, _ := parseHeaders([]string{"Authorization: Bearer xyz"})
+ h, err := parseHeaders([]string{"Authorization: Bearer xyz"})
+ if err != nil {
+ t.Fatalf("parseHeaders: %v", err)
+ }
- if _, err := jsonRPC(context.Background(), srv.Client(), srv.URL, "tools/list", map[string]any{}, h); err != nil {
+ if _, err := jsonRPC(context.Background(), srv.Client(), srv.URL, "tools/list", map[string]any{}, h); err != nil {
 t.Fatalf("jsonRPC: %v", err)
 }
📝 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
h, _ := parseHeaders([]string{"Authorization: Bearer xyz"})
if _, err := jsonRPC(context.Background(), srv.Client(), srv.URL, "tools/list", map[string]any{}, h); err != nil {
h, err := parseHeaders([]string{"Authorization: Bearer xyz"})
if err != nil {
t.Fatalf("parseHeaders: %v", err)
}
if _, err := jsonRPC(context.Background(), srv.Client(), srv.URL, "tools/list", map[string]any{}, h); err != nil {
t.Fatalf("jsonRPC: %v", err)
}
🤖 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 `@cmd/mcp/client_test.go` around lines 154 - 155, The test currently ignores
the error return from parseHeaders (used to build h) which can mask setup
failures and cause misleading jsonRPC errors; update the test to capture the
error (e.g., h, err := parseHeaders(...)) and immediately fail the test if err
!= nil (use t.Fatalf or t.Fatal/require for the test framework in use) before
calling jsonRPC so that parseHeaders failures are reported directly.

Comment thread cmd/mcp/client.go
for _, item := range raw {
k, v, ok := strings.Cut(item, ":")
if !ok {
return nil, output.ErrValidation("invalid --header %q (expected \"Key: Value\")", item)

@coderabbitai coderabbitai Bot Jun 13, 2026

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

Replace legacy output.Err*/output.Errorf with typed errs.* in command-facing paths.

Line 33, Line 37, Line 49, Line 53, and the downstream error sites still emit legacy output.* errors, which downgrades classification and loses typed metadata/cause propagation expected in cmd/**/*.go.

Suggested direction
- return nil, output.ErrValidation("invalid --header %q (expected \"Key: Value\")", item)
+ return nil, errs.NewValidationError(errs.SubtypeInvalidArgument, "invalid --header format").WithParam("--header")
- return nil, output.ErrValidation("invalid --url: %v", err)
+ return nil, errs.NewValidationError(errs.SubtypeInvalidArgument, "invalid MCP server URL").WithParam("--url").WithCause(err)
- return nil, output.ErrNetwork("MCP transport failed: %v", err)
+ return nil, errs.NewNetworkError(errs.SubtypeNetworkTransport, "MCP transport failed").WithCause(err)

As per coding guidelines, command-facing failures in **/*.go/cmd/**/*.go must use typed errs.*, preserve causes via .WithCause(err), and use WithParam("--flag") for user argument failures.

Also applies to: 37-37, 49-49, 53-53, 68-69, 73-74, 85-86, 91-95, 124-125, 136-142

🤖 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 `@cmd/mcp/client.go` at line 33, The command code returns legacy
output.ErrValidation for bad header parsing; replace that with a typed errs.*
error, e.g. errs.InvalidUserInput("invalid --header %q (expected \"Key:
Value\")", item).WithParam("--header") and, if this is wrapping a lower-level
error, chain the cause via .WithCause(err); update every other legacy output.*
site in cmd/mcp/client.go (the return at the header parse, and the other
occurrences flagged in the review) to use the appropriate errs.* constructor,
include WithParam("--flag") for user-argument errors and .WithCause(...) when
there is an underlying error so typed metadata and cause propagation are
preserved.

Source: Coding guidelines

Comment thread cmd/mcp/client.go
Comment on lines +101 to +105
if errObj, ok := payload["error"]; ok {
return nil, mcpErrorFrom(errObj)
}
return payload["result"], nil
}

@coderabbitai coderabbitai Bot Jun 13, 2026

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 | 🟡 Minor | ⚡ Quick win

Treat missing result+error as malformed JSON-RPC response, not success.

At Line 104, payload["result"] is returned even when the key is absent, which can surface malformed responses as ok with data: null.

Suggested fix
 if errObj, ok := payload["error"]; ok {
 return nil, mcpErrorFrom(errObj)
 }
- return payload["result"], nil
+ result, ok := payload["result"]
+ if !ok {
+ return nil, output.Errorf(output.ExitAPI, "mcp_error", "MCP response missing both result and error")
+ }
+ return result, nil
📝 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
if errObj, ok := payload["error"]; ok {
return nil, mcpErrorFrom(errObj)
}
return payload["result"], nil
}
if errObj, ok := payload["error"]; ok {
return nil, mcpErrorFrom(errObj)
}
result, ok := payload["result"]
if !ok {
return nil, errs.NewValidationError(errs.SubtypeInvalidArgument, "MCP response missing both result and error")
}
return result, nil
}
🤖 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 `@cmd/mcp/client.go` around lines 101 - 105, The code currently returns
payload["result"] even when the "result" key is missing; change the flow to
treat a response missing both "error" and "result" as malformed. Specifically,
after checking if errObj, ok := payload["error"]; ok { return nil,
mcpErrorFrom(errObj) }, add an explicit check for the presence of "result" (e.g.
if res, ok := payload["result"]; ok { return res, nil }) and otherwise return
nil and an error (e.g. fmt.Errorf or a suitable MCP error) indicating "malformed
JSON-RPC response: missing result and error". Keep references to payload,
"error", "result", and mcpErrorFrom when implementing the fix.

Comment thread cmd/mcp/client.go
Comment on lines +112 to +121
if bytes.HasPrefix(trimmed, []byte("event:")) || bytes.HasPrefix(trimmed, []byte("data:")) {
var last []byte
for _, line := range bytes.Split(trimmed, []byte("\n")) {
line = bytes.TrimSpace(line)
if rest, ok := bytes.CutPrefix(line, []byte("data:")); ok {
last = bytes.TrimSpace(rest)
}
}
trimmed = last
}

@coderabbitai coderabbitai Bot Jun 13, 2026

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

SSE parsing drops multiline data: payloads by keeping only the last line.

Line 117 overwrites last for each data: line, but SSE events can contain multiple data: lines that must be concatenated. Valid framed JSON can fail to parse.

Suggested fix
- var last []byte
+ var dataLines [][]byte
 for _, line := range bytes.Split(trimmed, []byte("\n")) {
 line = bytes.TrimSpace(line)
 if rest, ok := bytes.CutPrefix(line, []byte("data:")); ok {
- last = bytes.TrimSpace(rest)
+ dataLines = append(dataLines, bytes.TrimSpace(rest))
 }
 }
- trimmed = last
+ trimmed = bytes.Join(dataLines, []byte("\n"))
📝 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
if bytes.HasPrefix(trimmed, []byte("event:")) || bytes.HasPrefix(trimmed, []byte("data:")) {
var last[]byte
for _, line := range bytes.Split(trimmed, []byte("\n")) {
line = bytes.TrimSpace(line)
if rest, ok := bytes.CutPrefix(line, []byte("data:")); ok {
last = bytes.TrimSpace(rest)
}
}
trimmed = last
}
if bytes.HasPrefix(trimmed, []byte("event:")) || bytes.HasPrefix(trimmed, []byte("data:")) {
var dataLines [][]byte
for _, line := range bytes.Split(trimmed, []byte("\n")) {
line = bytes.TrimSpace(line)
if rest, ok := bytes.CutPrefix(line, []byte("data:")); ok {
dataLines = append(dataLines, bytes.TrimSpace(rest))
}
}
trimmed = bytes.Join(dataLines, []byte("\n"))
}
🤖 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 `@cmd/mcp/client.go` around lines 112 - 121, The SSE parsing loop in
cmd/mcp/client.go currently overwrites the variable last for each "data:" line,
dropping earlier lines; change the loop that inspects bytes.Split(trimmed,
[]byte("\n")) to accumulate each bytes.TrimSpace(rest) into a slice or
bytes.Buffer and then set trimmed to the concatenation of those lines joined
with a single '\n' (SSE semantics) instead of only the last line; keep the
existing use of bytes.CutPrefix and trimming but replace the single-variable
last with an append-and-join approach so multiline `data:` payloads are
preserved.

Comment thread cmd/mcp/list.go
Comment on lines +39 to +50
func runList(ctx context.Context, opts *listOptions) error {
headers, err := parseHeaders(opts.Headers)
if err != nil {
return err
}
client, err := httpClientFor(ctx, opts.Factory, opts.URL)
if err != nil {
return err
}
result, err := jsonRPC(ctx, client, opts.URL, "tools/list", map[string]any{}, headers)
if err != nil {
return err

@coderabbitai coderabbitai Bot Jun 13, 2026

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

Migrate MCP helper error returns to typed errs.* before passthrough.

runList correctly passes lower-layer errors through, but the current lower layer (cmd/mcp/client.go) returns legacy output.Err*/output.Errorf, so command-facing failures are not emitted as typed errors. This breaks the migrated cmd/**/*.go error contract for structured category/subtype/param metadata.

As per coding guidelines, cmd/**/*.go must use typed errs.* errors and preserve typed lower-layer errors unchanged; cmd/mcp/client.go currently emits legacy output.Err* values.

🤖 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 `@cmd/mcp/list.go` around lines 39 - 50, runList currently returns lower-layer
errors directly but must convert legacy output.Err*/output.Errorf errors into
the new typed errs.* before returning while leaving already-typed errs.*
untouched; update runList (and its error returns from parseHeaders,
httpClientFor, jsonRPC) to detect legacy output errors and wrap/translate them
into the appropriate errs.* variant (preserving message and metadata) and pass
through any errs.* errors unchanged; use a small converter helper (e.g.,
translateLegacyError(err) used inside runList) to centralize the mapping logic
so future calls (parseHeaders/httpClientFor/jsonRPC) get normalized typed
errors.

Source: Coding guidelines

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

Reviewers

@coderabbitai coderabbitai[bot] coderabbitai[bot] left review comments

+1 more reviewer

@LuuOW LuuOW LuuOW left review comments

Reviewers whose approvals may not affect merge requirements

At least 1 approving review is required to merge this pull request.

Assignees

No one assigned

Labels

size/L Large or sensitive change across domains or core paths

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

AltStyle によって変換されたページ (->オリジナル) /