Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ _Changes on `main` since the latest tagged release that have not yet been includ

- **Annotation, audit, cache, and SARIF tools are now always enabled** β€” Removed the `ENABLE_ANNOTATION_TOOLS` opt-in gate; all annotation, audit, query result cache, and SARIF analysis tools are registered by default. The `ENABLE_ANNOTATION_TOOLS` environment variable no longer controls tool availability; when set to `false`, it only disables the related auto-caching behaviour in result processing. ([#223](https://github.com/advanced-security/codeql-development-mcp-server/pull/223))
- **Go-based `ql-mcp-client` rewrite** β€” Replaced the Node.js `ql-mcp-client.js` integration test runner with a Go CLI (`gh-ql-mcp-client`) built with Cobra and `mcp-go`. Adds `list tools/prompts/resources` commands and assertion-based integration test validation. ([#223](https://github.com/advanced-security/codeql-development-mcp-server/pull/223))
- **Code Scanning lifecycle management** β€” Added `code-scanning list-analyses`, `list-alerts`, and `download-analysis` subcommands to `gh-ql-mcp-client` with GitHub REST API integration via `go-gh`. Added `sarif` parent subcommand for SARIF delegation workflows. Enhanced SARIF tools with `sarif_store` (session cache ingest), `sarif_deduplicate_rules` (cross-file rule deduplication), and `partialFingerprints` overlap mode with automatic fallback.
- **Persistent MRVA workflow state and caching** β€” Introduced a new `SqliteStore` backend plus annotation, audit, and query result cache tools to support the next phase of MCP-assisted CodeQL development and `seclab-taskflow-agent` integration. ([#169](https://github.com/advanced-security/codeql-development-mcp-server/pull/169))
Comment thread
data-douser marked this conversation as resolved.
- **Rust language support** β€” Added first-class Rust support with `PrintAST`, `PrintCFG`, `CallGraphFrom`, `CallGraphTo`, and `CallGraphFromTo` queries, bringing the total supported languages to 10. ([#195](https://github.com/advanced-security/codeql-development-mcp-server/pull/195))
- **Bug fixes and design improvements from recent evaluation sessions** β€” Fixed 5 bugs across `bqrs_interpret`, `bqrs_info`, `annotation_search`, `audit_add_notes`, and `query_results_cache_compare`; added `database_analyze` auto-caching and per-database mutex serialization; auto-enabled annotation tools in VS Code extension. ([#199](https://github.com/advanced-security/codeql-development-mcp-server/pull/199))
Expand All @@ -35,6 +36,7 @@ _Changes on `main` since the latest tagged release that have not yet been includ
| `audit_store_findings`, `audit_list_findings`, `audit_add_notes`, `audit_clear_repo` | Repo-keyed audit tools for MRVA finding management and triage workflows. ([#169](https://github.com/advanced-security/codeql-development-mcp-server/pull/169)) |
| `query_results_cache_lookup`, `query_results_cache_retrieve`, `query_results_cache_clear`, `query_results_cache_compare` | Query result cache tools for lookup, subset retrieval, cache clearing, and cross-database comparison. ([#169](https://github.com/advanced-security/codeql-development-mcp-server/pull/169)) |
| `sarif_list_rules`, `sarif_extract_rule`, `sarif_rule_to_markdown`, `sarif_compare_alerts`, `sarif_diff_runs` | SARIF analysis tools for rule discovery, per-rule extraction, Mermaid dataflow visualization, alert overlap comparison, and cross-run behavioral diffing. ([#204](https://github.com/advanced-security/codeql-development-mcp-server/pull/204)) |
| `sarif_store`, `sarif_deduplicate_rules` | SARIF session cache ingest and cross-file rule deduplication tools. `sarif_compare_alerts` enhanced with `partialFingerprints` overlap mode with automatic fallback to full-path comparison. |

#### MCP Server Resources

Expand All @@ -58,6 +60,8 @@ _Changes on `main` since the latest tagged release that have not yet been includ

- Added Rust coverage to CI and release workflows, including query unit tests and VSIX bundling. ([#195](https://github.com/advanced-security/codeql-development-mcp-server/pull/195))
- Added client integration tests for the new Rust queries and for the annotation, audit, and cache tool suites, including an MRVA triage workflow end-to-end test. ([#169](https://github.com/advanced-security/codeql-development-mcp-server/pull/169), [#195](https://github.com/advanced-security/codeql-development-mcp-server/pull/195))
- Added `code-scanning` and `sarif` subcommand groups to `gh-ql-mcp-client` with GitHub REST API client integration via `go-gh` for Code Scanning alert lifecycle management.
- Added `gh` extension packaging support with cross-compilation targets for `darwin/amd64`, `darwin/arm64`, `linux/amd64`, `linux/arm64`, `windows/amd64`.

### Changed

Expand Down
17 changes: 17 additions & 0 deletions client/cmd/code_scanning.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package cmd

import "github.com/spf13/cobra"

var codeScanningCmd = &cobra.Command{
Use: "code-scanning",
Aliases: []string{"cs"},
Short: "Manage Code Scanning analyses and alerts",
Long: "Commands for listing, downloading, dismissing, and reopening Code Scanning analyses and alerts via the GitHub REST API.",
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}

func init() {
rootCmd.AddCommand(codeScanningCmd)
}
81 changes: 81 additions & 0 deletions client/cmd/code_scanning_download_analysis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package cmd

import (
"encoding/json"
"fmt"
"os"
"path/filepath"

gh "github.com/advanced-security/codeql-development-mcp-server/client/internal/github"
"github.com/spf13/cobra"
)

var downloadAnalysisCmd = &cobra.Command{
Use: "download-analysis",
Short: "Download a Code Scanning analysis as SARIF",
RunE: runDownloadAnalysis,
}

var downloadAnalysisFlags struct {
repo string
analysisID int
output string
}

func init() {
codeScanningCmd.AddCommand(downloadAnalysisCmd)

f := downloadAnalysisCmd.Flags()
f.StringVar(&downloadAnalysisFlags.repo, "repo", "", "Repository in owner/repo format (required)")
f.IntVar(&downloadAnalysisFlags.analysisID, "analysis-id", 0, "Analysis ID to download (required)")
f.StringVar(&downloadAnalysisFlags.output, "output", "", "Output file path (default: sarif-downloads/<repo>/<id>.sarif)")

_ = downloadAnalysisCmd.MarkFlagRequired("repo")
_ = downloadAnalysisCmd.MarkFlagRequired("analysis-id")
}

func runDownloadAnalysis(cmd *cobra.Command, _ []string) error {
owner, repo, err := parseRepo(downloadAnalysisFlags.repo)
if err != nil {
return err
}

client, err := gh.NewClient()
if err != nil {
return err
}

sarif, err := client.GetAnalysisSARIF(owner, repo, downloadAnalysisFlags.analysisID)
if err != nil {
return err
}

// Determine output path
outPath := downloadAnalysisFlags.output
if outPath == "" {
outPath = filepath.Join("sarif-downloads", fmt.Sprintf("%s_%s", owner, repo),
fmt.Sprintf("%d.sarif", downloadAnalysisFlags.analysisID))
}

// Ensure directory exists
if err := os.MkdirAll(filepath.Dir(outPath), 0o750); err != nil {
return fmt.Errorf("create output directory: %w", err)
}

// Pretty-print the JSON
var pretty json.RawMessage
if err := json.Unmarshal(sarif, &pretty); err != nil {
// If not valid JSON, write as-is
if writeErr := os.WriteFile(outPath, sarif, 0o600); writeErr != nil {
return fmt.Errorf("write SARIF file: %w", writeErr)
}
} else {
formatted, _ := json.MarshalIndent(pretty, "", " ")
if err := os.WriteFile(outPath, formatted, 0o600); err != nil {
return fmt.Errorf("write SARIF file: %w", err)
}
}

fmt.Fprintf(cmd.OutOrStdout(), "Downloaded SARIF to %s (%d bytes)\n", outPath, len(sarif))
return nil
}
86 changes: 86 additions & 0 deletions client/cmd/code_scanning_list_alerts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package cmd

import (
"encoding/json"
"fmt"
"text/tabwriter"

gh "github.com/advanced-security/codeql-development-mcp-server/client/internal/github"
"github.com/spf13/cobra"
)

var listAlertsCmd = &cobra.Command{
Use: "list-alerts",
Short: "List Code Scanning alerts for a repository",
RunE: runListAlerts,
}

var listAlertsFlags struct {
repo string
ref string
state string
severity string
toolName string
sort string
direction string
perPage int
}

func init() {
codeScanningCmd.AddCommand(listAlertsCmd)

f := listAlertsCmd.Flags()
f.StringVar(&listAlertsFlags.repo, "repo", "", "Repository in owner/repo format (required)")
f.StringVar(&listAlertsFlags.ref, "ref", "", "Git ref to filter by")
f.StringVar(&listAlertsFlags.state, "state", "", "Alert state: open, closed, dismissed, fixed")
f.StringVar(&listAlertsFlags.severity, "severity", "", "Severity: critical, high, medium, low, warning, note, error")
f.StringVar(&listAlertsFlags.toolName, "tool-name", "", "Tool name to filter by")
f.StringVar(&listAlertsFlags.sort, "sort", "", "Sort by (created, updated)")
f.StringVar(&listAlertsFlags.direction, "direction", "", "Sort direction (asc, desc)")
f.IntVar(&listAlertsFlags.perPage, "per-page", 30, "Results per page (max 100)")

_ = listAlertsCmd.MarkFlagRequired("repo")
}

func runListAlerts(cmd *cobra.Command, _ []string) error {
owner, repo, err := parseRepo(listAlertsFlags.repo)
if err != nil {
return err
}

client, err := gh.NewClient()
if err != nil {
return err
}

alerts, err := client.ListAlerts(gh.ListAlertsOptions{
Owner: owner,
Repo: repo,
Ref: listAlertsFlags.ref,
State: listAlertsFlags.state,
Severity: listAlertsFlags.severity,
ToolName: listAlertsFlags.toolName,
Sort: listAlertsFlags.sort,
Direction: listAlertsFlags.direction,
PerPage: listAlertsFlags.perPage,
})
if err != nil {
return err
}

if OutputFormat() == "json" {
enc := json.NewEncoder(cmd.OutOrStdout())
enc.SetIndent("", " ")
return enc.Encode(alerts)
}

w := tabwriter.NewWriter(cmd.OutOrStdout(), 0, 4, 2, ' ', 0)
fmt.Fprintln(w, "NUM\tSTATE\tRULE\tSEVERITY\tFILE:LINE\tCREATED")
for _, a := range alerts {
loc := a.MostRecentInstance.Location
locStr := fmt.Sprintf("%s:%d", loc.Path, loc.StartLine)
fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%s\n",
a.Number, a.State, a.Rule.ID, a.Rule.Severity, locStr, a.CreatedAt)
}
return w.Flush()
}
81 changes: 81 additions & 0 deletions client/cmd/code_scanning_list_analyses.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package cmd

import (
"encoding/json"
"fmt"
"text/tabwriter"

gh "github.com/advanced-security/codeql-development-mcp-server/client/internal/github"
"github.com/spf13/cobra"
)

var listAnalysesCmd = &cobra.Command{
Use: "list-analyses",
Short: "List Code Scanning analyses for a repository",
RunE: runListAnalyses,
}

var listAnalysesFlags struct {
repo string
ref string
toolName string
sarifID string
sort string
direction string
perPage int
}

func init() {
codeScanningCmd.AddCommand(listAnalysesCmd)

f := listAnalysesCmd.Flags()
f.StringVar(&listAnalysesFlags.repo, "repo", "", "Repository in owner/repo format (required)")
f.StringVar(&listAnalysesFlags.ref, "ref", "", "Git ref to filter by")
f.StringVar(&listAnalysesFlags.toolName, "tool-name", "", "Tool name to filter by (e.g. CodeQL)")
f.StringVar(&listAnalysesFlags.sarifID, "sarif-id", "", "SARIF ID to filter by")
f.StringVar(&listAnalysesFlags.sort, "sort", "", "Sort by (created)")
f.StringVar(&listAnalysesFlags.direction, "direction", "", "Sort direction (asc, desc)")
f.IntVar(&listAnalysesFlags.perPage, "per-page", 30, "Results per page (max 100)")

_ = listAnalysesCmd.MarkFlagRequired("repo")
}

func runListAnalyses(cmd *cobra.Command, _ []string) error {
owner, repo, err := parseRepo(listAnalysesFlags.repo)
if err != nil {
return err
}

client, err := gh.NewClient()
if err != nil {
return err
}

analyses, err := client.ListAnalyses(gh.ListAnalysesOptions{
Owner: owner,
Repo: repo,
Ref: listAnalysesFlags.ref,
ToolName: listAnalysesFlags.toolName,
SarifID: listAnalysesFlags.sarifID,
Sort: listAnalysesFlags.sort,
Direction: listAnalysesFlags.direction,
PerPage: listAnalysesFlags.perPage,
})
if err != nil {
return err
}

if OutputFormat() == "json" {
enc := json.NewEncoder(cmd.OutOrStdout())
enc.SetIndent("", " ")
return enc.Encode(analyses)
}

w := tabwriter.NewWriter(cmd.OutOrStdout(), 0, 4, 2, ' ', 0)
fmt.Fprintln(w, "ID\tTOOL\tREF\tCATEGORY\tRESULTS\tRULES\tCREATED")
for _, a := range analyses {
fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%d\t%d\t%s\n",
a.ID, a.Tool.Name, a.Ref, a.Category, a.ResultsCount, a.RulesCount, a.CreatedAt)
}
return w.Flush()
}
9 changes: 4 additions & 5 deletions client/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,11 @@ var (
// rootCmd is the top-level command for the CLI.
var rootCmd = &cobra.Command{
Use: "gh-ql-mcp-client",
Short: "CodeQL Development MCP Client β€” integration test runner and CLI",
Long: `gh-ql-mcp-client is a CLI for running integration tests against a
CodeQL Development MCP Server.
Short: "CodeQL Development MCP Client β€” Code Scanning alert lifecycle management",
Long: `gh-ql-mcp-client is a CLI for managing Code Scanning alert lifecycles.

It connects to a CodeQL Development MCP Server via stdio or HTTP transport
and runs integration test fixtures from client/integration-tests/.
It connects to a CodeQL Development MCP Server to leverage SARIF analysis tools
and uses GitHub's Code Scanning REST API (via gh auth) for alert operations.

Use as a gh extension: gh ql-mcp-client <command> [flags]
Use standalone: gh-ql-mcp-client <command> [flags]`,
Expand Down
16 changes: 16 additions & 0 deletions client/cmd/sarif.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package cmd

import "github.com/spf13/cobra"

var sarifCmd = &cobra.Command{
Use: "sarif",
Short: "SARIF analysis and alert comparison tools",
Long: "Commands for comparing, deduplicating, and validating SARIF alerts using MCP server tools and LLM-driven analysis.",
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}

func init() {
rootCmd.AddCommand(sarifCmd)
}
14 changes: 14 additions & 0 deletions client/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,29 @@ module github.com/advanced-security/codeql-development-mcp-server/client
go 1.25.6

require (
github.com/cli/go-gh/v2 v2.13.0
github.com/mark3labs/mcp-go v0.47.0
github.com/spf13/cobra v1.10.2
)

require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/cli/safeexec v1.0.0 // indirect
github.com/cli/shurcooL-graphql v0.0.4 // indirect
github.com/google/jsonschema-go v0.4.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/henvic/httpretty v0.0.6 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.9 // indirect
github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/term v0.30.0 // indirect
golang.org/x/text v0.23.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading
Loading