Skip to content

Commit b777fc3

Browse files
authored
feat: ql-mcp-client Phase 3(A) — use tool, use resource, use prompt subcommands with shared primitives library (#233)
1 parent 3ee09df commit b777fc3

File tree

18 files changed

+1166
-42
lines changed

18 files changed

+1166
-42
lines changed

.github/actions/setup-codeql-environment/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ inputs:
2929
go-version:
3030
description: 'Go version to install'
3131
required: false
32-
default: '1.21'
32+
default: '1.25'
3333
dotnet-version:
3434
description: '.NET version to install'
3535
required: false

client/cmd/integration_tests.go

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010

1111
mcpclient "github.com/advanced-security/codeql-development-mcp-server/client/internal/mcp"
1212
itesting "github.com/advanced-security/codeql-development-mcp-server/client/internal/testing"
13-
"github.com/mark3labs/mcp-go/mcp"
1413
"github.com/spf13/cobra"
1514
)
1615

@@ -47,38 +46,29 @@ type mcpToolCaller struct {
4746
timeout time.Duration
4847
}
4948

50-
func (c *mcpToolCaller) CallToolRaw(name string, params map[string]any) ([]itesting.ContentBlock, bool, error) {
49+
func (c *mcpToolCaller) CallToolRaw(name string, params map[string]any) ([]mcpclient.ContentBlock, bool, error) {
5150
ctx := context.Background()
5251
if c.timeout > 0 {
5352
var cancel context.CancelFunc
5453
ctx, cancel = context.WithTimeout(ctx, c.timeout)
5554
defer cancel()
5655
}
57-
result, err := c.client.CallTool(ctx, name, params)
56+
57+
result, err := mcpclient.CallTool(ctx, c.client, name, params)
5858
if err != nil {
5959
return nil, false, err
6060
}
6161

62-
var blocks []itesting.ContentBlock
63-
for _, item := range result.Content {
64-
if textContent, ok := item.(mcp.TextContent); ok {
65-
blocks = append(blocks, itesting.ContentBlock{
66-
Type: "text",
67-
Text: textContent.Text,
68-
})
69-
}
70-
}
71-
72-
return blocks, result.IsError, nil
62+
return result.Content, result.IsError, nil
7363
}
7464

7565
func (c *mcpToolCaller) ListToolNames() ([]string, error) {
76-
tools, err := c.client.ListTools(context.Background())
66+
infos, err := mcpclient.ListTools(context.Background(), c.client)
7767
if err != nil {
7868
return nil, err
7969
}
80-
names := make([]string, len(tools))
81-
for i, t := range tools {
70+
names := make([]string, len(infos))
71+
for i, t := range infos {
8272
names[i] = t.Name
8373
}
8474
return names, nil

client/cmd/root.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func init() {
5151
rootCmd.PersistentFlags().StringVar(&mcpMode, "mode", "stdio", "MCP server transport mode (stdio or http)")
5252
rootCmd.PersistentFlags().StringVar(&mcpHost, "host", "localhost", "MCP server host (http mode)")
5353
rootCmd.PersistentFlags().IntVar(&mcpPort, "port", 3000, "MCP server port (http mode)")
54-
rootCmd.PersistentFlags().StringVar(&outputFmt, "format", "text", "Output format (text or json)")
54+
rootCmd.PersistentFlags().StringVar(&outputFmt, "format", "text", "Output format (text, json, or markdown)")
5555
}
5656

5757
// MCPMode returns the configured MCP transport mode.

client/cmd/use.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/spf13/cobra"
8+
)
9+
10+
var useCmd = &cobra.Command{
11+
Use: "use",
12+
Short: "Call an individual MCP server primitive (tool, resource, or prompt)",
13+
Long: `Connect to the MCP server and call a single primitive.
14+
15+
Subcommands:
16+
tool Call a tool by name with key-value arguments
17+
resource Read a resource by URI
18+
prompt Get a prompt by name with key-value arguments`,
19+
}
20+
21+
// parseArgs converts a list of "key=value" strings into a map.
22+
func parseArgs(args []string) (map[string]string, error) {
23+
result := make(map[string]string, len(args))
24+
for _, a := range args {
25+
key, value, found := strings.Cut(a, "=")
26+
if !found || key == "" {
27+
return nil, fmt.Errorf("invalid argument %q: expected key=value format", a)
28+
}
29+
result[key] = value
30+
}
31+
return result, nil
32+
}
33+
34+
// parseArgsAny converts a list of "key=value" strings into a map[string]any.
35+
func parseArgsAny(args []string) (map[string]any, error) {
36+
result := make(map[string]any, len(args))
37+
for _, a := range args {
38+
key, value, found := strings.Cut(a, "=")
39+
if !found || key == "" {
40+
return nil, fmt.Errorf("invalid argument %q: expected key=value format", a)
41+
}
42+
result[key] = value
43+
}
44+
return result, nil
45+
}
46+
47+
func init() {
48+
rootCmd.AddCommand(useCmd)
49+
}

client/cmd/use_prompt.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
8+
mcpclient "github.com/advanced-security/codeql-development-mcp-server/client/internal/mcp"
9+
"github.com/spf13/cobra"
10+
)
11+
12+
var usePromptArgs []string
13+
14+
var usePromptCmd = &cobra.Command{
15+
Use: "prompt <name>",
16+
Short: "Get an MCP server prompt by name",
17+
Long: `Get a specific MCP server prompt with key-value arguments and print the resulting messages.
18+
19+
Example:
20+
gh-ql-mcp-client use prompt explain_codeql_query --arg queryPath=/path/to/query.ql --arg language=javascript
21+
gh-ql-mcp-client use prompt explain_codeql_query --arg queryPath=/path/to/query.ql --format json`,
22+
Args: cobra.ExactArgs(1),
23+
RunE: runUsePrompt,
24+
}
25+
26+
func init() {
27+
useCmd.AddCommand(usePromptCmd)
28+
usePromptCmd.Flags().StringArrayVar(&usePromptArgs, "arg", nil, "Prompt argument in key=value format (repeatable)")
29+
}
30+
31+
func runUsePrompt(_ *cobra.Command, args []string) error {
32+
promptName := args[0]
33+
34+
params, err := parseArgs(usePromptArgs)
35+
if err != nil {
36+
return fmt.Errorf("parse prompt arguments: %w", err)
37+
}
38+
39+
ctx := context.Background()
40+
client, err := connectMCPClient(ctx)
41+
if err != nil {
42+
return err
43+
}
44+
defer client.Close()
45+
46+
result, err := mcpclient.GetPrompt(ctx, client, promptName, params)
47+
if err != nil {
48+
return err
49+
}
50+
51+
return outputPromptMessages(result)
52+
}
53+
54+
func outputPromptMessages(result *mcpclient.PromptMessages) error {
55+
switch OutputFormat() {
56+
case "json":
57+
s, err := mcpclient.FormatJSON(result)
58+
if err != nil {
59+
return err
60+
}
61+
fmt.Fprintln(os.Stdout, s)
62+
case "markdown":
63+
fmt.Fprint(os.Stdout, mcpclient.FormatPromptMessagesMarkdown(result))
64+
default:
65+
fmt.Fprint(os.Stdout, mcpclient.FormatPromptMessagesText(result))
66+
}
67+
return nil
68+
}

client/cmd/use_resource.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
8+
mcpclient "github.com/advanced-security/codeql-development-mcp-server/client/internal/mcp"
9+
"github.com/spf13/cobra"
10+
)
11+
12+
var useResourceCmd = &cobra.Command{
13+
Use: "resource <uri>",
14+
Short: "Read an MCP server resource by URI",
15+
Long: `Read a specific MCP server resource and print its content.
16+
17+
Example:
18+
gh-ql-mcp-client use resource codeql://server/tools
19+
gh-ql-mcp-client use resource codeql://server/overview --format markdown`,
20+
Args: cobra.ExactArgs(1),
21+
RunE: runUseResource,
22+
}
23+
24+
func init() {
25+
useCmd.AddCommand(useResourceCmd)
26+
}
27+
28+
func runUseResource(_ *cobra.Command, args []string) error {
29+
uri := args[0]
30+
31+
ctx := context.Background()
32+
client, err := connectMCPClient(ctx)
33+
if err != nil {
34+
return err
35+
}
36+
defer client.Close()
37+
38+
result, err := mcpclient.ReadResource(ctx, client, uri)
39+
if err != nil {
40+
return err
41+
}
42+
43+
return outputResourceContent(result)
44+
}
45+
46+
func outputResourceContent(result *mcpclient.ResourceContent) error {
47+
switch OutputFormat() {
48+
case "json":
49+
s, err := mcpclient.FormatJSON(result)
50+
if err != nil {
51+
return err
52+
}
53+
fmt.Fprintln(os.Stdout, s)
54+
case "markdown":
55+
fmt.Fprint(os.Stdout, mcpclient.FormatResourceContentMarkdown(result))
56+
default:
57+
fmt.Fprint(os.Stdout, mcpclient.FormatResourceContentText(result))
58+
}
59+
return nil
60+
}

0 commit comments

Comments
 (0)