Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
11 changes: 11 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ builds:
env:
- CGO_ENABLED=0

- # MCP server
id: mcpserver
main: ./pkg/cmd/mcpserver
binary: plugin-validator-mcp
goos:
- linux
- windows
- darwin
env:
- CGO_ENABLED=0

archives:
- formats: [ tar.gz ]
format_overrides:
Expand Down
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,20 @@ Then you can run the utility:
plugincheck2 -sourceCodeUri [source_code_location/] [plugin_archive.zip]
```

### MCP Server (for AI assistants)

The plugin validator can also be used as an MCP (Model Context Protocol) server, which allows AI assistants and code editors like Claude, VS Code with Continue, and Cline to validate Grafana plugins directly.

To build and use the MCP server:

```SHELL
git clone git@github.com:grafana/plugin-validator.git
cd plugin-validator
go build -o ~/.local/bin/plugin-validator-mcp ./pkg/cmd/mcpserver
```

For detailed configuration instructions for different AI tools and editors, see the [MCP Server README](pkg/cmd/mcpserver/README.md).

### Generating local files For validation

You must create a `.zip` archive containing the `dist/` directory but named as your plugin ID:
Expand Down Expand Up @@ -186,7 +200,6 @@ analyzers:
- my-plugin-id
```


### Source code

You can specify the location of the plugin source code to the validator with the `-sourceCodeUri` option. Doing so allows for additional [analyzers](#analyzers) to be run and for a more complete scan.
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/hashicorp/go-version v1.8.0
github.com/jarcoal/httpmock v1.4.1
github.com/magefile/mage v1.15.0
github.com/modelcontextprotocol/go-sdk v1.3.0
github.com/ossf/osv-schema/bindings/go v0.0.0-20251230224438-88c48750ddae
github.com/r3labs/diff/v3 v3.0.2
github.com/smartystreets/goconvey v1.8.1
Expand Down Expand Up @@ -122,6 +123,7 @@ require (
github.com/google/generative-ai-go v0.15.1 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-containerregistry v0.20.6 // indirect
github.com/google/jsonschema-go v0.4.2 // indirect
github.com/google/osv-scalibr v0.4.1-0.20251202121049-5e7e15f4a036 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
Expand Down Expand Up @@ -193,6 +195,7 @@ require (
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.etcd.io/bbolt v1.4.2 // indirect
go.opencensus.io v0.24.0 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/gohugoio/hashstructure v0.5.0 h1:G2fjSBU36RdwEJBWJ+919ERvOVqAg9tfcYp47K9swqg=
github.com/gohugoio/hashstructure v0.5.0/go.mod h1:Ser0TniXuu/eauYmrwM4o64EBvySxNzITEOLlm4igec=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
Expand Down Expand Up @@ -260,6 +262,8 @@ github.com/google/go-containerregistry v0.20.6 h1:cvWX87UxxLgaH76b4hIvya6Dzz9qHB
github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2FAkwCDf9/HZgsFJ02E2Y=
github.com/google/go-cpy v0.0.0-20211218193943-a9c933c06932 h1:5/4TSDzpDnHQ8rKEEQBjRlYx77mHOvXu08oGchxej7o=
github.com/google/go-cpy v0.0.0-20211218193943-a9c933c06932/go.mod h1:cC6EdPbj/17GFCPDK39NRarlMI+kt+O60S12cNB5J9Y=
github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8=
github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
github.com/google/osv-scalibr v0.4.1-0.20251202121049-5e7e15f4a036 h1:a+w+8ZQYYybXPWI1yJD+mXri5fMLcThlP41rIB7XNns=
github.com/google/osv-scalibr v0.4.1-0.20251202121049-5e7e15f4a036/go.mod h1:9Ze2W6nQmu1WX2s95ezOAVZhPDbcA6ZGuEHgFT/sQEU=
github.com/google/osv-scanner/v2 v2.3.1 h1:97NVCr8QNdS9deD8zxB0cIPI7vmcqAm8YJhclnXETu8=
Expand Down Expand Up @@ -352,6 +356,8 @@ github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/modelcontextprotocol/go-sdk v1.3.0 h1:gMfZkv3DzQF5q/DcQePo5rahEY+sguyPfXDfNBcT0Zs=
github.com/modelcontextprotocol/go-sdk v1.3.0/go.mod h1:AnQ//Qc6+4nIyyrB4cxBU7UW9VibK4iOZBeyP/rF1IE=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/montanaflynn/stats v0.6.3/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
Expand Down Expand Up @@ -508,6 +514,8 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
Expand Down
22 changes: 11 additions & 11 deletions pkg/analysis/analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ var (
)

type Pass struct {
AnalyzerName string
RootDir string
CheckParams CheckParams
ResultOf map[*Analyzer]any
Report func(string, Diagnostic)
Diagnostics *Diagnostics
AnalyzerName string
RootDir string
CheckParams CheckParams
ResultOf map[*Analyzer]any
Report func(string, Diagnostic)
Diagnostics *Diagnostics
}

type CheckParams struct {
Expand Down Expand Up @@ -85,11 +85,11 @@ func (p *Pass) AnalyzerHasErrors(a *Analyzer) bool {
}

type Diagnostic struct {
Severity Severity
Title string
Detail string
Context string `json:"Context,omitempty"`
Name string
Severity Severity `json:"Severity" jsonschema:"description=The severity level of the issue."`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for response for the LLM call.

Title string `json:"Title" jsonschema:"description=A short, human-readable summary of the issue."`
Detail string `json:"Detail" jsonschema:"description=A detailed description of the issue."`
Context string `json:"Context,omitempty" jsonschema:"description=Additional context about the issue."`
Name string `json:"Name" jsonschema:"description=The name of the analysis that was run."`
}

type Diagnostics map[string][]Diagnostic
Expand Down
240 changes: 240 additions & 0 deletions pkg/cmd/mcpserver/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
# Plugin Validator MCP Server

An MCP (Model Context Protocol) server that provides Grafana plugin validation capabilities to AI assistants and code editors.

## Building

```bash
# From the project root
go build -o bin/mcpserver ./pkg/cmd/mcpserver

# Or using mage
mage build:commands
```

## Installation

### Quick Install from Release (Recommended)

**Linux/macOS:**

Run the installation script:

```bash
curl -fsSL https://raw.githubusercontent.com/grafana/plugin-validator/main/scripts/install-mcp.sh | bash
```

Or download and inspect the script first:

```bash
wget https://raw.githubusercontent.com/grafana/plugin-validator/main/scripts/install-mcp.sh
chmod +x install-mcp.sh
./install-mcp.sh
```

**Windows (PowerShell):**

```powershell
# Download latest release
$version = (Invoke-RestMethod "https://api.github.com/repos/grafana/plugin-validator/releases/latest").tag_name
$arch = if ([Environment]::Is64BitOperatingSystem) { "amd64" } else { "386" }
$url = "https://github.com/grafana/plugin-validator/releases/download/$version/plugin-validator_$($version.TrimStart('v'))_windows_$arch.zip"

# Download and extract
Invoke-WebRequest -Uri $url -OutFile "$env:TEMP\plugin-validator.zip"
Expand-Archive -Path "$env:TEMP\plugin-validator.zip" -DestinationPath "$env:TEMP\plugin-validator" -Force

# Move to user bin directory
$binDir = "$env:USERPROFILE\.local\bin"
New-Item -ItemType Directory -Force -Path $binDir | Out-Null
Move-Item -Path "$env:TEMP\plugin-validator\plugin-validator-mcp.exe" -Destination "$binDir\" -Force

# Add to PATH if not already present
if ($env:PATH -notlike "*$binDir*") {
[Environment]::SetEnvironmentVariable("PATH", "$env:PATH;$binDir", "User")
}
```

### Install via Go

If you have Go installed and prefer building from source:

```bash
# Clone and build
git clone https://github.com/grafana/plugin-validator.git
cd plugin-validator
go build -o ~/.local/bin/plugin-validator-mcp ./pkg/cmd/mcpserver

# Make sure ~/.local/bin is in your PATH
export PATH="$HOME/.local/bin:$PATH"
```

## Configuration

### Claude Code (CLI & VS Code Extension)

**Option 1: Global Configuration**

Add to `~/.claude.json` (shared between CLI and VS Code extension):

```json
{
"mcpServers": {
"plugin-validator": {
"command": "/home/YOUR_USERNAME/.local/bin/plugin-validator-mcp",
"args": []
}
}
}
```

On macOS, use:
```json
{
"mcpServers": {
"plugin-validator": {
"command": "/Users/YOUR_USERNAME/.local/bin/plugin-validator-mcp",
"args": []
}
}
}
```

**Option 2: Project-Scoped**

Create `.mcp.json` in your project root:

```json
{
"plugin-validator": {
"command": "/home/YOUR_USERNAME/.local/bin/plugin-validator-mcp",
"args": []
}
}
```

For more details on MCP server types and configuration, see [Claude Code Plugin Documentation](https://docs.anthropic.com/en/docs/claude-code).

### Claude Desktop (macOS)

Edit `~/Library/Application Support/Claude/claude_desktop_config.json`:

```json
{
"mcpServers": {
"plugin-validator": {
"command": "/Users/YOUR_USERNAME/.local/bin/plugin-validator-mcp"
}
}
}
```

### Claude Desktop (Linux)

Edit `~/.config/Claude/claude_desktop_config.json`:

```json
{
"mcpServers": {
"plugin-validator": {
"command": "/home/YOUR_USERNAME/.local/bin/plugin-validator-mcp"
}
}
}
```

### VS Code with Continue Extension

Edit `~/.continue/config.json` (Linux/macOS):

```json
{
"mcpServers": [
{
"name": "plugin-validator",
"command": "~/.local/bin/plugin-validator-mcp"
}
]
}
```

### Cline (VS Code Extension)

Edit `~/.cline/mcp_settings.json` (Linux/macOS):

```json
{
"mcpServers": {
"plugin-validator": {
"command": "/home/YOUR_USERNAME/.local/bin/plugin-validator-mcp",
"args": []
}
}
}


## Usage

Once configured, you can ask your AI assistant to validate Grafana plugins:

```
Validate this Grafana plugin: /path/to/plugin.zip
```

```
Check this plugin with source code:
- Plugin: ./my-plugin.zip
- Source: https://github.com/user/my-plugin
```

## Tool Details

### validate_plugin

Validates a Grafana plugin against publishing requirements.

**Inputs:**

- `pluginPath` (required): Path or URL to the plugin archive (.zip)
- `sourceCodeUri` (optional): Path or URL to plugin source code (zip, folder, or git repo)

**Output:**

- `diagnostics`: Structured validation results with errors, warnings, and recommendations

## Troubleshooting

### Server not found

Make sure the binary path is correct:

```bash
which plugin-validator-mcp
# or
ls -la ~/.local/bin/plugin-validator-mcp
```

### Permission denied

Make the binary executable:

```bash
chmod +x ~/.local/bin/plugin-validator-mcp
```

### Test manually

Run the server directly to check for errors:

```bash
~/.local/bin/plugin-validator-mcp
# Press Ctrl+C to exit
```

## Development

Run tests:

```bash
go test ./pkg/cmd/mcpserver -v
```
Loading
Loading