Skip to content

fleet-mcp: document required Fleet API endpoints and verify them at startup - #50817

Draft
lukeheath wants to merge 1 commit into
mainfrom
50814-fleet-mcp-endpoint-self-check
Draft

fleet-mcp: document required Fleet API endpoints and verify them at startup#50817
lukeheath wants to merge 1 commit into
mainfrom
50814-fleet-mcp-endpoint-self-check

Conversation

@lukeheath

@lukeheath lukeheath commented Aug 7, 2026

Copy link
Copy Markdown
Member

Related issue: Resolves #50814

Checklist for submitter

  • Input data is properly validated, SELECT * is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters.
  • Timeouts are implemented and retries are limited to avoid infinite loops

Testing

  • Added/updated automated tests
  • QA'd all new/changed functionality manually

Details

The dogfood Fleet Slack bot runs fleet-mcp with a least-privilege API-only user restricted to specific API endpoints. The MCP toolset has grown since that allowlist was written, so tools started failing with opaque 403s that nothing caught at deploy time.

This PR:

  1. Adds requiredEndpoints (new startup_check.go): the canonical list of the 19 Fleet API routes the MCP toolset depends on, matching the list enumerated in the issue.
  2. Adds a startup self-check: after the existing API-only verification, the server probes each route with the configured token and logs a warning for every route that returns HTTP 403, plus a summary pointing at the README allowlist. Non-fatal: the server still starts (remaining tools keep working, and a 403 can also be an intentional role limit). The check runs in a background goroutine so readiness (/healthz on SSE, stdin on stdio) never waits on a slow Fleet (30s cap). If Fleet becomes unreachable mid-check, the summary reports the check as incomplete rather than claiming an all-clear, and both blocked and unverified counts are reported when both occur.
  3. Probes are side-effect free: GET probes use ID 0 or a sentinel identifier that never exists. The two POST probes send empty JSON bodies that Fleet rejects during validation: POST /reports/run with no query/query_id errors in NewDistributedQueryCampaign before any query row or campaign is created, and POST /hosts/0/query fails the host lookup in runLiveQueryOnHostByIDEndpoint before the query is examined. A test (TestRequiredEndpoints_ProbesAreSideEffectFree) enforces this shape.
  4. Adds a drift guard test: TestRequiredEndpointsCoverSourcePaths scans the package source for Fleet API path literals and fails if one is missing from requiredEndpoints, so adding a tool that calls a new Fleet route forces the list (and README) to be updated. Where the HTTP method is extractable (method and path literal on the same makeFleetRequest line), the check is method-aware, so a new POST on an already-listed GET path is caught too. Exempt: GET /api/v1/fleet/results/websocket (raw handler, not subject to endpoint restrictions) and POST /api/v1/fleet/reports (developer-only -seed mode).
  5. Documents the list in the README: new "Required Fleet API endpoints" section with a per-route "used by" table, notes on the websocket handler and seed mode, and a description of the self-check. The security model section and the "Adding a new tool" checklist link to it.

No new dependencies; the module's go.mod is unchanged.

Manual QA

Built the binary and ran it against a fake Fleet server:

  • Nothing blocked: startup self-check: all 19 required Fleet API endpoints are reachable with the configured token, and the fake server's request log confirms all 19 probes with {} POST bodies and no other traffic.
  • Routes returning 403: one warning per blocked route (... returned HTTP 403 — blocked by the API-only user's endpoint restrictions or the token's role; MCP tools that call it will fail) plus the summary, and the server still starts.
  • Fake Fleet killed mid-check: per-route "could not probe" warnings and an honest startup self-check: incomplete — 16 of 19 ... could not be probed summary; the server still starts.
  • SSE mode with a blocked route: GET /healthz returns 200 immediately (readiness does not wait on the check) while the warnings land in the log.

Summary by CodeRabbit

  • New Features
    • Added a startup self-check that verifies access to required Fleet API endpoints.
    • Reports blocked, unavailable, or successfully verified endpoints without preventing the service from starting.
    • Performs checks safely, including non-destructive probes for write operations.
  • Bug Fixes
    • Service readiness and transport setup no longer wait for endpoint verification to complete.
  • Tests
    • Added coverage for endpoint accessibility, blocked requests, unavailable results, safe probes, and route coverage.

…tartup

Deployments that lock the API-only user down with endpoint restrictions
had no way to catch allowlist drift when the MCP toolset grew: tools
failed at runtime with opaque 403s. Add a requiredEndpoints list as the
source of truth, a non-fatal background self-check that probes each
route at startup and warns on 403s, a drift test that fails when the
package starts calling a Fleet API route missing from the list, and a
README section enumerating the routes to allowlist.

Resolves #50814
@lukeheath
lukeheath requested a review from a team as a code owner August 7, 2026 21:30
Copilot AI lite review requested due to automatic review settings August 7, 2026 21:30
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The MCP server now defines and probes all required Fleet API endpoints during startup. The check uses a 30-second overall timeout, classifies forbidden and transport failures separately, drains responses, and uses side-effect-free POST requests. Startup continues without waiting for verification. Tests validate endpoint behavior, path normalization, POST probe safety, and coverage of Fleet API routes used by the source.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive The startup self-check matches the issue, but README documentation cannot be verified because cmd/fleet-mcp/README.md was excluded by !**/*.md. Review cmd/fleet-mcp/README.md to confirm all 19 routes, usage notes, exclusions, and startup self-check documentation are present.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: documenting required Fleet API endpoints and verifying them at startup.
Description check ✅ Passed The description identifies the issue, explains the implementation, documents testing, and completes the relevant checklist items.
Out of Scope Changes check ✅ Passed All reviewable code and test changes support endpoint documentation, startup verification, probe safety, or endpoint-list drift detection.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 50814-fleet-mcp-endpoint-self-check

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/fleet-mcp/startup_check_test.go`:
- Around line 150-190: Update the startup endpoint validation around callRe to
resolve local endpoint variables assigned Fleet API paths, preserving their
associated HTTP method when checking coveredMethod and requiredEndpoints. Use Go
AST analysis with local assignment tracking or shared method/path descriptors,
and add a fixture that verifies a variable endpoint path with a mismatched
method is rejected.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 40f08067-ef2d-4163-b20e-e22032e65333

📥 Commits

Reviewing files that changed from the base of the PR and between bc537a3 and 98ea08e.

⛔ Files ignored due to path filters (1)
  • cmd/fleet-mcp/README.md is excluded by !**/*.md
📒 Files selected for processing (3)
  • cmd/fleet-mcp/main.go
  • cmd/fleet-mcp/startup_check.go
  • cmd/fleet-mcp/startup_check_test.go

Comment on lines +150 to +190
litRe := regexp.MustCompile(`"(/api/v1/fleet/[^"]*)"`)
// Method-aware variant for call sites where the method and path literal
// share a line, e.g. makeFleetRequest(ctx, "POST", "/api/v1/fleet/...",
// or with the path wrapped in fmt.Sprintf.
callRe := regexp.MustCompile(`makeFleetRequest\(ctx, "(GET|POST|PUT|PATCH|DELETE)",\s*(?:fmt\.Sprintf\()?"(/api/v1/fleet/[^"]*)"`)
files, err := filepath.Glob("*.go")
if err != nil {
t.Fatal(err)
}
for _, f := range files {
// startup_check.go defines the list itself; its probe literals are
// not additional API usage.
if strings.HasSuffix(f, "_test.go") || f == "startup_check.go" {
continue
}
src, err := os.ReadFile(f)
if err != nil {
t.Fatal(err)
}
for _, m := range litRe.FindAllStringSubmatch(string(src), -1) {
lit := m[1]
norm := normalizeAPIPath(lit)
if _, ok := exemptPaths[norm]; ok {
continue
}
if _, ok := covered[norm]; !ok {
t.Errorf("%s uses Fleet API path %q which is not listed in requiredEndpoints (startup_check.go); add it there and to the README endpoint list", f, lit)
}
}
for _, m := range callRe.FindAllStringSubmatch(string(src), -1) {
method, lit := m[1], m[2]
norm := normalizeAPIPath(lit)
if _, ok := exemptPaths[norm]; ok {
continue
}
if _, ok := exemptMethodPaths[method+" "+norm]; ok {
continue
}
if _, ok := coveredMethod[method+" "+norm]; !ok {
t.Errorf("%s calls %s %s which is not listed (with that method) in requiredEndpoints (startup_check.go); add it there and to the README endpoint list", f, method, lit)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Validate methods for variable endpoint paths.

callRe does not match calls that pass a local path variable. For example, cmd/fleet-mcp/fleet_integration.go:344-354 passes endpoint to makeFleetRequest after assigning the Fleet path on an earlier line.

The generic litRe check still finds the path. A future change from GET to POST can therefore pass this test while requiredEndpoints continues to probe GET. The startup check can then report success although the live tool requires a blocked method.

Use Go AST analysis with local assignment tracking, or define the tool routes from shared method-and-path descriptors. Add a fixture for a variable endpoint path with a method mismatch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/fleet-mcp/startup_check_test.go` around lines 150 - 190, Update the
startup endpoint validation around callRe to resolve local endpoint variables
assigned Fleet API paths, preserving their associated HTTP method when checking
coveredMethod and requiredEndpoints. Use Go AST analysis with local assignment
tracking or shared method/path descriptors, and add a fixture that verifies a
variable endpoint path with a mismatched method is rejected.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a canonical, documented allowlist of Fleet API routes required by cmd/fleet-mcp, plus a non-fatal startup self-check that probes those routes with the configured API-only token and logs actionable warnings for any HTTP 403 blocks (without delaying readiness).

Changes:

  • Introduces requiredEndpoints and a background startup probe (verifyRequiredEndpoints) to detect allowlist drift early.
  • Adds tests to validate probe behavior and guard against endpoint-list drift from source usage.
  • Documents the required endpoint list and the startup self-check behavior in cmd/fleet-mcp/README.md.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
cmd/fleet-mcp/startup_check.go Adds the required endpoint list and startup probing/logging logic.
cmd/fleet-mcp/startup_check_test.go Adds tests for probing outcomes and drift/side-effect safety assertions.
cmd/fleet-mcp/README.md Documents required endpoints and explains the startup self-check and exclusions.
cmd/fleet-mcp/main.go Starts the self-check concurrently after API-only verification.
Suppressed comments (1)

cmd/fleet-mcp/startup_check_test.go:92

  • TestRequiredEndpoints_ProbesAreSideEffectFree currently only checks that POST probes have a non-nil body, but it doesn’t verify that the body is actually the intended empty JSON object ({}). That means a future change could accidentally add query SQL or other fields and the test would still pass, contradicting the test’s own comment and the PR description.
	// Every POST probe must carry a body that fails Fleet-side validation
	// before anything is created or executed: no query SQL, no real
	// host/query IDs in the path.
	realIDRe := regexp.MustCompile(`^[1-9][0-9]*$`)
	for _, e := range requiredEndpoints {
		if e.method == "GET" {
			continue
		}
		if e.body == nil {
			t.Errorf("POST probe %s must send a JSON body", e.route)
		}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +98 to +100
// Drain a bounded amount so the connection can be reused.
_, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, 4096))
resp.Body.Close()
Comment on lines +3 to +12
import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"regexp"
"slices"
"strings"
"testing"
)
@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 68.53%. Comparing base (f654fe9) to head (98ea08e).
⚠️ Report is 7 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main   #50817   +/-   ##
=======================================
  Coverage   68.52%   68.53%           
=======================================
  Files        3977     3977           
  Lines      256094   256142   +48     
  Branches    13658    13658           
=======================================
+ Hits       175489   175536   +47     
+ Misses      64987    64986    -1     
- Partials    15618    15620    +2     
Flag Coverage Δ
backend 69.63% <ø> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@lukeheath
lukeheath marked this pull request as draft August 8, 2026 15:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fleet-mcp: document required Fleet API endpoints and verify them at startup

2 participants