# CLAUDE.md - AI Assistant Guide for Apache SkyWalking MCP

This file provides guidance for AI assistants working with the Apache SkyWalking MCP codebase.

## Project Overview

Apache SkyWalking MCP — an MCP (Model Context Protocol) server that bridges AI agents with Apache SkyWalking OAP via GraphQL. It exposes SkyWalking's observability data (traces, logs, metrics, topology, alarms, events) as MCP tools, prompts, and resources. Binary name: `swmcp`.

## Repository Structure

```
skywalking-mcp/
├── cmd/skywalking-mcp/       # Entry point (cobra/viper CLI, three subcommands)
├── internal/
│   ├── config/               # Config structs for each transport mode
│   ├── swmcp/                # MCP server factory + transport adapters (stdio/sse/streamable)
│   ├── tools/                # MCP tool implementations (16 tools, grouped by domain)
│   ├── prompts/              # MCP prompt definitions (10 prompts, three groups)
│   └── resources/            # MCP resources (embedded MQE docs + dynamic metrics)
└── dist/                     # Distribution license files
```

## Build & Development Commands

```bash
make build            # Build binary to bin/swmcp
make lint             # Run golangci-lint (22 linters)
make fix-lint         # Auto-fix lint issues
make license-header   # Check Apache 2.0 license headers
make fix-license      # Fix license headers and dependency licenses
make build-image      # Build Docker image skywalking-mcp:latest
make clean            # Remove build artifacts
```

Unit tests exist for selected transport/context behavior. CI runs license checks, lint, and docker build.

## Architecture

### Transport & Context Flow

Three MCP transport modes as cobra subcommands: `stdio`, `sse`, `streamable`.

The SkyWalking OAP URL is resolved in priority order:
- **All transports**: `--sw-url` flag > `http://localhost:12800/graphql`

SSE and HTTP transports always use the configured server URL.

Basic auth is configured via `--sw-username` / `--sw-password` flags. The startup flags support `${ENV_VAR}` syntax to resolve credentials from environment variables (e.g. `--sw-password ${MY_SECRET}`). If a referenced env var is not set, a warning is logged and the credential is treated as empty.

TLS verification is enforced by default. Use `--sw-insecure` to skip verification (development/self-signed certs only).

Each transport injects the OAP URL, insecure flag, and auth into the request context via `WithSkyWalkingURLAndInsecure()` and `WithSkyWalkingAuth()`. Tools extract them downstream using `skywalking-cli`'s `contextkey.BaseURL{}`, `contextkey.Insecure{}`, `contextkey.Username{}`, and `contextkey.Password{}`.

### CORS / CSRF (`internal/swmcp/cors.go`)

`sse` and `streamable` transports support `--allowed-origins` (comma-separated). When set, requests with an `Origin` header not in the list are rejected with `403 Forbidden`. CORS response headers are set for allowed origins. When the flag is empty (default), all origins are permitted. The middleware is injected via `WithHTTPServer` / `WithStreamableHTTPServer` so the MCP handler is wrapped rather than forked.

### Server Wiring (`internal/swmcp/server.go`)

`newMCPServer()` is the central registration point — it creates the MCP server and calls all `Add*Tools()`, `Add*Resources()`, and `Add*Prompts()` functions. New capabilities must be registered here.

### Generic Tool Framework (`internal/tools/tools.go`)

`Tool[T, R]` is a typed generic wrapper over MCP's untyped interface. `ConvertTool()` bridges typed handlers into MCP by auto-binding JSON arguments to `T` and marshaling `R` back to JSON. If `R` is already `*mcp.CallToolResult`, it passes through directly. All tools are marked idempotent by default.

### Communication with SkyWalking OAP

- **Most tools** use `skywalking-cli` packages (`pkg/graphql/...`) which communicate via GraphQL
- **MQE tools** use direct HTTP calls to the OAP `/graphql` endpoint via `executeGraphQLWithContext()` in `mqe.go`. The HTTP client reads `contextkey.Insecure{}` to configure TLS and validates the URL scheme (`http`/`https` only) before each request.
- **Time handling**: `common.go` provides `BuildDurationWithContext()` and `GetTimeContext()` which fetch the OAP server's time/timezone for accurate duration calculations

### Input Validation (`internal/tools/mqe.go`)

All MQE tool inputs are validated before use:
- `validateMQETextField`: UTF-8, max length, no control characters — applied to all entity fields
- `validateLayerField`: additionally enforces `^[A-Z0-9_]+$` for `layer` / `dest_layer`
- `validateMQEExpression`: UTF-8, max 2048 chars, no control characters, max nesting depth 12
- `validateMetricName`: `^[A-Za-z0-9_.:-]+$` pattern, max 128 chars
- `validateRegexComplexity`: parses the regex AST via `regexp/syntax` and rejects patterns with >50 nodes
- `validateURLScheme` (`common.go`): rejects non-http/https OAP URLs before HTTP requests

## Extending the Server

### Adding a New Tool
1. Create or edit a file in `internal/tools/` (group by domain, e.g. `event.go`)
2. Define request struct with `json` tags, write handler using `NewTool()`, create `Add*Tools()` function
3. Register in `newMCPServer()` in `server.go`
4. Follow existing tools (e.g. `event.go`) as reference for the pattern

### Adding a New Prompt
1. Add handler in `internal/prompts/` (analysis, trace, or utility group)
2. Register via `s.AddPrompt()` in the corresponding group function in `registry.go`

### Adding a New Resource
1. For static content: embed files with `//go:embed` in `internal/resources/`
2. For dynamic content: call internal tool functions in the resource handler
3. Register via `s.AddResource()` in `AddMQEResources()` or a new registration function

## Code Conventions

### License Header
All `.go` files must have the Apache 2.0 license header (17-line block). Run `make fix-license` to auto-fix.

### Lint Rules (`.golangci.yml`)
- Max function length: 100 lines / 50 statements
- Cyclomatic complexity: 15
- Line length: 150 chars
- Imports: `goimports` with local prefix `github.com/apache/skywalking-mcp`
- Import order: stdlib, third-party, blank line, local packages
- 22 linters enabled including gosec, errcheck, dupl, gocritic

### Error Handling in Tools
Tool handlers should return `(mcp.NewToolResultError(...), nil)` for expected query failures (bad input, OAP errors), not `(nil, err)`. Reserve Go errors for truly unexpected failures. Use the `ErrMarshalFailed` constant for JSON marshal errors.

## CI & Merge Policy

Squash-merge only. PRs to `main` require 1 approval and passing `Required` status check (license + lint + docker build). Go 1.25.
