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.
- kubectl_mcp_server-1.12.0.dist-info/METADATA +711 -0
- kubectl_mcp_server-1.12.0.dist-info/RECORD +45 -0
- kubectl_mcp_server-1.12.0.dist-info/WHEEL +5 -0
- kubectl_mcp_server-1.12.0.dist-info/entry_points.txt +3 -0
- kubectl_mcp_server-1.12.0.dist-info/licenses/LICENSE +21 -0
- kubectl_mcp_server-1.12.0.dist-info/top_level.txt +2 -0
- kubectl_mcp_tool/__init__.py +21 -0
- kubectl_mcp_tool/__main__.py +46 -0
- kubectl_mcp_tool/auth/__init__.py +13 -0
- kubectl_mcp_tool/auth/config.py +71 -0
- kubectl_mcp_tool/auth/scopes.py +148 -0
- kubectl_mcp_tool/auth/verifier.py +82 -0
- kubectl_mcp_tool/cli/__init__.py +9 -0
- kubectl_mcp_tool/cli/__main__.py +10 -0
- kubectl_mcp_tool/cli/cli.py +111 -0
- kubectl_mcp_tool/diagnostics.py +355 -0
- kubectl_mcp_tool/k8s_config.py +289 -0
- kubectl_mcp_tool/mcp_server.py +530 -0
- kubectl_mcp_tool/prompts/__init__.py +5 -0
- kubectl_mcp_tool/prompts/prompts.py +823 -0
- kubectl_mcp_tool/resources/__init__.py +5 -0
- kubectl_mcp_tool/resources/resources.py +305 -0
- kubectl_mcp_tool/tools/__init__.py +28 -0
- kubectl_mcp_tool/tools/browser.py +371 -0
- kubectl_mcp_tool/tools/cluster.py +315 -0
- kubectl_mcp_tool/tools/core.py +421 -0
- kubectl_mcp_tool/tools/cost.py +680 -0
- kubectl_mcp_tool/tools/deployments.py +381 -0
- kubectl_mcp_tool/tools/diagnostics.py +174 -0
- kubectl_mcp_tool/tools/helm.py +1561 -0
- kubectl_mcp_tool/tools/networking.py +296 -0
- kubectl_mcp_tool/tools/operations.py +501 -0
- kubectl_mcp_tool/tools/pods.py +582 -0
- kubectl_mcp_tool/tools/security.py +333 -0
- kubectl_mcp_tool/tools/storage.py +133 -0
- kubectl_mcp_tool/utils/__init__.py +17 -0
- kubectl_mcp_tool/utils/helpers.py +80 -0
- tests/__init__.py +9 -0
- tests/conftest.py +379 -0
- tests/test_auth.py +256 -0
- tests/test_browser.py +349 -0
- tests/test_prompts.py +536 -0
- tests/test_resources.py +343 -0
- tests/test_server.py +384 -0
- tests/test_tools.py +659 -0
tests/test_server.py
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for MCP Server initialization and configuration.
|
|
3
|
+
|
|
4
|
+
This module tests:
|
|
5
|
+
- Server initialization
|
|
6
|
+
- Configuration options
|
|
7
|
+
- Transport methods
|
|
8
|
+
- Dependency checking
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
import asyncio
|
|
13
|
+
from unittest.mock import patch, MagicMock, AsyncMock
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestServerInitialization:
|
|
17
|
+
"""Tests for MCPServer initialization."""
|
|
18
|
+
|
|
19
|
+
@pytest.mark.unit
|
|
20
|
+
def test_server_creates_successfully(self):
|
|
21
|
+
"""Test that server creates successfully."""
|
|
22
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
23
|
+
|
|
24
|
+
with patch("kubectl_mcp_tool.mcp_server.MCPServer._check_dependencies", return_value=True):
|
|
25
|
+
with patch("kubernetes.config.load_kube_config"):
|
|
26
|
+
server = MCPServer(name="test-server")
|
|
27
|
+
|
|
28
|
+
assert server is not None
|
|
29
|
+
assert server.name == "test-server"
|
|
30
|
+
|
|
31
|
+
@pytest.mark.unit
|
|
32
|
+
def test_server_name_is_set(self):
|
|
33
|
+
"""Test that server name is properly set."""
|
|
34
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
35
|
+
|
|
36
|
+
with patch("kubectl_mcp_tool.mcp_server.MCPServer._check_dependencies", return_value=True):
|
|
37
|
+
with patch("kubernetes.config.load_kube_config"):
|
|
38
|
+
server = MCPServer(name="my-custom-server")
|
|
39
|
+
|
|
40
|
+
assert server.name == "my-custom-server"
|
|
41
|
+
|
|
42
|
+
@pytest.mark.unit
|
|
43
|
+
def test_non_destructive_mode_default(self):
|
|
44
|
+
"""Test that non-destructive mode is disabled by default."""
|
|
45
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
46
|
+
|
|
47
|
+
with patch("kubectl_mcp_tool.mcp_server.MCPServer._check_dependencies", return_value=True):
|
|
48
|
+
with patch("kubernetes.config.load_kube_config"):
|
|
49
|
+
server = MCPServer(name="test")
|
|
50
|
+
|
|
51
|
+
assert server.non_destructive is False
|
|
52
|
+
|
|
53
|
+
@pytest.mark.unit
|
|
54
|
+
def test_non_destructive_mode_enabled(self):
|
|
55
|
+
"""Test that non_destructive mode can be enabled."""
|
|
56
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
57
|
+
|
|
58
|
+
with patch("kubectl_mcp_tool.mcp_server.MCPServer._check_dependencies", return_value=True):
|
|
59
|
+
with patch("kubernetes.config.load_kube_config"):
|
|
60
|
+
server = MCPServer(name="test", non_destructive=True)
|
|
61
|
+
|
|
62
|
+
assert server.non_destructive is True
|
|
63
|
+
|
|
64
|
+
@pytest.mark.unit
|
|
65
|
+
def test_fastmcp_server_instance(self):
|
|
66
|
+
"""Test that FastMCP server instance is created."""
|
|
67
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
68
|
+
|
|
69
|
+
with patch("kubectl_mcp_tool.mcp_server.MCPServer._check_dependencies", return_value=True):
|
|
70
|
+
with patch("kubernetes.config.load_kube_config"):
|
|
71
|
+
server = MCPServer(name="test")
|
|
72
|
+
|
|
73
|
+
assert hasattr(server, 'server')
|
|
74
|
+
assert server.server is not None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TestToolRegistration:
|
|
78
|
+
"""Tests for tool registration."""
|
|
79
|
+
|
|
80
|
+
@pytest.mark.unit
|
|
81
|
+
def test_tools_are_registered(self):
|
|
82
|
+
"""Test that tools are registered during initialization."""
|
|
83
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
84
|
+
|
|
85
|
+
with patch("kubectl_mcp_tool.mcp_server.MCPServer._check_dependencies", return_value=True):
|
|
86
|
+
with patch("kubernetes.config.load_kube_config"):
|
|
87
|
+
server = MCPServer(name="test")
|
|
88
|
+
|
|
89
|
+
# Server should have tools
|
|
90
|
+
assert hasattr(server, 'server')
|
|
91
|
+
|
|
92
|
+
@pytest.mark.unit
|
|
93
|
+
def test_setup_tools_called(self):
|
|
94
|
+
"""Test that setup_tools is called during initialization."""
|
|
95
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
96
|
+
|
|
97
|
+
with patch("kubectl_mcp_tool.mcp_server.MCPServer._check_dependencies", return_value=True):
|
|
98
|
+
with patch("kubernetes.config.load_kube_config"):
|
|
99
|
+
with patch.object(MCPServer, 'setup_tools') as mock_setup:
|
|
100
|
+
# Create server - setup_tools is called in __init__
|
|
101
|
+
# We can't easily patch it before __init__, so just verify the method exists
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
# Verify setup_tools method exists
|
|
105
|
+
assert hasattr(MCPServer, 'setup_tools')
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class TestResourceRegistration:
|
|
109
|
+
"""Tests for resource registration."""
|
|
110
|
+
|
|
111
|
+
@pytest.mark.unit
|
|
112
|
+
def test_resources_are_registered(self):
|
|
113
|
+
"""Test that resources are registered during initialization."""
|
|
114
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
115
|
+
|
|
116
|
+
with patch("kubectl_mcp_tool.mcp_server.MCPServer._check_dependencies", return_value=True):
|
|
117
|
+
with patch("kubernetes.config.load_kube_config"):
|
|
118
|
+
server = MCPServer(name="test")
|
|
119
|
+
|
|
120
|
+
assert hasattr(server, 'server')
|
|
121
|
+
|
|
122
|
+
@pytest.mark.unit
|
|
123
|
+
def test_setup_resources_method_exists(self):
|
|
124
|
+
"""Test that setup_resources method exists."""
|
|
125
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
126
|
+
|
|
127
|
+
assert hasattr(MCPServer, 'setup_resources')
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class TestPromptRegistration:
|
|
131
|
+
"""Tests for prompt registration."""
|
|
132
|
+
|
|
133
|
+
@pytest.mark.unit
|
|
134
|
+
def test_prompts_are_registered(self):
|
|
135
|
+
"""Test that prompts are registered during initialization."""
|
|
136
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
137
|
+
|
|
138
|
+
with patch("kubectl_mcp_tool.mcp_server.MCPServer._check_dependencies", return_value=True):
|
|
139
|
+
with patch("kubernetes.config.load_kube_config"):
|
|
140
|
+
server = MCPServer(name="test")
|
|
141
|
+
|
|
142
|
+
assert hasattr(server, 'server')
|
|
143
|
+
|
|
144
|
+
@pytest.mark.unit
|
|
145
|
+
def test_setup_prompts_method_exists(self):
|
|
146
|
+
"""Test that setup_prompts method exists."""
|
|
147
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
148
|
+
|
|
149
|
+
assert hasattr(MCPServer, 'setup_prompts')
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class TestDependencyChecking:
|
|
153
|
+
"""Tests for dependency checking."""
|
|
154
|
+
|
|
155
|
+
@pytest.mark.unit
|
|
156
|
+
def test_dependencies_checked_lazily(self):
|
|
157
|
+
"""Test that dependencies are checked lazily."""
|
|
158
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
159
|
+
|
|
160
|
+
with patch("kubectl_mcp_tool.mcp_server.MCPServer._check_dependencies", return_value=True):
|
|
161
|
+
with patch("kubernetes.config.load_kube_config"):
|
|
162
|
+
server = MCPServer(name="test")
|
|
163
|
+
|
|
164
|
+
# Dependencies should not be checked until accessed
|
|
165
|
+
assert server._dependencies_checked is False
|
|
166
|
+
|
|
167
|
+
@pytest.mark.unit
|
|
168
|
+
def test_dependencies_checked_on_access(self):
|
|
169
|
+
"""Test that dependencies are checked on first access."""
|
|
170
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
171
|
+
|
|
172
|
+
with patch("kubectl_mcp_tool.mcp_server.MCPServer._check_dependencies", return_value=True) as mock_check:
|
|
173
|
+
with patch("kubernetes.config.load_kube_config"):
|
|
174
|
+
server = MCPServer(name="test")
|
|
175
|
+
# Access the property
|
|
176
|
+
_ = server.dependencies_available
|
|
177
|
+
|
|
178
|
+
assert server._dependencies_checked is True
|
|
179
|
+
|
|
180
|
+
@pytest.mark.unit
|
|
181
|
+
def test_check_tool_availability(self):
|
|
182
|
+
"""Test tool availability checking."""
|
|
183
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
184
|
+
|
|
185
|
+
with patch("kubectl_mcp_tool.mcp_server.MCPServer._check_dependencies", return_value=True):
|
|
186
|
+
with patch("kubernetes.config.load_kube_config"):
|
|
187
|
+
server = MCPServer(name="test")
|
|
188
|
+
|
|
189
|
+
with patch("shutil.which", return_value="/usr/bin/kubectl"):
|
|
190
|
+
with patch("subprocess.check_output", return_value=b'{"clientVersion": {}}'):
|
|
191
|
+
result = server._check_tool_availability("kubectl")
|
|
192
|
+
assert result is True
|
|
193
|
+
|
|
194
|
+
@pytest.mark.unit
|
|
195
|
+
def test_check_tool_not_available(self):
|
|
196
|
+
"""Test tool availability when tool is not found."""
|
|
197
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
198
|
+
|
|
199
|
+
with patch("kubectl_mcp_tool.mcp_server.MCPServer._check_dependencies", return_value=True):
|
|
200
|
+
with patch("kubernetes.config.load_kube_config"):
|
|
201
|
+
server = MCPServer(name="test")
|
|
202
|
+
|
|
203
|
+
with patch("shutil.which", return_value=None):
|
|
204
|
+
result = server._check_tool_availability("nonexistent-tool")
|
|
205
|
+
assert result is False
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class TestNonDestructiveMode:
|
|
209
|
+
"""Tests for non-destructive mode."""
|
|
210
|
+
|
|
211
|
+
@pytest.mark.unit
|
|
212
|
+
def test_check_destructive_returns_none_when_allowed(self):
|
|
213
|
+
"""Test that _check_destructive returns None when destructive ops are allowed."""
|
|
214
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
215
|
+
|
|
216
|
+
with patch("kubectl_mcp_tool.mcp_server.MCPServer._check_dependencies", return_value=True):
|
|
217
|
+
with patch("kubernetes.config.load_kube_config"):
|
|
218
|
+
server = MCPServer(name="test", non_destructive=False)
|
|
219
|
+
|
|
220
|
+
result = server._check_destructive()
|
|
221
|
+
assert result is None
|
|
222
|
+
|
|
223
|
+
@pytest.mark.unit
|
|
224
|
+
def test_check_destructive_returns_error_when_blocked(self):
|
|
225
|
+
"""Test that _check_destructive returns error when destructive ops are blocked."""
|
|
226
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
227
|
+
|
|
228
|
+
with patch("kubectl_mcp_tool.mcp_server.MCPServer._check_dependencies", return_value=True):
|
|
229
|
+
with patch("kubernetes.config.load_kube_config"):
|
|
230
|
+
server = MCPServer(name="test", non_destructive=True)
|
|
231
|
+
|
|
232
|
+
result = server._check_destructive()
|
|
233
|
+
assert result is not None
|
|
234
|
+
assert result["success"] is False
|
|
235
|
+
assert "non-destructive mode" in result["error"]
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class TestSecretMasking:
|
|
239
|
+
"""Tests for secret masking."""
|
|
240
|
+
|
|
241
|
+
@pytest.mark.unit
|
|
242
|
+
def test_mask_secrets_method_exists(self):
|
|
243
|
+
"""Test that _mask_secrets method exists."""
|
|
244
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
245
|
+
|
|
246
|
+
assert hasattr(MCPServer, '_mask_secrets')
|
|
247
|
+
|
|
248
|
+
@pytest.mark.unit
|
|
249
|
+
def test_masks_base64_secrets(self):
|
|
250
|
+
"""Test that base64-encoded secrets are masked."""
|
|
251
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
252
|
+
|
|
253
|
+
with patch("kubectl_mcp_tool.mcp_server.MCPServer._check_dependencies", return_value=True):
|
|
254
|
+
with patch("kubernetes.config.load_kube_config"):
|
|
255
|
+
server = MCPServer(name="test")
|
|
256
|
+
|
|
257
|
+
text = """
|
|
258
|
+
data:
|
|
259
|
+
password: c2VjcmV0UGFzc3dvcmQxMjM0NTY3ODkw
|
|
260
|
+
"""
|
|
261
|
+
masked = server._mask_secrets(text)
|
|
262
|
+
assert "[MASKED]" in masked
|
|
263
|
+
|
|
264
|
+
@pytest.mark.unit
|
|
265
|
+
def test_masks_password_fields(self):
|
|
266
|
+
"""Test that password fields are masked."""
|
|
267
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
268
|
+
|
|
269
|
+
with patch("kubectl_mcp_tool.mcp_server.MCPServer._check_dependencies", return_value=True):
|
|
270
|
+
with patch("kubernetes.config.load_kube_config"):
|
|
271
|
+
server = MCPServer(name="test")
|
|
272
|
+
|
|
273
|
+
text = 'password: "supersecretpassword"'
|
|
274
|
+
masked = server._mask_secrets(text)
|
|
275
|
+
assert "[MASKED]" in masked
|
|
276
|
+
|
|
277
|
+
@pytest.mark.unit
|
|
278
|
+
def test_masks_token_fields(self):
|
|
279
|
+
"""Test that token fields are masked."""
|
|
280
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
281
|
+
|
|
282
|
+
with patch("kubectl_mcp_tool.mcp_server.MCPServer._check_dependencies", return_value=True):
|
|
283
|
+
with patch("kubernetes.config.load_kube_config"):
|
|
284
|
+
server = MCPServer(name="test")
|
|
285
|
+
|
|
286
|
+
text = 'token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.test"'
|
|
287
|
+
masked = server._mask_secrets(text)
|
|
288
|
+
assert "[MASKED]" in masked
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class TestTransportMethods:
|
|
292
|
+
"""Tests for transport methods."""
|
|
293
|
+
|
|
294
|
+
@pytest.mark.unit
|
|
295
|
+
def test_serve_stdio_method_exists(self):
|
|
296
|
+
"""Test that serve_stdio method exists."""
|
|
297
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
298
|
+
|
|
299
|
+
with patch("kubectl_mcp_tool.mcp_server.MCPServer._check_dependencies", return_value=True):
|
|
300
|
+
with patch("kubernetes.config.load_kube_config"):
|
|
301
|
+
server = MCPServer(name="test")
|
|
302
|
+
|
|
303
|
+
assert hasattr(server, 'serve_stdio')
|
|
304
|
+
|
|
305
|
+
@pytest.mark.unit
|
|
306
|
+
def test_serve_sse_method_exists(self):
|
|
307
|
+
"""Test that serve_sse method exists."""
|
|
308
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
309
|
+
|
|
310
|
+
with patch("kubectl_mcp_tool.mcp_server.MCPServer._check_dependencies", return_value=True):
|
|
311
|
+
with patch("kubernetes.config.load_kube_config"):
|
|
312
|
+
server = MCPServer(name="test")
|
|
313
|
+
|
|
314
|
+
assert hasattr(server, 'serve_sse')
|
|
315
|
+
|
|
316
|
+
@pytest.mark.unit
|
|
317
|
+
def test_serve_http_method_exists(self):
|
|
318
|
+
"""Test that serve_http method exists."""
|
|
319
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
320
|
+
|
|
321
|
+
with patch("kubectl_mcp_tool.mcp_server.MCPServer._check_dependencies", return_value=True):
|
|
322
|
+
with patch("kubernetes.config.load_kube_config"):
|
|
323
|
+
server = MCPServer(name="test")
|
|
324
|
+
|
|
325
|
+
assert hasattr(server, 'serve_http')
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class TestServerConfiguration:
|
|
329
|
+
"""Tests for server configuration."""
|
|
330
|
+
|
|
331
|
+
@pytest.mark.unit
|
|
332
|
+
def test_server_with_default_config(self):
|
|
333
|
+
"""Test server with default configuration."""
|
|
334
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
335
|
+
|
|
336
|
+
with patch("kubectl_mcp_tool.mcp_server.MCPServer._check_dependencies", return_value=True):
|
|
337
|
+
with patch("kubernetes.config.load_kube_config"):
|
|
338
|
+
server = MCPServer(name="default-server")
|
|
339
|
+
|
|
340
|
+
assert server.name == "default-server"
|
|
341
|
+
assert server.non_destructive is False
|
|
342
|
+
|
|
343
|
+
@pytest.mark.unit
|
|
344
|
+
def test_server_with_custom_config(self):
|
|
345
|
+
"""Test server with custom configuration."""
|
|
346
|
+
from kubectl_mcp_tool.mcp_server import MCPServer
|
|
347
|
+
|
|
348
|
+
with patch("kubectl_mcp_tool.mcp_server.MCPServer._check_dependencies", return_value=True):
|
|
349
|
+
with patch("kubernetes.config.load_kube_config"):
|
|
350
|
+
server = MCPServer(
|
|
351
|
+
name="custom-server",
|
|
352
|
+
non_destructive=True
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
assert server.name == "custom-server"
|
|
356
|
+
assert server.non_destructive is True
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
class TestModuleExports:
|
|
360
|
+
"""Tests for module exports."""
|
|
361
|
+
|
|
362
|
+
@pytest.mark.unit
|
|
363
|
+
def test_mcpserver_exported(self):
|
|
364
|
+
"""Test that MCPServer is exported from module."""
|
|
365
|
+
from kubectl_mcp_tool import MCPServer
|
|
366
|
+
|
|
367
|
+
assert MCPServer is not None
|
|
368
|
+
|
|
369
|
+
@pytest.mark.unit
|
|
370
|
+
def test_version_exported(self):
|
|
371
|
+
"""Test that __version__ is exported from module."""
|
|
372
|
+
from kubectl_mcp_tool import __version__
|
|
373
|
+
|
|
374
|
+
assert __version__ is not None
|
|
375
|
+
assert isinstance(__version__, str)
|
|
376
|
+
|
|
377
|
+
@pytest.mark.unit
|
|
378
|
+
def test_diagnostics_exported(self):
|
|
379
|
+
"""Test that diagnostics functions are exported."""
|
|
380
|
+
from kubectl_mcp_tool import run_diagnostics, check_kubectl_installation, check_cluster_connection
|
|
381
|
+
|
|
382
|
+
assert run_diagnostics is not None
|
|
383
|
+
assert check_kubectl_installation is not None
|
|
384
|
+
assert check_cluster_connection is not None
|