feat: ql-mcp-client Phase 3(A) — use tool, use resource, use prompt subcommands with shared primitives library#233
Conversation
- Add client/internal/mcp/primitives.go with shared CallTool, ReadResource, GetPrompt, ListTools, ListResources, ListPrompts functions and formatters - Add client/internal/mcp/primitives_test.go with comprehensive unit tests - Add GetPrompt and ReadResource to client/internal/mcp/client.go - Add client/cmd/use.go parent subcommand - Add client/cmd/use_tool.go, use_resource.go, use_prompt.go subcommands - Add client/cmd/use_test.go with CLI tests - Refactor client/cmd/integration_tests.go to use shared primitives - Refactor client/internal/testing/runner.go to use shared ContentBlock type - Support --format json|text|markdown across all subcommands Agent-Logs-Url: https://github.com/advanced-security/codeql-development-mcp-server/sessions/78e52dbe-a4f9-4a7a-b042-c14ad89421b1 Co-authored-by: data-douser <70299490+data-douser@users.noreply.github.com>
…1.25 The client/go.mod requires go 1.25.6 but the setup action defaulted to Go 1.21, causing build failures when npm run build-and-test uses the action's default Go version. Agent-Logs-Url: https://github.com/advanced-security/codeql-development-mcp-server/sessions/78e52dbe-a4f9-4a7a-b042-c14ad89421b1 Co-authored-by: data-douser <70299490+data-douser@users.noreply.github.com>
Agent-Logs-Url: https://github.com/advanced-security/codeql-development-mcp-server/sessions/78e52dbe-a4f9-4a7a-b042-c14ad89421b1 Co-authored-by: data-douser <70299490+data-douser@users.noreply.github.com>
ql-mcp-client Phase 3(A) — use tool, use resource, use prompt subcommands with shared primitives library
data-douser
left a comment
There was a problem hiding this comment.
End-to-End Validation Review — ql-mcp-client Phase 3(A) (#222)
Built the Go binary, built the MCP server, and ran all subcommands against a live MCP server via stdio transport.
Unit Tests
$ go test ./...
ok client/cmd 0.477s
ok client/internal/mcp 3.837s
ok client/internal/testing 0.719s
E2E Results (live MCP server via stdio)
| Test | Command | Result |
|---|---|---|
use tool (no args) |
use tool codeql_resolve_languages --format text |
✅ 15 languages returned |
use tool (JSON) |
use tool codeql_resolve_languages --format json |
✅ Valid JSON with content array |
use tool (markdown) |
use tool codeql_resolve_languages --format markdown |
✅ Output rendered |
use tool (with --arg) |
use tool sarif_list_rules --arg sarifPath=query-results.sarif |
✅ 1 rule, 2 results returned |
use tool (wrong arg name) |
use tool codeql_pack_ls --arg workspacePath=. |
✅ [ERROR] + validation message |
use resource (text) |
use resource codeql://server/tools --format text |
✅ Full tool reference with URI/MIME metadata |
use resource (JSON) |
use resource codeql://server/overview --format json |
✅ Valid JSON with contents array |
use resource (markdown) |
use resource codeql://server/prompts --format markdown |
✅ 58 lines of markdown |
use prompt (with args, text) |
use prompt explain_codeql_query --arg queryPath=... --arg language=javascript --format text |
✅ 169 lines, role/content rendered |
use prompt (JSON) |
Same prompt --format json |
✅ Valid JSON, messages array with 1 entry |
use prompt (markdown) |
Same prompt --format markdown |
✅ 169 lines with ### user headers |
| Missing required args | use tool, use resource, use prompt (no positional arg) |
✅ All exit 1 with "accepts 1 arg(s)" |
| Nonexistent resource | use resource codeql://nonexistent |
✅ Exit 1, clear error message |
| Nonexistent prompt | use prompt nonexistent_prompt |
✅ Exit 1, clear error message |
Bug Found: use tool exit code on MCP-level errors
$ ./gh-ql-mcp-client use tool nonexistent_tool --format text; echo "exit: $?"
[ERROR]
MCP error -32602: Tool nonexistent_tool not found
exit: 0 # ← should be non-zeroWhen a tool call returns IsError: true in the ToolResult, outputToolResult() in use_tool.go still returns nil, so the CLI exits 0. Compare with use resource and use prompt which both correctly exit 1 on server errors.
Suggested fix in outputToolResult():
func outputToolResult(result *mcpclient.ToolResult) error {
// ... format and print ...
if result.IsError {
return fmt.Errorf("tool returned error")
}
return nil
}Minor Nit: cutString duplicates strings.Cut
cutString() in client/cmd/use.go reimplements strings.Cut (available since Go 1.18; module targets Go 1.25). Could simplify by replacing with strings.Cut(a, "=") and deleting the 7-line function.
Acceptance Criteria Cross-Reference (#222)
| # | Criterion | Status |
|---|---|---|
| 1 | use tool <name> --arg k=v calls tool, prints response |
✅ Verified E2E |
| 2 | use resource <uri> reads resource, prints content |
✅ Verified E2E |
| 3 | use prompt <name> --arg k=v gets prompt, prints messages |
✅ Verified E2E |
| 4 | All three support --format json|text|markdown |
✅ All 9 combinations verified |
| 5 | Test runner uses primitives.go for all MCP calls |
✅ Code review confirmed |
| 6 | No duplicated MCP invocation logic | ✅ Single source of truth in primitives.go |
| 7 | go test ./... passes |
✅ All 3 packages pass |
| 8 | Existing integration test fixtures pass | |
| 9 | npm run build-and-test |
Summary
Implementation is solid. All CLI subcommands work correctly against a live MCP server. The shared primitives library architecture is clean. One bug found (exit code 0 on tool errors) and one minor nit (cutString → strings.Cut). Requesting changes for the exit code issue since it affects scripting/automation use cases.
…tring with strings.Cut - outputToolResult() now returns an error when result.IsError is true, so the CLI exits 1 on MCP-level tool errors (e.g. nonexistent tool) - Replace custom cutString() with stdlib strings.Cut (available since Go 1.18) Agent-Logs-Url: https://github.com/advanced-security/codeql-development-mcp-server/sessions/cc25a44f-aecb-453c-be59-8064533607d3 Co-authored-by: data-douser <70299490+data-douser@users.noreply.github.com>
Fixed both issues in fc24b2c:
|
data-douser
left a comment
There was a problem hiding this comment.
Re-validation after fixes (fc24b2c)
Both issues from the previous review have been resolved. Rebuilt the binary, re-ran unit tests and E2E tests against a live MCP server.
Fix 1: Exit code on IsError tool results ✅
# Before fix: exit 0
# After fix:
$ ./gh-ql-mcp-client use tool nonexistent_tool --format text; echo "exit: $?"
[ERROR]
MCP error -32602: Tool nonexistent_tool not found
tool returned error
exit: 1 ✅
$ ./gh-ql-mcp-client use tool nonexistent_tool --format json 2>/dev/null
{"content":[...],"isError":true} # JSON on stdout
# "tool returned error" on stderr — clean separation ✅Fix 2: cutString → strings.Cut ✅
Custom cutString function removed, both parseArgs and parseArgsAny now use strings.Cut.
Re-validation results
| Test | Result |
|---|---|
go test ./... |
✅ All 3 packages pass |
use tool codeql_resolve_languages (text) |
✅ 15 languages, exit 0 |
use tool sarif_list_rules --arg sarifPath=... |
✅ Results returned, exit 0 |
use tool nonexistent_tool |
✅ [ERROR], exit 1 |
use tool nonexistent --format json |
✅ JSON on stdout, error on stderr, exit 1 |
use resource codeql://server/overview |
✅ 100 lines, exit 0 |
use prompt explain_codeql_query --arg ... |
✅ 168 lines, exit 0 |
All previously identified issues are fixed. Happy paths unaffected.
After Phase 2, the Go binary can run integration tests and list primitives, but cannot call an individual prompt, resource, or tool from the CLI. Additionally, the integration test runner had its own inline MCP invocation logic that should be shared.
Shared primitives library (
client/internal/mcp/primitives.go)Single source of truth for calling MCP primitives, used by both CLI subcommands and the integration test runner:
CallTool(ctx, client, name, args) → *ToolResultReadResource(ctx, client, uri) → *ResourceContentGetPrompt(ctx, client, name, args) → *PromptMessagesListTools,ListResources,ListPrompts— typed list wrappersFormat{JSON,*Text,*Markdown}— output formatters for all result typesNew
usesubcommandsAll three support
--format json|text|markdown.Client wrapper additions (
client/internal/mcp/client.go)GetPrompt(ctx, name, args)andReadResource(ctx, uri)to bothinnerClientinterface andClientwrapperclient_test.goaccordinglyIntegration test runner refactoring
client/cmd/integration_tests.go—mcpToolCaller.CallToolRawandListToolNamesnow delegate to the shared primitives library instead of inline MCP SDK callsclient/internal/testing/runner.go— usesmcpprim.ContentBlockfrom primitives (eliminated duplicated type definition)Infrastructure
.github/actions/setup-codeql-environment/action.ymldefaultgo-versionfrom1.21→1.25to matchclient/go.mod(go 1.25.6)Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
update.code.visualstudio.com/opt/hostedtoolcache/node/24.13.0/x64/bin/node node scripts/download-vscode.js(dns block)If you need me to access, download, or install something from one of these locations, you can either: