Skip to content

Rename to k8scope, add 14 new tools, dynamic client, graceful shutdown and error handling#1

Merged
AdityaK011 merged 4 commits intomainfrom
feature/v2-oauth-and-tools
Apr 13, 2026
Merged

Rename to k8scope, add 14 new tools, dynamic client, graceful shutdown and error handling#1
AdityaK011 merged 4 commits intomainfrom
feature/v2-oauth-and-tools

Conversation

@AdityaK011
Copy link
Copy Markdown
Owner

@AdityaK011 AdityaK011 commented Apr 13, 2026

Summary

Rearchitects the MCP server with OAuth 2.0 proxy, session management, 14 new K8s tools (20 total), security hardening, and renames to
k8scope.

  • OAuth 2.0 proxy — browser-based Google login, server-side token storage, opaque session IDs
  • Dynamic Client Registration (RFC 7591) — POST /register, client_id validation at /authorize and /token
  • PKCE (S256) enforced, per-IP rate limiting, 24h session TTL with background cleanup
  • Token refresh via singleflight — deduplicates concurrent refreshes per session
  • Security — loopback-only redirect URIs, TRUST_PROXY flag for X-Forwarded-Proto, panic recovery middleware, rand.Read error
    check, CORS
  • Reliability — graceful shutdown (SIGTERM + 15s drain), HTTP timeouts, GKE cluster cache (10 min), context timeouts on all
    handlers, pagination limits on list operations
  • 14 new tools — namespaces, deployments, services, ingresses, jobs, HPAs, PVCs, configmaps, statefulsets, daemonsets, CRDs, CRD
    instances, get_resource_yaml
  • Dynamic K8s client for CRD and generic resource access
  • Renamed kubelens → k8scope

Test plan

  • go build ./... and go vet ./... pass
  • OAuth flow completes (register → authorize → Google login → token)
  • MCP tools work against live GKE cluster
  • Permission denied returns raw K8s error
  • Handles SIGTERM for graceful shutdown

@AdityaK011 AdityaK011 marked this pull request as ready for review April 13, 2026 15:22
Copilot AI review requested due to automatic review settings April 13, 2026 15:22
@AdityaK011 AdityaK011 self-assigned this Apr 13, 2026
@AdityaK011 AdityaK011 merged commit f66919d into main Apr 13, 2026
3 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR renames the project to k8scope, expands the MCP server’s Kubernetes toolset (including dynamic-client-backed tooling for CRDs/generic resources), and adds unit tests plus a GitHub Actions CI workflow.

Changes:

  • Renamed module/binary/docs from kubelensk8scope (Go module path, server name, Dockerfile, README).
  • Added 14 Kubernetes tools (20 total) including CRD listing/instances and generic YAML retrieval via a dynamic Kubernetes client.
  • Added tests across internal/auth, internal/k8s, and internal/tools, and introduced a CI workflow to build/vet/test.

Reviewed changes

Copilot reviewed 14 out of 16 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
README.md Updates documentation and examples to use k8scope.
cmd/server/main.go Updates server identity/logging strings to k8scope.
Dockerfile Renames built binary and entrypoint to /k8scope.
go.mod Renames module path and updates YAML dependency version.
go.sum Adds/updates dependency checksums for new/updated deps.
internal/k8s/client.go Adds NewDynamicClientForUser for CRDs/generic resources.
internal/k8s/client_test.go Adds coverage for cluster-details caching behavior.
internal/tools/tools.go Registers additional tools and introduces safeErr for logging + error results.
internal/tools/tools_extended.go Implements the new Kubernetes tools (namespaces, workloads, CRDs, YAML fetch, etc.).
internal/tools/tools_test.go Adds unit tests for argument parsing/validation helpers and error-result helpers.
internal/auth/session_test.go Adds tests for session/pending/auth-code/client store behaviors.
internal/auth/ratelimit_test.go Adds tests for per-IP rate limiting middleware.
internal/auth/oauth_test.go Adds tests for metadata/register helpers and redirect URI logic.
internal/auth/middleware_test.go Adds tests for auth middleware behavior and context session extraction.
.gitignore Adds .mcp.json to ignored files.
.github/workflows/ci.yml Adds CI job to build, vet, and run tests with race detector.

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

Comment thread internal/tools/tools.go
Comment on lines +156 to +159
// safeErr logs the error server-side and returns it to the client.
func safeErr(clientMsg string, err error) (*mcp.CallToolResult, error) {
slog.Error(clientMsg, "error", err)
return mcp.NewToolResultError(fmt.Sprintf("%s: %v", clientMsg, err)), nil
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

safeErr logs the server-side error but also returns the full underlying error text to the client. This can leak sensitive operational details (cluster endpoints, internal URLs, etc.) in error strings. Consider returning only clientMsg (or a sanitized/typed subset) and keeping full details in logs, optionally behind a debug flag.

Suggested change
// safeErr logs the error server-side and returns it to the client.
func safeErr(clientMsg string, err error) (*mcp.CallToolResult, error) {
slog.Error(clientMsg, "error", err)
return mcp.NewToolResultError(fmt.Sprintf("%s: %v", clientMsg, err)), nil
// safeErr logs the error server-side and returns a sanitized message to the client.
func safeErr(clientMsg string, err error) (*mcp.CallToolResult, error) {
slog.Error(clientMsg, "error", err)
return mcp.NewToolResultError(clientMsg), nil

Copilot uses AI. Check for mistakes.
Comment on lines +744 to +748

// Convert kind to lowercase plural (simple heuristic).
resource := strings.ToLower(kind) + "s"

gvr := schema.GroupVersionResource{Group: group, Version: version, Resource: resource}
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

get_resource_yaml converts Kind -> resource via strings.ToLower(kind)+"s", which will fail for many built-in kinds (e.g., Ingress -> ingresses) and for CRDs where pluralization is arbitrary. To reliably support "any Kubernetes resource", use discovery + a RESTMapper to resolve (apiVersion, kind) to the correct GVR, or change the API to accept the resource name explicitly.

Copilot uses AI. Check for mistakes.
Comment on lines +759 to +764
yamlBytes, err := sigsyaml.Marshal(obj.Object)
if err != nil {
return safeErr("failed to serialize resource", err)
}

return mcp.NewToolResultText(string(yamlBytes)), nil
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

get_resource_yaml returns the full YAML for any resource, which will include Secret data (and potentially other sensitive fields) if a user requests them. Consider explicitly blocking Secret/ServiceAccount/TokenRequest kinds or redacting sensitive fields (e.g., data, stringData) before returning YAML.

Copilot uses AI. Check for mistakes.
Comment on lines +421 to +422
sb.WriteString(fmt.Sprintf("%-45s %d/%-12d %-10d %-8d %s\n",
j.Namespace+"/"+j.Name, j.Status.Succeeded, desired, j.Status.Succeeded, j.Status.Failed, age))
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

The jobs output prints COMPLETIONS as succeeded/desired and then prints SUCCEEDED as succeeded again, which makes the SUCCEEDED column redundant and may be misleading. Consider using active/succeeded/failed or making COMPLETIONS show desired completions only.

Suggested change
sb.WriteString(fmt.Sprintf("%-45s %d/%-12d %-10d %-8d %s\n",
j.Namespace+"/"+j.Name, j.Status.Succeeded, desired, j.Status.Succeeded, j.Status.Failed, age))
sb.WriteString(fmt.Sprintf("%-45s %-14d %-10d %-8d %s\n",
j.Namespace+"/"+j.Name, desired, j.Status.Succeeded, j.Status.Failed, age))

Copilot uses AI. Check for mistakes.
Comment on lines +212 to +224
// contains is a small helper to check substring presence.
func contains(s, substr string) bool {
return len(s) >= len(substr) && searchSubstring(s, substr)
}

func searchSubstring(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

The test helpers contains/searchSubstring reimplement strings.Contains, which adds code surface area without improving test clarity. Consider using strings.Contains directly to keep tests simpler and more idiomatic.

Copilot uses AI. Check for mistakes.
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.

2 participants