Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .github/workflows/CI.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ jobs:
- name: Lint
run: make lint

- name: Test
run: make test

- name: Build Docker images
run: make docker

Expand Down
11 changes: 5 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ make build-image # Build Docker image skywalking-mcp:latest
make clean # Remove build artifacts
```

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

## Architecture

Expand All @@ -41,12 +41,11 @@ No unit tests exist yet. CI runs license checks, lint, and docker build.
Three MCP transport modes as cobra subcommands: `stdio`, `sse`, `streamable`.

The SkyWalking OAP URL is resolved in priority order:
- **stdio**: `set_skywalking_url` session tool > `--sw-url` flag > `http://localhost:12800/graphql`
- **SSE/HTTP**: `SW-URL` HTTP header > `--sw-url` flag > `http://localhost:12800/graphql`
- **All transports**: `--sw-url` flag > `http://localhost:12800/graphql`

The `set_skywalking_url` tool is only available in stdio mode (single client, well-defined session). SSE and HTTP transports use per-request headers instead.
SSE and HTTP transports always use the configured server URL.

Basic auth is configured via `--sw-username` / `--sw-password` flags. Both flags (and the `set_skywalking_url` tool) support `${ENV_VAR}` syntax to resolve credentials from environment variables (e.g. `--sw-password ${MY_SECRET}`).
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}`).

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

Expand Down Expand Up @@ -99,4 +98,4 @@ Tool handlers should return `(mcp.NewToolResultError(...), nil)` for expected qu

## CI & Merge Policy

Squash-merge only. PRs to `main` require 1 approval and passing `Required` status check (license + lint + docker build). Go 1.25.
Squash-merge only. PRs to `main` require 1 approval and passing `Required` status check (license + lint + docker build). Go 1.25.
12 changes: 11 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ PLATFORMS ?= linux/amd64
MULTI_PLATFORMS ?= linux/amd64,linux/arm64
OUTPUT ?= --load
IMAGE_TAGS ?= -t $(IMAGE):$(VERSION) -t $(IMAGE):latest
GO_TEST_FLAGS ?=
GO_TEST_PKGS ?= ./...

.PHONY: all
all: build ;
Expand All @@ -48,6 +50,14 @@ build: ## Build the binary.
-X ${VERSION_PATH}.date=${BUILD_DATE}" \
-o bin/swmcp cmd/skywalking-mcp/main.go

.PHONY: test
test: ## Run unit tests.
go test $(GO_TEST_FLAGS) $(GO_TEST_PKGS)

.PHONY: test-cover
test-cover: ## Run unit tests with coverage output in coverage.txt.
go test $(GO_TEST_FLAGS) -coverprofile=coverage.txt $(GO_TEST_PKGS)

$(GO_LINT):
@$(GO_LINT) version > /dev/null 2>&1 || go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.0
$(LICENSE_EYE):
Expand Down Expand Up @@ -139,7 +149,7 @@ PUSH_RELEASE_SCRIPTS := ./scripts/push-release.sh
release-push-candidate:
${PUSH_RELEASE_SCRIPTS}

.PHONY: lint fix-lint
.PHONY: lint fix-lint test test-cover
.PHONY: license-header fix-license-header dependency-license fix-dependency-license
.PHONY: release-binary release-source release-sign release-assembly
.PHONY: release-push-candidate docker-build-multi
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ bin/swmcp stdio --sw-url http://localhost:12800 --sw-username admin --sw-passwor
bin/swmcp sse --sse-address localhost:8000 --base-path /mcp --sw-url http://localhost:12800
```

Transport URL behavior:

- `stdio`, `sse`, and `streamable` all use the configured `--sw-url` value (or the default `http://localhost:12800/graphql`).
- `sse` and `streamable` ignore request-level URL override headers.

### Usage with Cursor, Copilot, Claude Code

```json
Expand Down Expand Up @@ -128,7 +133,6 @@ SkyWalking MCP provides the following tools to query and analyze SkyWalking OAP

| Category | Tool Name | Description |
|--------------|--------------------------------|---------------------------------------------------------------------------------------------------|
| **Session** | `set_skywalking_url` | Set the SkyWalking OAP server URL and optional basic auth credentials for the current session (stdio mode only). Supports `${ENV_VAR}` syntax for credentials. |
| **Trace** | `query_traces` | Query traces with multi-condition filtering (service, endpoint, state, tags, and time range via start/end/step). Supports `full`, `summary`, and `errors_only` views with performance insights. |
| **Log** | `query_logs` | Query logs with filters for service, instance, endpoint, trace ID, tags, and time range. Supports cold storage and pagination. |
| **MQE** | `execute_mqe_expression` | Execute MQE (Metrics Query Expression) to query and calculate metrics data. Supports calculations, aggregations, TopN, trend analysis, and multiple result types. |
Expand Down Expand Up @@ -176,4 +180,4 @@ SkyWalking MCP provides the following prompts for guided analysis workflows:

[Apache 2.0 License.](/LICENSE)

[mcp]: https://modelcontextprotocol.io/
[mcp]: https://modelcontextprotocol.io/
53 changes: 8 additions & 45 deletions internal/swmcp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,13 @@ import (
)

// newMCPServer creates a new MCP server with all tools, resources, and prompts registered.
// When stdio is true, session management tools (set_skywalking_url) are also registered,
// since stdio has a single client and session semantics are well-defined.
func newMCPServer(stdio bool) *server.MCPServer {
func newMCPServer() *server.MCPServer {
s := server.NewMCPServer(
"skywalking-mcp", "0.1.0",
server.WithResourceCapabilities(true, true),
server.WithPromptCapabilities(true),
server.WithLogging(),
)
if stdio {
AddSessionTools(s)
}
tools.AddTraceTools(s)
tools.AddLogTools(s)
tools.AddMQETools(s)
Expand Down Expand Up @@ -131,63 +126,31 @@ func withConfiguredAuth(ctx context.Context) context.Context {
return ctx
}

// urlFromHeaders extracts URL for a request.
// URL is sourced from Header > configured value > Default.
func urlFromHeaders(req *http.Request) string {
urlStr := req.Header.Get("SW-URL")
if urlStr == "" {
return configuredSkyWalkingURL()
}

return tools.FinalizeURL(urlStr)
}

// applySessionOverrides checks for a session in the context and applies any
// URL or auth overrides that were set via the set_skywalking_url tool.
func applySessionOverrides(ctx context.Context) context.Context {
session := SessionFromContext(ctx)
if session == nil {
return ctx
}
if url := session.URL(); url != "" {
ctx = context.WithValue(ctx, contextkey.BaseURL{}, url)
}
if username := session.Username(); username != "" {
ctx = WithSkyWalkingAuth(ctx, username, session.Password())
}
return ctx
}

// EnhanceStdioContextFunc returns a StdioContextFunc that enriches the context
// with SkyWalking settings from the global configuration and a per-session store.
// with SkyWalking settings from the global configuration.
func EnhanceStdioContextFunc() server.StdioContextFunc {
session := &Session{}
return func(ctx context.Context) context.Context {
ctx = WithSession(ctx, session)
ctx = WithSkyWalkingURLAndInsecure(ctx, configuredSkyWalkingURL(), false)
ctx = withConfiguredAuth(ctx)
ctx = applySessionOverrides(ctx)
return ctx
}
}

// EnhanceSSEContextFunc returns a SSEContextFunc that enriches the context
// with SkyWalking settings from SSE request headers and CLI-configured auth.
// with SkyWalking settings from the CLI configuration and configured auth.
func EnhanceSSEContextFunc() server.SSEContextFunc {
return func(ctx context.Context, req *http.Request) context.Context {
urlStr := urlFromHeaders(req)
ctx = WithSkyWalkingURLAndInsecure(ctx, urlStr, false)
return func(ctx context.Context, _ *http.Request) context.Context {
ctx = WithSkyWalkingURLAndInsecure(ctx, configuredSkyWalkingURL(), false)
ctx = withConfiguredAuth(ctx)
return ctx
}
}

// EnhanceHTTPContextFunc returns a HTTPContextFunc that enriches the context
// with SkyWalking settings from HTTP request headers and CLI-configured auth.
// with SkyWalking settings from the CLI configuration and configured auth.
func EnhanceHTTPContextFunc() server.HTTPContextFunc {
return func(ctx context.Context, req *http.Request) context.Context {
urlStr := urlFromHeaders(req)
ctx = WithSkyWalkingURLAndInsecure(ctx, urlStr, false)
return func(ctx context.Context, _ *http.Request) context.Context {
ctx = WithSkyWalkingURLAndInsecure(ctx, configuredSkyWalkingURL(), false)
ctx = withConfiguredAuth(ctx)
return ctx
}
Expand Down
Loading
Loading