awslabs.eks-mcp-server 0.1.1__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.
- awslabs/__init__.py +13 -0
- awslabs/eks_mcp_server/__init__.py +14 -0
- awslabs/eks_mcp_server/aws_helper.py +71 -0
- awslabs/eks_mcp_server/cloudwatch_handler.py +670 -0
- awslabs/eks_mcp_server/consts.py +33 -0
- awslabs/eks_mcp_server/eks_kb_handler.py +86 -0
- awslabs/eks_mcp_server/eks_stack_handler.py +661 -0
- awslabs/eks_mcp_server/iam_handler.py +359 -0
- awslabs/eks_mcp_server/k8s_apis.py +506 -0
- awslabs/eks_mcp_server/k8s_client_cache.py +164 -0
- awslabs/eks_mcp_server/k8s_handler.py +1151 -0
- awslabs/eks_mcp_server/logging_helper.py +52 -0
- awslabs/eks_mcp_server/models.py +271 -0
- awslabs/eks_mcp_server/server.py +151 -0
- awslabs/eks_mcp_server/templates/eks-templates/eks-with-vpc.yaml +454 -0
- awslabs/eks_mcp_server/templates/k8s-templates/deployment.yaml +49 -0
- awslabs/eks_mcp_server/templates/k8s-templates/service.yaml +18 -0
- awslabs_eks_mcp_server-0.1.1.dist-info/METADATA +596 -0
- awslabs_eks_mcp_server-0.1.1.dist-info/RECORD +23 -0
- awslabs_eks_mcp_server-0.1.1.dist-info/WHEEL +4 -0
- awslabs_eks_mcp_server-0.1.1.dist-info/entry_points.txt +2 -0
- awslabs_eks_mcp_server-0.1.1.dist-info/licenses/LICENSE +175 -0
- awslabs_eks_mcp_server-0.1.1.dist-info/licenses/NOTICE +2 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
|
|
4
|
+
# with the License. A copy of the License is located at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
|
|
9
|
+
# OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
|
|
10
|
+
# and limitations under the License.
|
|
11
|
+
|
|
12
|
+
"""Logging helper for the EKS MCP Server."""
|
|
13
|
+
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from loguru import logger
|
|
16
|
+
from mcp.server.fastmcp import Context
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class LogLevel(Enum):
|
|
21
|
+
"""Enum for log levels."""
|
|
22
|
+
|
|
23
|
+
DEBUG = 'debug'
|
|
24
|
+
INFO = 'info'
|
|
25
|
+
WARNING = 'warning'
|
|
26
|
+
ERROR = 'error'
|
|
27
|
+
CRITICAL = 'critical'
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def log_with_request_id(ctx: Context, level: LogLevel, message: str, **kwargs: Any) -> None:
|
|
31
|
+
"""Log a message with the request ID from the context.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
ctx: The MCP context containing the request ID
|
|
35
|
+
level: The log level (from LogLevel enum)
|
|
36
|
+
message: The message to log
|
|
37
|
+
**kwargs: Additional fields to include in the log message
|
|
38
|
+
"""
|
|
39
|
+
# Format the log message with request_id
|
|
40
|
+
log_message = f'[request_id={ctx.request_id}] {message}'
|
|
41
|
+
|
|
42
|
+
# Log at the appropriate level
|
|
43
|
+
if level == LogLevel.DEBUG:
|
|
44
|
+
logger.debug(log_message, **kwargs)
|
|
45
|
+
elif level == LogLevel.INFO:
|
|
46
|
+
logger.info(log_message, **kwargs)
|
|
47
|
+
elif level == LogLevel.WARNING:
|
|
48
|
+
logger.warning(log_message, **kwargs)
|
|
49
|
+
elif level == LogLevel.ERROR:
|
|
50
|
+
logger.error(log_message, **kwargs)
|
|
51
|
+
elif level == LogLevel.CRITICAL:
|
|
52
|
+
logger.critical(log_message, **kwargs)
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
|
|
4
|
+
# with the License. A copy of the License is located at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
|
|
9
|
+
# OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
|
|
10
|
+
# and limitations under the License.
|
|
11
|
+
|
|
12
|
+
"""Data models for the EKS MCP Server."""
|
|
13
|
+
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from mcp.types import CallToolResult
|
|
16
|
+
from pydantic import BaseModel, Field
|
|
17
|
+
from typing import Any, Dict, List, Optional, Union
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class EventItem(BaseModel):
|
|
21
|
+
"""Summary of a Kubernetes event.
|
|
22
|
+
|
|
23
|
+
This model represents a Kubernetes event with timestamps, message, and metadata.
|
|
24
|
+
Events provide information about state changes and important occurrences in the cluster.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
first_timestamp: Optional[str] = Field(
|
|
28
|
+
None, description='First timestamp of the event in ISO format'
|
|
29
|
+
)
|
|
30
|
+
last_timestamp: Optional[str] = Field(
|
|
31
|
+
None, description='Last timestamp of the event in ISO format'
|
|
32
|
+
)
|
|
33
|
+
count: Optional[int] = Field(None, description='Count of occurrences', ge=0)
|
|
34
|
+
message: str = Field(..., description='Event message describing what happened')
|
|
35
|
+
reason: Optional[str] = Field(
|
|
36
|
+
None, description='Short, machine-understandable reason for the event'
|
|
37
|
+
)
|
|
38
|
+
reporting_component: Optional[str] = Field(
|
|
39
|
+
None, description='Component that reported the event (e.g., kubelet, controller-manager)'
|
|
40
|
+
)
|
|
41
|
+
type: Optional[str] = Field(None, description='Event type (Normal, Warning)')
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Operation(str, Enum):
|
|
45
|
+
"""Kubernetes resource operations for single resources."""
|
|
46
|
+
|
|
47
|
+
CREATE = 'create'
|
|
48
|
+
REPLACE = 'replace'
|
|
49
|
+
PATCH = 'patch'
|
|
50
|
+
DELETE = 'delete'
|
|
51
|
+
READ = 'read'
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ApplyYamlResponse(CallToolResult):
|
|
55
|
+
"""Response model for apply_yaml tool."""
|
|
56
|
+
|
|
57
|
+
force_applied: bool = Field(
|
|
58
|
+
False, description='Whether force option was used to update existing resources'
|
|
59
|
+
)
|
|
60
|
+
resources_created: int = Field(0, description='Number of resources created')
|
|
61
|
+
resources_updated: int = Field(0, description='Number of resources updated (when force=True)')
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class KubernetesResourceResponse(CallToolResult):
|
|
65
|
+
"""Response model for single Kubernetes resource operations."""
|
|
66
|
+
|
|
67
|
+
kind: str = Field(..., description='Kind of the Kubernetes resource')
|
|
68
|
+
name: str = Field(..., description='Name of the Kubernetes resource')
|
|
69
|
+
namespace: Optional[str] = Field(None, description='Namespace of the Kubernetes resource')
|
|
70
|
+
api_version: str = Field(..., description='API version of the Kubernetes resource')
|
|
71
|
+
operation: str = Field(
|
|
72
|
+
..., description='Operation performed (create, replace, patch, delete, read)'
|
|
73
|
+
)
|
|
74
|
+
resource: Optional[Dict[str, Any]] = Field(
|
|
75
|
+
None, description='Resource data (for read operation)'
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class ResourceSummary(BaseModel):
|
|
80
|
+
"""Summary of a Kubernetes resource."""
|
|
81
|
+
|
|
82
|
+
name: str = Field(..., description='Name of the resource')
|
|
83
|
+
namespace: Optional[str] = Field(None, description='Namespace of the resource')
|
|
84
|
+
creation_timestamp: Optional[str] = Field(None, description='Creation timestamp')
|
|
85
|
+
labels: Optional[Dict[str, str]] = Field(None, description='Resource labels')
|
|
86
|
+
annotations: Optional[Dict[str, str]] = Field(None, description='Resource annotations')
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class KubernetesResourceListResponse(CallToolResult):
|
|
90
|
+
"""Response model for list_resources tool."""
|
|
91
|
+
|
|
92
|
+
kind: str = Field(..., description='Kind of the Kubernetes resources')
|
|
93
|
+
api_version: str = Field(..., description='API version of the Kubernetes resources')
|
|
94
|
+
namespace: Optional[str] = Field(None, description='Namespace of the Kubernetes resources')
|
|
95
|
+
count: int = Field(..., description='Number of resources found')
|
|
96
|
+
items: List[ResourceSummary] = Field(..., description='List of resources')
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class ApiVersionsResponse(CallToolResult):
|
|
100
|
+
"""Response model for list_api_versions tool."""
|
|
101
|
+
|
|
102
|
+
cluster_name: str = Field(..., description='Name of the EKS cluster')
|
|
103
|
+
api_versions: List[str] = Field(..., description='List of available API versions')
|
|
104
|
+
count: int = Field(..., description='Number of API versions')
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class GenerateAppManifestResponse(CallToolResult):
|
|
108
|
+
"""Response model for generate_app_manifest tool."""
|
|
109
|
+
|
|
110
|
+
output_file_path: str = Field(..., description='Path to the output manifest file')
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class PodLogsResponse(CallToolResult):
|
|
114
|
+
"""Response model for get_pod_logs tool."""
|
|
115
|
+
|
|
116
|
+
pod_name: str = Field(..., description='Name of the pod')
|
|
117
|
+
namespace: str = Field(..., description='Namespace of the pod')
|
|
118
|
+
container_name: Optional[str] = Field(None, description='Container name (if specified)')
|
|
119
|
+
log_lines: List[str] = Field(..., description='Pod log lines')
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class EventsResponse(CallToolResult):
|
|
123
|
+
"""Response model for get_k8s_events tool."""
|
|
124
|
+
|
|
125
|
+
involved_object_kind: str = Field(..., description='Kind of the involved object')
|
|
126
|
+
involved_object_name: str = Field(..., description='Name of the involved object')
|
|
127
|
+
involved_object_namespace: Optional[str] = Field(
|
|
128
|
+
None, description='Namespace of the involved object'
|
|
129
|
+
)
|
|
130
|
+
count: int = Field(..., description='Number of events found')
|
|
131
|
+
events: List[EventItem] = Field(..., description='List of events')
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class CloudWatchLogEntry(BaseModel):
|
|
135
|
+
"""Model for a CloudWatch log entry.
|
|
136
|
+
|
|
137
|
+
This model represents a single log entry from CloudWatch logs,
|
|
138
|
+
containing a timestamp and the log message.
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
timestamp: str = Field(..., description='Timestamp of the log entry in ISO format')
|
|
142
|
+
message: str = Field(..., description='Log message content')
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class CloudWatchLogsResponse(CallToolResult):
|
|
146
|
+
"""Response model for get_cloudwatch_logs tool.
|
|
147
|
+
|
|
148
|
+
This model contains the response from a CloudWatch logs query,
|
|
149
|
+
including resource information, time range, and log entries.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
resource_type: str = Field(..., description='Resource type (pod, node, container)')
|
|
153
|
+
resource_name: str = Field(..., description='Resource name')
|
|
154
|
+
cluster_name: str = Field(..., description='Name of the EKS cluster')
|
|
155
|
+
log_type: str = Field(
|
|
156
|
+
..., description='Log type (application, host, performance, control-plane, or custom)'
|
|
157
|
+
)
|
|
158
|
+
log_group: str = Field(..., description='CloudWatch log group name')
|
|
159
|
+
start_time: str = Field(..., description='Start time in ISO format')
|
|
160
|
+
end_time: str = Field(..., description='End time in ISO format')
|
|
161
|
+
log_entries: List[Dict[str, Any]] = Field(
|
|
162
|
+
..., description='Log entries with timestamps and messages'
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class CloudWatchDataPoint(BaseModel):
|
|
167
|
+
"""Model for a CloudWatch metric data point.
|
|
168
|
+
|
|
169
|
+
This model represents a single data point from CloudWatch metrics,
|
|
170
|
+
containing a timestamp and the corresponding metric value.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
timestamp: str = Field(..., description='Timestamp of the data point in ISO format')
|
|
174
|
+
value: float = Field(..., description='Metric value')
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class CloudWatchMetricsResponse(CallToolResult):
|
|
178
|
+
"""Response model for get_cloudwatch_metrics tool.
|
|
179
|
+
|
|
180
|
+
This model contains the response from a CloudWatch metrics query,
|
|
181
|
+
including resource information, metric details, time range, and data points.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
resource_type: str = Field(..., description='Resource type (pod, node, container, cluster)')
|
|
185
|
+
resource_name: str = Field(..., description='Resource name')
|
|
186
|
+
cluster_name: str = Field(..., description='Name of the EKS cluster')
|
|
187
|
+
metric_name: str = Field(..., description='Metric name (e.g., cpu_usage_total, memory_rss)')
|
|
188
|
+
namespace: str = Field(..., description='CloudWatch namespace (e.g., ContainerInsights)')
|
|
189
|
+
start_time: str = Field(..., description='Start time in ISO format')
|
|
190
|
+
end_time: str = Field(..., description='End time in ISO format')
|
|
191
|
+
data_points: List[Dict[str, Any]] = Field(
|
|
192
|
+
..., description='Metric data points with timestamps and values'
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class StackSummary(BaseModel):
|
|
197
|
+
"""Summary of a CloudFormation stack."""
|
|
198
|
+
|
|
199
|
+
stack_name: str = Field(..., description='Name of the CloudFormation stack')
|
|
200
|
+
stack_id: str = Field(..., description='ID of the CloudFormation stack')
|
|
201
|
+
cluster_name: str = Field(..., description='Name of the EKS cluster')
|
|
202
|
+
creation_time: str = Field(..., description='Creation time of the stack')
|
|
203
|
+
stack_status: str = Field(..., description='Current status of the stack')
|
|
204
|
+
description: Optional[str] = Field(None, description='Description of the stack')
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class GenerateTemplateResponse(CallToolResult):
|
|
208
|
+
"""Response model for generate operation of manage_eks_stacks tool."""
|
|
209
|
+
|
|
210
|
+
template_path: str = Field(..., description='Path to the generated template')
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class DeployStackResponse(CallToolResult):
|
|
214
|
+
"""Response model for deploy operation of manage_eks_stacks tool."""
|
|
215
|
+
|
|
216
|
+
stack_name: str = Field(..., description='Name of the CloudFormation stack')
|
|
217
|
+
stack_arn: str = Field(..., description='ARN of the CloudFormation stack')
|
|
218
|
+
cluster_name: str = Field(..., description='Name of the EKS cluster')
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class DescribeStackResponse(CallToolResult):
|
|
222
|
+
"""Response model for describe operation of manage_eks_stacks tool."""
|
|
223
|
+
|
|
224
|
+
stack_name: str = Field(..., description='Name of the CloudFormation stack')
|
|
225
|
+
stack_id: str = Field(..., description='ID of the CloudFormation stack')
|
|
226
|
+
cluster_name: str = Field(..., description='Name of the EKS cluster')
|
|
227
|
+
creation_time: str = Field(..., description='Creation time of the stack')
|
|
228
|
+
stack_status: str = Field(..., description='Current status of the stack')
|
|
229
|
+
outputs: Dict[str, str] = Field(..., description='Stack outputs')
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class DeleteStackResponse(CallToolResult):
|
|
233
|
+
"""Response model for delete operation of manage_eks_stacks tool."""
|
|
234
|
+
|
|
235
|
+
stack_name: str = Field(..., description='Name of the deleted CloudFormation stack')
|
|
236
|
+
stack_id: str = Field(..., description='ID of the deleted CloudFormation stack')
|
|
237
|
+
cluster_name: str = Field(..., description='Name of the EKS cluster')
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class PolicySummary(BaseModel):
|
|
241
|
+
"""Summary of an IAM policy."""
|
|
242
|
+
|
|
243
|
+
policy_type: str = Field(..., description='Type of the policy (Managed or Inline)')
|
|
244
|
+
description: Optional[str] = Field(None, description='Description of the policy')
|
|
245
|
+
policy_document: Optional[Dict[str, Any]] = Field(None, description='Policy document')
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class RoleDescriptionResponse(CallToolResult):
|
|
249
|
+
"""Response model for get_policies_for_role tool."""
|
|
250
|
+
|
|
251
|
+
role_arn: str = Field(..., description='ARN of the IAM role')
|
|
252
|
+
assume_role_policy_document: Dict[str, Any] = Field(
|
|
253
|
+
..., description='Assume role policy document'
|
|
254
|
+
)
|
|
255
|
+
description: Optional[str] = Field(None, description='Description of the IAM role')
|
|
256
|
+
managed_policies: List[PolicySummary] = Field(
|
|
257
|
+
..., description='Managed policies attached to the IAM role'
|
|
258
|
+
)
|
|
259
|
+
inline_policies: List[PolicySummary] = Field(
|
|
260
|
+
..., description='Inline policies embedded in the IAM role'
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class AddInlinePolicyResponse(CallToolResult):
|
|
265
|
+
"""Response model for add_inline_policy tool."""
|
|
266
|
+
|
|
267
|
+
policy_name: str = Field(..., description='Name of the inline policy to create')
|
|
268
|
+
role_name: str = Field(..., description='Name of the role to add the policy to')
|
|
269
|
+
permissions_added: Union[Dict[str, Any], List[Dict[str, Any]]] = Field(
|
|
270
|
+
..., description='Permissions to include in the policy (in JSON format)'
|
|
271
|
+
)
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
|
|
4
|
+
# with the License. A copy of the License is located at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
|
|
9
|
+
# OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
|
|
10
|
+
# and limitations under the License.
|
|
11
|
+
|
|
12
|
+
"""awslabs EKS MCP Server implementation.
|
|
13
|
+
|
|
14
|
+
This module implements the EKS MCP Server, which provides tools for managing Amazon EKS clusters
|
|
15
|
+
and Kubernetes resources through the Model Context Protocol (MCP).
|
|
16
|
+
|
|
17
|
+
Environment Variables:
|
|
18
|
+
AWS_REGION: AWS region to use for AWS API calls
|
|
19
|
+
AWS_PROFILE: AWS profile to use for credentials
|
|
20
|
+
FASTMCP_LOG_LEVEL: Log level (default: WARNING)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
from awslabs.eks_mcp_server.cloudwatch_handler import CloudWatchHandler
|
|
25
|
+
from awslabs.eks_mcp_server.eks_kb_handler import EKSKnowledgeBaseHandler
|
|
26
|
+
from awslabs.eks_mcp_server.eks_stack_handler import EksStackHandler
|
|
27
|
+
from awslabs.eks_mcp_server.iam_handler import IAMHandler
|
|
28
|
+
from awslabs.eks_mcp_server.k8s_handler import K8sHandler
|
|
29
|
+
from loguru import logger
|
|
30
|
+
from mcp.server.fastmcp import FastMCP
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Define server instructions and dependencies
|
|
34
|
+
SERVER_INSTRUCTIONS = """
|
|
35
|
+
# Amazon EKS MCP Server
|
|
36
|
+
|
|
37
|
+
This MCP server provides tools for managing Amazon EKS clusters and is the preferred mechanism for creating new EKS clusters.
|
|
38
|
+
|
|
39
|
+
## Usage Notes
|
|
40
|
+
|
|
41
|
+
- By default, the server runs in read-only mode. Use the `--allow-write` flag to enable write operations.
|
|
42
|
+
- Access to sensitive data (logs, events, Kubernetes Secrets) requires the `--allow-sensitive-data-access` flag.
|
|
43
|
+
- For safety reasons, CloudFormation stacks can only be modified by the tool that created them.
|
|
44
|
+
- When creating or updating resources, always check for existing resources first to avoid conflicts.
|
|
45
|
+
- Use the `list_api_versions` tool to find the correct apiVersion for Kubernetes resources.
|
|
46
|
+
|
|
47
|
+
## Common Workflows
|
|
48
|
+
|
|
49
|
+
### Creating and Deploying an Application
|
|
50
|
+
1. Generate a CloudFormation template: `manage_eks_stacks(operation='generate', template_file='/path/to/template.yaml', cluster_name='my-cluster')`
|
|
51
|
+
2. Deploy the CloudFormation stack: `manage_eks_stacks(operation='deploy', template_file='/path/to/template.yaml', cluster_name='my-cluster')`
|
|
52
|
+
3. Generate an application manifest: `generate_app_manifest(app_name='my-app', image_uri='123456789012.dkr.ecr.us-east-1.amazonaws.com/my-repo:latest')`
|
|
53
|
+
4. Apply the manifest: `apply_yaml(yaml_path='/path/to/manifest.yaml', cluster_name='my-cluster', namespace='default')`
|
|
54
|
+
5. Monitor the application: `get_pod_logs(cluster_name='my-cluster', namespace='default', pod_name='my-app-pod')`
|
|
55
|
+
|
|
56
|
+
### Troubleshooting Application Issues
|
|
57
|
+
1. Check pod status: `list_k8s_resources(cluster_name='my-cluster', kind='Pod', api_version='v1', namespace='default', field_selector='metadata.name=my-pod')`
|
|
58
|
+
2. Get pod events: `get_k8s_events(cluster_name='my-cluster', kind='Pod', name='my-pod', namespace='default')`
|
|
59
|
+
3. Check pod logs: `get_pod_logs(cluster_name='my-cluster', namespace='default', pod_name='my-pod')`
|
|
60
|
+
4. Monitor metrics: `get_cloudwatch_metrics(resource_type='pod', resource_name='my-pod', cluster_name='my-cluster', metric_name='cpu_usage_total', namespace='ContainerInsights')`
|
|
61
|
+
5. Search troubleshooting guide: `search_eks_troubleshoot_guide(query='pod pending')`
|
|
62
|
+
|
|
63
|
+
## Best Practices
|
|
64
|
+
|
|
65
|
+
- Use descriptive names for resources to make them easier to identify and manage.
|
|
66
|
+
- Apply proper labels and annotations to Kubernetes resources for better organization.
|
|
67
|
+
- Use namespaces to isolate resources and avoid naming conflicts.
|
|
68
|
+
- Monitor resource usage with CloudWatch metrics to identify performance issues.
|
|
69
|
+
- Check logs and events when troubleshooting issues with Kubernetes resources.
|
|
70
|
+
- Follow the principle of least privilege when creating IAM policies.
|
|
71
|
+
- Use the search_eks_troubleshoot_guide tool when encountering common EKS issues.
|
|
72
|
+
- Always verify API versions with list_api_versions before creating resources.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
SERVER_DEPENDENCIES = [
|
|
76
|
+
'pydantic',
|
|
77
|
+
'loguru',
|
|
78
|
+
'boto3',
|
|
79
|
+
'kubernetes',
|
|
80
|
+
'requests',
|
|
81
|
+
'pyyaml',
|
|
82
|
+
'cachetools',
|
|
83
|
+
'requests_auth_aws_sigv4',
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
# Global reference to the MCP server instance for testing purposes
|
|
87
|
+
mcp = None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def create_server():
|
|
91
|
+
"""Create and configure the MCP server instance."""
|
|
92
|
+
return FastMCP(
|
|
93
|
+
'awslabs.eks-mcp-server',
|
|
94
|
+
instructions=SERVER_INSTRUCTIONS,
|
|
95
|
+
dependencies=SERVER_DEPENDENCIES,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def main():
|
|
100
|
+
"""Run the MCP server with CLI argument support."""
|
|
101
|
+
global mcp
|
|
102
|
+
|
|
103
|
+
parser = argparse.ArgumentParser(
|
|
104
|
+
description='An AWS Labs Model Context Protocol (MCP) server for EKS'
|
|
105
|
+
)
|
|
106
|
+
parser.add_argument(
|
|
107
|
+
'--allow-write',
|
|
108
|
+
action=argparse.BooleanOptionalAction,
|
|
109
|
+
default=False,
|
|
110
|
+
help='Enable write access mode (allow mutating operations)',
|
|
111
|
+
)
|
|
112
|
+
parser.add_argument(
|
|
113
|
+
'--allow-sensitive-data-access',
|
|
114
|
+
action=argparse.BooleanOptionalAction,
|
|
115
|
+
default=False,
|
|
116
|
+
help='Enable sensitive data access (required for reading logs, events, and Kubernetes Secrets)',
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
args = parser.parse_args()
|
|
120
|
+
|
|
121
|
+
allow_write = args.allow_write
|
|
122
|
+
allow_sensitive_data_access = args.allow_sensitive_data_access
|
|
123
|
+
|
|
124
|
+
# Log startup mode
|
|
125
|
+
mode_info = []
|
|
126
|
+
if not allow_write:
|
|
127
|
+
mode_info.append('read-only mode')
|
|
128
|
+
if not allow_sensitive_data_access:
|
|
129
|
+
mode_info.append('restricted sensitive data access mode')
|
|
130
|
+
|
|
131
|
+
mode_str = ' in ' + ', '.join(mode_info) if mode_info else ''
|
|
132
|
+
logger.info(f'Starting EKS MCP Server{mode_str}')
|
|
133
|
+
|
|
134
|
+
# Create the MCP server instance
|
|
135
|
+
mcp = create_server()
|
|
136
|
+
|
|
137
|
+
# Initialize handlers - all tools are always registered, access control is handled within tools
|
|
138
|
+
CloudWatchHandler(mcp, allow_sensitive_data_access)
|
|
139
|
+
EKSKnowledgeBaseHandler(mcp)
|
|
140
|
+
EksStackHandler(mcp, allow_write)
|
|
141
|
+
K8sHandler(mcp, allow_write, allow_sensitive_data_access)
|
|
142
|
+
IAMHandler(mcp, allow_write)
|
|
143
|
+
|
|
144
|
+
# Run server
|
|
145
|
+
mcp.run()
|
|
146
|
+
|
|
147
|
+
return mcp
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
if __name__ == '__main__':
|
|
151
|
+
main()
|