kubectl-mcp-server 1.12.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. kubectl_mcp_server-1.12.0.dist-info/METADATA +711 -0
  2. kubectl_mcp_server-1.12.0.dist-info/RECORD +45 -0
  3. kubectl_mcp_server-1.12.0.dist-info/WHEEL +5 -0
  4. kubectl_mcp_server-1.12.0.dist-info/entry_points.txt +3 -0
  5. kubectl_mcp_server-1.12.0.dist-info/licenses/LICENSE +21 -0
  6. kubectl_mcp_server-1.12.0.dist-info/top_level.txt +2 -0
  7. kubectl_mcp_tool/__init__.py +21 -0
  8. kubectl_mcp_tool/__main__.py +46 -0
  9. kubectl_mcp_tool/auth/__init__.py +13 -0
  10. kubectl_mcp_tool/auth/config.py +71 -0
  11. kubectl_mcp_tool/auth/scopes.py +148 -0
  12. kubectl_mcp_tool/auth/verifier.py +82 -0
  13. kubectl_mcp_tool/cli/__init__.py +9 -0
  14. kubectl_mcp_tool/cli/__main__.py +10 -0
  15. kubectl_mcp_tool/cli/cli.py +111 -0
  16. kubectl_mcp_tool/diagnostics.py +355 -0
  17. kubectl_mcp_tool/k8s_config.py +289 -0
  18. kubectl_mcp_tool/mcp_server.py +530 -0
  19. kubectl_mcp_tool/prompts/__init__.py +5 -0
  20. kubectl_mcp_tool/prompts/prompts.py +823 -0
  21. kubectl_mcp_tool/resources/__init__.py +5 -0
  22. kubectl_mcp_tool/resources/resources.py +305 -0
  23. kubectl_mcp_tool/tools/__init__.py +28 -0
  24. kubectl_mcp_tool/tools/browser.py +371 -0
  25. kubectl_mcp_tool/tools/cluster.py +315 -0
  26. kubectl_mcp_tool/tools/core.py +421 -0
  27. kubectl_mcp_tool/tools/cost.py +680 -0
  28. kubectl_mcp_tool/tools/deployments.py +381 -0
  29. kubectl_mcp_tool/tools/diagnostics.py +174 -0
  30. kubectl_mcp_tool/tools/helm.py +1561 -0
  31. kubectl_mcp_tool/tools/networking.py +296 -0
  32. kubectl_mcp_tool/tools/operations.py +501 -0
  33. kubectl_mcp_tool/tools/pods.py +582 -0
  34. kubectl_mcp_tool/tools/security.py +333 -0
  35. kubectl_mcp_tool/tools/storage.py +133 -0
  36. kubectl_mcp_tool/utils/__init__.py +17 -0
  37. kubectl_mcp_tool/utils/helpers.py +80 -0
  38. tests/__init__.py +9 -0
  39. tests/conftest.py +379 -0
  40. tests/test_auth.py +256 -0
  41. tests/test_browser.py +349 -0
  42. tests/test_prompts.py +536 -0
  43. tests/test_resources.py +343 -0
  44. tests/test_server.py +384 -0
  45. tests/test_tools.py +659 -0
@@ -0,0 +1,45 @@
1
+ kubectl_mcp_server-1.12.0.dist-info/licenses/LICENSE,sha256=nH9Z0W0WNH2oQ4cPrBAU8ldDcHfeI6NUbkSGiazYWgQ,1070
2
+ kubectl_mcp_tool/__init__.py,sha256=Z_IyVF0iPum8NzMC_5yXS1P_1vwrW9yQUalEfCEjjzs,580
3
+ kubectl_mcp_tool/__main__.py,sha256=CE6cTD6PA71Ap0i5_gE17Pb9FcedOJmtGRNzZ5-TFSc,1490
4
+ kubectl_mcp_tool/diagnostics.py,sha256=uwolSoHadRkB-J8PAsabbexfj6sTNCIIRRrABBRXoTU,11776
5
+ kubectl_mcp_tool/k8s_config.py,sha256=StM6Bb1SAVbFgNg5wKRmb_9aQ1wxEHtriCTIjvEFc5U,7853
6
+ kubectl_mcp_tool/mcp_server.py,sha256=OUrW04BRVpmsr1-8NDTjC1vaJ0VQCbjkcXkavy9lzVs,20927
7
+ kubectl_mcp_tool/auth/__init__.py,sha256=ot8ivZZkDtV8Rg0y1UYruwobKCPyxX1svqh35wWxKvY,347
8
+ kubectl_mcp_tool/auth/config.py,sha256=wi3wuJNMyDqMeluDHL0MaJyedIFv5CFVxiUaEVaTvzk,2267
9
+ kubectl_mcp_tool/auth/scopes.py,sha256=KPmuGO0SrTkjzlElWFOV29ie9apTdMklOCkiA-965lI,6147
10
+ kubectl_mcp_tool/auth/verifier.py,sha256=ChZM-UsZJgZc3LjSfw8VfSydkKqKBZ1s8es71LlB_A0,2431
11
+ kubectl_mcp_tool/cli/__init__.py,sha256=qGL_jH_5iv4cZsRvAbqAeKUN-ETh1K98_uJAPfgLvSU,147
12
+ kubectl_mcp_tool/cli/__main__.py,sha256=OxhHqW8YsTd565acTDlZknljPAw6FN9JW7pNtgWIcFE,196
13
+ kubectl_mcp_tool/cli/cli.py,sha256=f82U3B2I93lQUVJGKL33dPhxQ-5GBNjaJ26PwPj4O0Y,3769
14
+ kubectl_mcp_tool/prompts/__init__.py,sha256=BacBNfoVxow6aci8Zzcfam3m1oM7yYzM0IRT1L3uCOQ,77
15
+ kubectl_mcp_tool/prompts/prompts.py,sha256=ZfmTCio8NqOYYxF8VVo9f6VWGCS34J8tvmBfhNblr58,22942
16
+ kubectl_mcp_tool/resources/__init__.py,sha256=ERkn0ErlaGi9-dybv4wrAaT8WretvNp6K002h7Agjno,83
17
+ kubectl_mcp_tool/resources/resources.py,sha256=kvK4OM3Ox5cFvWDqJBTXOfBgnRYdoqdvvjsdCg0PJfY,12713
18
+ kubectl_mcp_tool/tools/__init__.py,sha256=mlvnz6P99jKwj6S8NJuX9UyaOLU1ARa5-CCkcc-3ju0,958
19
+ kubectl_mcp_tool/tools/browser.py,sha256=PDT7Uj7CCBQI9CFAaMSM_3i6v3NXIAf-mwd1y7iESa8,15193
20
+ kubectl_mcp_tool/tools/cluster.py,sha256=qQh6bET3-KQ8nkB2GnB8A7gohbJLrx6_K11TPYR6AII,11956
21
+ kubectl_mcp_tool/tools/core.py,sha256=zL0bGCxPGocH34ueZlSsBVsNp_tUL0C2gqyjlJljMXY,15009
22
+ kubectl_mcp_tool/tools/cost.py,sha256=OMiNopWvi5TTY-HX-3JSekelgReTEn88wAFddhui--M,27065
23
+ kubectl_mcp_tool/tools/deployments.py,sha256=TX1QHa22QU7rK7KG2-p5FB5KyWasZGfu-SK2CA08gWI,14990
24
+ kubectl_mcp_tool/tools/diagnostics.py,sha256=_xVcbmT3smQvOMwrZ0hlJ0FpgPbB4bdQgLkNcDJ8Vj4,6695
25
+ kubectl_mcp_tool/tools/helm.py,sha256=VgpiGPNbq4gQ4NQwefaRQPKGI7woN4QTHxtoz0_cNW8,57094
26
+ kubectl_mcp_tool/tools/networking.py,sha256=4_30hyQM-9t-X-yJWBrsdA9nPONZk3F6kyw3Y5-JKrA,11977
27
+ kubectl_mcp_tool/tools/operations.py,sha256=egYhlmqVZygddCkDJ2GDr89EjApps_PssWraOVfRmwQ,18800
28
+ kubectl_mcp_tool/tools/pods.py,sha256=7Ezk9khxoKsJYw8xEyBMjFdpLXmisGGrnKVVJ3rbhLE,23801
29
+ kubectl_mcp_tool/tools/security.py,sha256=Tu5SFPoeSgXkoc7WEpnnmdrMDJaB5znDoIVyyAy1Rho,12550
30
+ kubectl_mcp_tool/tools/storage.py,sha256=lXWp7131P39eTAsaMFrrJbWPuOGfDolVbD6npTBy1eQ,5124
31
+ kubectl_mcp_tool/utils/__init__.py,sha256=CHBCpaXwt994DlqyRFkkRky2TK8OmmDl0Gyc28369gI,348
32
+ kubectl_mcp_tool/utils/helpers.py,sha256=W--wiVSKKqmjpxxdLT0J6rmhOQcp1OFk9jLrtQUVpGw,2444
33
+ tests/__init__.py,sha256=qZPXYXv3whkkWhi61Ngzj09GHnIFlVSZrajE0XRk55o,290
34
+ tests/conftest.py,sha256=6054YlpuGleV3Wg8BnVj4lnKWhGk-Eqc9JYTXxOmsXs,10782
35
+ tests/test_auth.py,sha256=PoESfWiN92wSGUdVwLL3Z1AP6C1zUsVmgTI7Q8ZdlxM,11074
36
+ tests/test_browser.py,sha256=Oav6N4ivr_DW9038bVl31eAACt6vQORTr0uRgfeLs9A,14074
37
+ tests/test_prompts.py,sha256=3TcJUvSNxhTqfySW6DCrW9MwiMDumciLQRjjbucwqlA,17803
38
+ tests/test_resources.py,sha256=Z0Ex8WdRz-B3VZa1s0eAaDDGbhy7dRdqy1uFVOe2Qbo,12689
39
+ tests/test_server.py,sha256=lLvgbqutnivSgQMNrki0O48whBQt0UXjdwT047nf0nw,14415
40
+ tests/test_tools.py,sha256=MYJn8YlZIg2boX0OGTXyONRuA3M7vYZEATkwSVsya0c,30241
41
+ kubectl_mcp_server-1.12.0.dist-info/METADATA,sha256=Czr2PxQzTXEnV2Lc0d4d-wezu5rDBkSj_Fry1pajS_Q,23715
42
+ kubectl_mcp_server-1.12.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
43
+ kubectl_mcp_server-1.12.0.dist-info/entry_points.txt,sha256=zeOxQGaNC4r58deEmqsLCU5hfMjF0VqFUt9P5wWsKEM,109
44
+ kubectl_mcp_server-1.12.0.dist-info/top_level.txt,sha256=o5IpfOGG-lqU8rVWJeK9aYC0r4f6qEX09QiBhZlYbkQ,23
45
+ kubectl_mcp_server-1.12.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ kubectl-mcp = kubectl_mcp_tool.__main__:main
3
+ kubectl-mcp-serve = kubectl_mcp_tool.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Rohit Ghumare
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ kubectl_mcp_tool
2
+ tests
@@ -0,0 +1,21 @@
1
+ """
2
+ Kubectl MCP Tool - A Model Context Protocol server for Kubernetes.
3
+
4
+ This package provides an MCP server that enables AI assistants to interact
5
+ with Kubernetes clusters through natural language commands.
6
+
7
+ For more information, see: https://github.com/rohitg00/kubectl-mcp-server
8
+ """
9
+
10
+ __version__ = "1.12.0"
11
+
12
+ from .mcp_server import MCPServer
13
+ from .diagnostics import run_diagnostics, check_kubectl_installation, check_cluster_connection
14
+
15
+ __all__ = [
16
+ "__version__",
17
+ "MCPServer",
18
+ "run_diagnostics",
19
+ "check_kubectl_installation",
20
+ "check_cluster_connection",
21
+ ]
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env python3
2
+ """Main entry point for the kubectl MCP tool."""
3
+
4
+ import asyncio
5
+ import argparse
6
+ import logging
7
+ import sys
8
+ import platform
9
+
10
+ if platform.system() == "Windows":
11
+ asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
12
+
13
+ from .mcp_server import MCPServer
14
+
15
+ logging.basicConfig(
16
+ level=logging.INFO,
17
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
18
+ handlers=[logging.StreamHandler(sys.stderr)]
19
+ )
20
+ logger = logging.getLogger("mcp-server")
21
+
22
+
23
+ def main():
24
+ """Run the kubectl MCP server."""
25
+ parser = argparse.ArgumentParser(description="Kubectl MCP Server")
26
+ parser.add_argument("--transport", choices=["stdio", "sse", "http", "streamable-http"], default="stdio")
27
+ parser.add_argument("--port", type=int, default=8000)
28
+ parser.add_argument("--host", type=str, default="0.0.0.0")
29
+ parser.add_argument("--non-destructive", action="store_true", help="Block destructive operations")
30
+ args = parser.parse_args()
31
+
32
+ server = MCPServer(name="kubernetes", non_destructive=args.non_destructive)
33
+
34
+ try:
35
+ if args.transport == "stdio":
36
+ asyncio.run(server.serve_stdio())
37
+ elif args.transport == "sse":
38
+ asyncio.run(server.serve_sse(host=args.host, port=args.port))
39
+ elif args.transport in ("http", "streamable-http"):
40
+ asyncio.run(server.serve_http(host=args.host, port=args.port))
41
+ except KeyboardInterrupt:
42
+ pass
43
+
44
+
45
+ if __name__ == "__main__":
46
+ main()
@@ -0,0 +1,13 @@
1
+ """MCP Authorization module implementing RFC 9728 OAuth 2.0 Protected Resource Metadata."""
2
+
3
+ from .config import AuthConfig, get_auth_config
4
+ from .scopes import MCPScopes, TOOL_SCOPES
5
+ from .verifier import create_auth_verifier
6
+
7
+ __all__ = [
8
+ "AuthConfig",
9
+ "get_auth_config",
10
+ "MCPScopes",
11
+ "TOOL_SCOPES",
12
+ "create_auth_verifier",
13
+ ]
@@ -0,0 +1,71 @@
1
+ """Authentication configuration loaded from environment variables."""
2
+
3
+ import os
4
+ import logging
5
+ from dataclasses import dataclass, field
6
+ from typing import List, Optional
7
+
8
+ logger = logging.getLogger("mcp-server.auth")
9
+
10
+
11
+ @dataclass
12
+ class AuthConfig:
13
+ """Authentication configuration."""
14
+
15
+ enabled: bool = False
16
+ issuer_url: Optional[str] = None
17
+ jwks_uri: Optional[str] = None
18
+ audience: str = "kubectl-mcp-server"
19
+ required_scopes: List[str] = field(default_factory=lambda: ["mcp:tools"])
20
+ resource_url: Optional[str] = None
21
+
22
+ def validate(self) -> bool:
23
+ """Validate configuration."""
24
+ if not self.enabled:
25
+ return True
26
+
27
+ if not self.issuer_url:
28
+ logger.error("MCP_AUTH_ISSUER is required when authentication is enabled")
29
+ return False
30
+
31
+ return True
32
+
33
+ @property
34
+ def effective_jwks_uri(self) -> Optional[str]:
35
+ """Get JWKS URI, deriving from issuer if not explicitly set."""
36
+ if self.jwks_uri:
37
+ return self.jwks_uri
38
+ if self.issuer_url:
39
+ # Standard OIDC discovery path
40
+ issuer = self.issuer_url.rstrip("/")
41
+ return f"{issuer}/.well-known/jwks.json"
42
+ return None
43
+
44
+
45
+ def get_auth_config() -> AuthConfig:
46
+ """Load authentication configuration from environment variables."""
47
+ enabled = os.environ.get("MCP_AUTH_ENABLED", "").lower() in ("1", "true", "yes")
48
+
49
+ config = AuthConfig(
50
+ enabled=enabled,
51
+ issuer_url=os.environ.get("MCP_AUTH_ISSUER"),
52
+ jwks_uri=os.environ.get("MCP_AUTH_JWKS_URI"),
53
+ audience=os.environ.get("MCP_AUTH_AUDIENCE", "kubectl-mcp-server"),
54
+ required_scopes=_parse_scopes(os.environ.get("MCP_AUTH_REQUIRED_SCOPES", "mcp:tools")),
55
+ resource_url=os.environ.get("MCP_AUTH_RESOURCE_URL"),
56
+ )
57
+
58
+ if enabled:
59
+ logger.info(f"Authentication enabled with issuer: {config.issuer_url}")
60
+ logger.info(f"Required scopes: {config.required_scopes}")
61
+ else:
62
+ logger.debug("Authentication disabled")
63
+
64
+ return config
65
+
66
+
67
+ def _parse_scopes(scopes_str: str) -> List[str]:
68
+ """Parse comma-separated scopes string."""
69
+ if not scopes_str:
70
+ return []
71
+ return [s.strip() for s in scopes_str.split(",") if s.strip()]
@@ -0,0 +1,148 @@
1
+ """MCP OAuth 2.0 scope definitions for fine-grained access control."""
2
+
3
+ from enum import Enum
4
+ from typing import Dict, List, Set
5
+
6
+
7
+ class MCPScopes(str, Enum):
8
+ """MCP OAuth 2.0 scopes."""
9
+
10
+ # Base scopes
11
+ READ = "mcp:read"
12
+ WRITE = "mcp:write"
13
+ ADMIN = "mcp:admin"
14
+ TOOLS = "mcp:tools"
15
+
16
+ # Category-specific scopes
17
+ HELM = "mcp:helm"
18
+ DIAGNOSTICS = "mcp:diagnostics"
19
+ NETWORKING = "mcp:networking"
20
+ STORAGE = "mcp:storage"
21
+ SECURITY = "mcp:security"
22
+ COST = "mcp:cost"
23
+
24
+ @classmethod
25
+ def all_scopes(cls) -> List[str]:
26
+ """Return all available scopes."""
27
+ return [scope.value for scope in cls]
28
+
29
+ @classmethod
30
+ def read_scopes(cls) -> List[str]:
31
+ """Return scopes for read-only access."""
32
+ return [cls.READ.value, cls.DIAGNOSTICS.value]
33
+
34
+ @classmethod
35
+ def write_scopes(cls) -> List[str]:
36
+ """Return scopes for write access."""
37
+ return [cls.READ.value, cls.WRITE.value]
38
+
39
+ @classmethod
40
+ def admin_scopes(cls) -> List[str]:
41
+ """Return scopes for admin access."""
42
+ return cls.all_scopes()
43
+
44
+
45
+ # Map tool names to required scopes
46
+ # Tools not in this map require the default scope (mcp:tools)
47
+ TOOL_SCOPES: Dict[str, Set[str]] = {
48
+ # Read-only tools - require mcp:read
49
+ "get_pods": {MCPScopes.READ.value},
50
+ "get_pod_details": {MCPScopes.READ.value},
51
+ "list_namespaces": {MCPScopes.READ.value},
52
+ "get_deployments": {MCPScopes.READ.value},
53
+ "get_services": {MCPScopes.READ.value},
54
+ "get_nodes": {MCPScopes.READ.value},
55
+ "get_events": {MCPScopes.READ.value},
56
+ "get_configmaps": {MCPScopes.READ.value},
57
+ "describe_resource": {MCPScopes.READ.value},
58
+ "get_cluster_info": {MCPScopes.READ.value},
59
+ "get_api_resources": {MCPScopes.READ.value},
60
+ "get_api_versions": {MCPScopes.READ.value},
61
+ "get_resource_usage": {MCPScopes.READ.value},
62
+ "get_pod_logs": {MCPScopes.READ.value},
63
+
64
+ # Write tools - require mcp:write
65
+ "create_namespace": {MCPScopes.WRITE.value},
66
+ "delete_namespace": {MCPScopes.WRITE.value, MCPScopes.ADMIN.value},
67
+ "scale_deployment": {MCPScopes.WRITE.value},
68
+ "restart_deployment": {MCPScopes.WRITE.value},
69
+ "delete_pod": {MCPScopes.WRITE.value},
70
+ "apply_manifest": {MCPScopes.WRITE.value},
71
+ "delete_resource": {MCPScopes.WRITE.value},
72
+ "patch_resource": {MCPScopes.WRITE.value},
73
+ "create_configmap": {MCPScopes.WRITE.value},
74
+ "update_configmap": {MCPScopes.WRITE.value},
75
+ "delete_configmap": {MCPScopes.WRITE.value},
76
+ "create_secret": {MCPScopes.WRITE.value, MCPScopes.ADMIN.value},
77
+ "delete_secret": {MCPScopes.WRITE.value, MCPScopes.ADMIN.value},
78
+
79
+ # Admin tools - require mcp:admin
80
+ "get_rbac_roles": {MCPScopes.ADMIN.value, MCPScopes.SECURITY.value},
81
+ "get_cluster_roles": {MCPScopes.ADMIN.value, MCPScopes.SECURITY.value},
82
+ "audit_rbac_permissions": {MCPScopes.ADMIN.value, MCPScopes.SECURITY.value},
83
+ "analyze_pod_security": {MCPScopes.ADMIN.value, MCPScopes.SECURITY.value},
84
+ "check_secrets_security": {MCPScopes.ADMIN.value, MCPScopes.SECURITY.value},
85
+ "get_pod_security_info": {MCPScopes.ADMIN.value, MCPScopes.SECURITY.value},
86
+ "cordon_node": {MCPScopes.ADMIN.value},
87
+ "uncordon_node": {MCPScopes.ADMIN.value},
88
+ "drain_node": {MCPScopes.ADMIN.value},
89
+ "taint_node": {MCPScopes.ADMIN.value},
90
+
91
+ # Helm tools - require mcp:helm
92
+ "helm_list_releases": {MCPScopes.HELM.value, MCPScopes.READ.value},
93
+ "helm_get_values": {MCPScopes.HELM.value, MCPScopes.READ.value},
94
+ "helm_get_manifest": {MCPScopes.HELM.value, MCPScopes.READ.value},
95
+ "helm_install": {MCPScopes.HELM.value, MCPScopes.WRITE.value},
96
+ "helm_upgrade": {MCPScopes.HELM.value, MCPScopes.WRITE.value},
97
+ "helm_uninstall": {MCPScopes.HELM.value, MCPScopes.WRITE.value},
98
+ "helm_rollback": {MCPScopes.HELM.value, MCPScopes.WRITE.value},
99
+ "helm_repo_add": {MCPScopes.HELM.value, MCPScopes.WRITE.value},
100
+ "helm_repo_list": {MCPScopes.HELM.value, MCPScopes.READ.value},
101
+ "helm_search": {MCPScopes.HELM.value, MCPScopes.READ.value},
102
+
103
+ # Diagnostic tools - require mcp:diagnostics
104
+ "run_pod_diagnostics": {MCPScopes.DIAGNOSTICS.value},
105
+ "check_pod_health": {MCPScopes.DIAGNOSTICS.value},
106
+ "analyze_crashloopbackoff": {MCPScopes.DIAGNOSTICS.value},
107
+ "diagnose_pending_pods": {MCPScopes.DIAGNOSTICS.value},
108
+ "check_resource_quotas": {MCPScopes.DIAGNOSTICS.value},
109
+ "analyze_network_policies": {MCPScopes.DIAGNOSTICS.value, MCPScopes.NETWORKING.value},
110
+ "get_cluster_health": {MCPScopes.DIAGNOSTICS.value},
111
+
112
+ # Networking tools - require mcp:networking
113
+ "get_network_policies": {MCPScopes.NETWORKING.value, MCPScopes.READ.value},
114
+ "get_ingresses": {MCPScopes.NETWORKING.value, MCPScopes.READ.value},
115
+ "test_service_connectivity": {MCPScopes.NETWORKING.value, MCPScopes.DIAGNOSTICS.value},
116
+
117
+ # Storage tools - require mcp:storage
118
+ "get_persistent_volumes": {MCPScopes.STORAGE.value, MCPScopes.READ.value},
119
+ "get_persistent_volume_claims": {MCPScopes.STORAGE.value, MCPScopes.READ.value},
120
+ "get_storage_classes": {MCPScopes.STORAGE.value, MCPScopes.READ.value},
121
+ "analyze_storage_usage": {MCPScopes.STORAGE.value, MCPScopes.DIAGNOSTICS.value},
122
+
123
+ # Cost tools - require mcp:cost
124
+ "estimate_workload_cost": {MCPScopes.COST.value},
125
+ "get_resource_recommendations": {MCPScopes.COST.value, MCPScopes.DIAGNOSTICS.value},
126
+ "analyze_resource_efficiency": {MCPScopes.COST.value, MCPScopes.DIAGNOSTICS.value},
127
+ }
128
+
129
+
130
+ def get_required_scopes(tool_name: str) -> Set[str]:
131
+ """Get required scopes for a tool."""
132
+ return TOOL_SCOPES.get(tool_name, {MCPScopes.TOOLS.value})
133
+
134
+
135
+ def has_required_scopes(token_scopes: Set[str], tool_name: str) -> bool:
136
+ """Check if token has required scopes for a tool."""
137
+ required = get_required_scopes(tool_name)
138
+
139
+ # mcp:tools grants access to all tools
140
+ if MCPScopes.TOOLS.value in token_scopes:
141
+ return True
142
+
143
+ # mcp:admin grants access to all tools
144
+ if MCPScopes.ADMIN.value in token_scopes:
145
+ return True
146
+
147
+ # Check if token has at least one of the required scopes
148
+ return bool(token_scopes & required)
@@ -0,0 +1,82 @@
1
+ """JWT token verification via JWKS endpoints from OIDC-compliant identity providers."""
2
+
3
+ import logging
4
+ from typing import Optional, Any
5
+
6
+ from .config import AuthConfig
7
+
8
+ logger = logging.getLogger("mcp-server.auth")
9
+
10
+
11
+ def create_auth_verifier(config: AuthConfig) -> Optional[Any]:
12
+ """
13
+ Create an authentication verifier based on configuration.
14
+
15
+ Returns a FastMCP-compatible auth verifier or None if auth is disabled.
16
+ """
17
+ if not config.enabled:
18
+ logger.debug("Authentication disabled, no verifier created")
19
+ return None
20
+
21
+ if not config.validate():
22
+ raise ValueError("Invalid authentication configuration")
23
+
24
+ try:
25
+ from fastmcp.server.auth import JWTVerifier
26
+ except ImportError:
27
+ logger.warning(
28
+ "FastMCP auth module not available. "
29
+ "Authentication requires fastmcp>=3.0.0 with auth support."
30
+ )
31
+ return None
32
+
33
+ jwks_uri = config.effective_jwks_uri
34
+ if not jwks_uri:
35
+ raise ValueError("JWKS URI could not be determined from configuration")
36
+
37
+ logger.info(f"Creating JWT verifier with JWKS URI: {jwks_uri}")
38
+ logger.info(f"Expected audience: {config.audience}")
39
+ logger.info(f"Expected issuer: {config.issuer_url}")
40
+
41
+ verifier = JWTVerifier(
42
+ jwks_uri=jwks_uri,
43
+ issuer=config.issuer_url,
44
+ audience=config.audience,
45
+ )
46
+
47
+ return verifier
48
+
49
+
50
+ def create_auth_settings(config: AuthConfig) -> Optional[Any]:
51
+ """
52
+ Create RFC 9728 Protected Resource Metadata settings.
53
+
54
+ This enables the /.well-known/oauth-protected-resource endpoint
55
+ that MCP clients use to discover authorization requirements.
56
+ """
57
+ if not config.enabled:
58
+ return None
59
+
60
+ if not config.resource_url:
61
+ logger.debug("No resource URL configured, skipping RFC 9728 metadata")
62
+ return None
63
+
64
+ try:
65
+ from pydantic import AnyHttpUrl
66
+ from mcp.server.auth.settings import AuthSettings
67
+ except ImportError:
68
+ logger.warning(
69
+ "MCP auth settings not available. "
70
+ "RFC 9728 metadata requires mcp>=1.8.0."
71
+ )
72
+ return None
73
+
74
+ logger.info(f"Creating RFC 9728 auth settings for resource: {config.resource_url}")
75
+
76
+ settings = AuthSettings(
77
+ issuer_url=AnyHttpUrl(config.issuer_url),
78
+ resource_server_url=AnyHttpUrl(config.resource_url),
79
+ required_scopes=config.required_scopes,
80
+ )
81
+
82
+ return settings
@@ -0,0 +1,9 @@
1
+ """
2
+ CLI package for kubectl-mcp-tool.
3
+
4
+ Provides command-line interface for managing the MCP server.
5
+ """
6
+
7
+ from .cli import main
8
+
9
+ __all__ = ["main"]
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Main entry point for the kubectl-mcp-tool CLI.
4
+ Allows running as: python -m kubectl_mcp_tool.cli
5
+ """
6
+
7
+ from .cli import main
8
+
9
+ if __name__ == "__main__":
10
+ exit(main())
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env python3
2
+ """CLI module for kubectl-mcp-tool."""
3
+
4
+ import sys
5
+ import os
6
+ import logging
7
+ import asyncio
8
+ import argparse
9
+ import traceback
10
+ from ..mcp_server import MCPServer
11
+
12
+ log_file = os.environ.get("MCP_LOG_FILE")
13
+ log_level = logging.DEBUG if os.environ.get("MCP_DEBUG", "").lower() in ("1", "true") else logging.INFO
14
+
15
+ handlers = []
16
+ if log_file:
17
+ os.makedirs(os.path.dirname(log_file), exist_ok=True)
18
+ handlers.append(logging.FileHandler(log_file))
19
+ handlers.append(logging.StreamHandler(sys.stderr))
20
+
21
+ logging.basicConfig(
22
+ level=log_level,
23
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
24
+ handlers=handlers
25
+ )
26
+ logger = logging.getLogger("kubectl-mcp-cli")
27
+
28
+
29
+ async def serve_stdio():
30
+ """Serve the MCP server over stdio transport."""
31
+ server = MCPServer("kubernetes")
32
+ await server.serve_stdio()
33
+
34
+
35
+ async def serve_sse(host: str, port: int):
36
+ """Serve the MCP server over SSE transport."""
37
+ server = MCPServer("kubernetes")
38
+ await server.serve_sse(host=host, port=port)
39
+
40
+
41
+ async def serve_http(host: str, port: int):
42
+ """Serve the MCP server over HTTP transport."""
43
+ server = MCPServer("kubernetes")
44
+ await server.serve_http(host=host, port=port)
45
+
46
+
47
+ def main():
48
+ """Main entry point for the CLI."""
49
+ parser = argparse.ArgumentParser(
50
+ description="kubectl-mcp-tool - MCP server for Kubernetes",
51
+ formatter_class=argparse.RawDescriptionHelpFormatter,
52
+ epilog="""
53
+ Examples:
54
+ kubectl-mcp serve # stdio transport (Claude Desktop/Cursor)
55
+ kubectl-mcp serve --transport sse # SSE transport
56
+ kubectl-mcp serve --transport http # HTTP transport
57
+ kubectl-mcp diagnostics # Run cluster diagnostics
58
+ """
59
+ )
60
+ subparsers = parser.add_subparsers(dest="command", help="Command to run")
61
+
62
+ serve_parser = subparsers.add_parser("serve", help="Start the MCP server")
63
+ serve_parser.add_argument(
64
+ "--transport",
65
+ choices=["stdio", "sse", "http", "streamable-http"],
66
+ default="stdio",
67
+ help="Transport to use (default: stdio)"
68
+ )
69
+ serve_parser.add_argument("--host", type=str, default="0.0.0.0", help="Host for SSE/HTTP (default: 0.0.0.0)")
70
+ serve_parser.add_argument("--port", type=int, default=8000, help="Port for SSE/HTTP (default: 8000)")
71
+ serve_parser.add_argument("--debug", action="store_true", help="Enable debug logging")
72
+
73
+ subparsers.add_parser("version", help="Show version")
74
+ subparsers.add_parser("diagnostics", help="Run cluster diagnostics")
75
+
76
+ args = parser.parse_args()
77
+
78
+ if hasattr(args, 'debug') and args.debug:
79
+ logging.getLogger().setLevel(logging.DEBUG)
80
+ os.environ["MCP_DEBUG"] = "1"
81
+
82
+ try:
83
+ if args.command == "serve":
84
+ if args.transport == "stdio":
85
+ asyncio.run(serve_stdio())
86
+ elif args.transport == "sse":
87
+ asyncio.run(serve_sse(args.host, args.port))
88
+ elif args.transport in ("http", "streamable-http"):
89
+ asyncio.run(serve_http(args.host, args.port))
90
+ elif args.command == "version":
91
+ from .. import __version__
92
+ print(f"kubectl-mcp-tool version {__version__}")
93
+ elif args.command == "diagnostics":
94
+ from ..diagnostics import run_diagnostics
95
+ import json
96
+ results = run_diagnostics()
97
+ print(json.dumps(results, indent=2))
98
+ else:
99
+ parser.print_help()
100
+ except KeyboardInterrupt:
101
+ pass
102
+ except Exception as e:
103
+ logger.error(f"Error: {e}")
104
+ if hasattr(args, 'debug') and args.debug:
105
+ logger.error(traceback.format_exc())
106
+ return 1
107
+ return 0
108
+
109
+
110
+ if __name__ == "__main__":
111
+ sys.exit(main())