awslabs.cdk-mcp-server 0.0.10417__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 (72) hide show
  1. awslabs/__init__.py +2 -0
  2. awslabs/cdk_mcp_server/__init__.py +8 -0
  3. awslabs/cdk_mcp_server/core/__init__.py +1 -0
  4. awslabs/cdk_mcp_server/core/resources.py +271 -0
  5. awslabs/cdk_mcp_server/core/search_utils.py +182 -0
  6. awslabs/cdk_mcp_server/core/server.py +74 -0
  7. awslabs/cdk_mcp_server/core/tools.py +324 -0
  8. awslabs/cdk_mcp_server/data/__init__.py +1 -0
  9. awslabs/cdk_mcp_server/data/cdk_nag_parser.py +331 -0
  10. awslabs/cdk_mcp_server/data/construct_descriptions.py +32 -0
  11. awslabs/cdk_mcp_server/data/genai_cdk_loader.py +423 -0
  12. awslabs/cdk_mcp_server/data/lambda_powertools_loader.py +48 -0
  13. awslabs/cdk_mcp_server/data/schema_generator.py +666 -0
  14. awslabs/cdk_mcp_server/data/solutions_constructs_parser.py +782 -0
  15. awslabs/cdk_mcp_server/server.py +7 -0
  16. awslabs/cdk_mcp_server/static/CDK_GENERAL_GUIDANCE.md +232 -0
  17. awslabs/cdk_mcp_server/static/CDK_NAG_GUIDANCE.md +192 -0
  18. awslabs/cdk_mcp_server/static/__init__.py +5 -0
  19. awslabs/cdk_mcp_server/static/bedrock/agent/actiongroups.md +137 -0
  20. awslabs/cdk_mcp_server/static/bedrock/agent/alias.md +39 -0
  21. awslabs/cdk_mcp_server/static/bedrock/agent/collaboration.md +91 -0
  22. awslabs/cdk_mcp_server/static/bedrock/agent/creation.md +149 -0
  23. awslabs/cdk_mcp_server/static/bedrock/agent/custom_orchestration.md +74 -0
  24. awslabs/cdk_mcp_server/static/bedrock/agent/overview.md +78 -0
  25. awslabs/cdk_mcp_server/static/bedrock/agent/prompt_override.md +70 -0
  26. awslabs/cdk_mcp_server/static/bedrock/bedrockguardrails.md +188 -0
  27. awslabs/cdk_mcp_server/static/bedrock/knowledgebases/chunking.md +137 -0
  28. awslabs/cdk_mcp_server/static/bedrock/knowledgebases/datasources.md +225 -0
  29. awslabs/cdk_mcp_server/static/bedrock/knowledgebases/kendra.md +81 -0
  30. awslabs/cdk_mcp_server/static/bedrock/knowledgebases/overview.md +116 -0
  31. awslabs/cdk_mcp_server/static/bedrock/knowledgebases/parsing.md +36 -0
  32. awslabs/cdk_mcp_server/static/bedrock/knowledgebases/transformation.md +30 -0
  33. awslabs/cdk_mcp_server/static/bedrock/knowledgebases/vector/aurora.md +185 -0
  34. awslabs/cdk_mcp_server/static/bedrock/knowledgebases/vector/creation.md +80 -0
  35. awslabs/cdk_mcp_server/static/bedrock/knowledgebases/vector/opensearch.md +56 -0
  36. awslabs/cdk_mcp_server/static/bedrock/knowledgebases/vector/pinecone.md +66 -0
  37. awslabs/cdk_mcp_server/static/bedrock/profiles.md +153 -0
  38. awslabs/cdk_mcp_server/static/genai_cdk/bedrock/agent/actiongroups.md +137 -0
  39. awslabs/cdk_mcp_server/static/genai_cdk/bedrock/agent/alias.md +39 -0
  40. awslabs/cdk_mcp_server/static/genai_cdk/bedrock/agent/collaboration.md +91 -0
  41. awslabs/cdk_mcp_server/static/genai_cdk/bedrock/agent/creation.md +149 -0
  42. awslabs/cdk_mcp_server/static/genai_cdk/bedrock/agent/custom_orchestration.md +74 -0
  43. awslabs/cdk_mcp_server/static/genai_cdk/bedrock/agent/overview.md +78 -0
  44. awslabs/cdk_mcp_server/static/genai_cdk/bedrock/agent/prompt_override.md +70 -0
  45. awslabs/cdk_mcp_server/static/genai_cdk/bedrock/bedrockguardrails.md +188 -0
  46. awslabs/cdk_mcp_server/static/genai_cdk/bedrock/knowledgebases/chunking.md +137 -0
  47. awslabs/cdk_mcp_server/static/genai_cdk/bedrock/knowledgebases/datasources.md +225 -0
  48. awslabs/cdk_mcp_server/static/genai_cdk/bedrock/knowledgebases/kendra.md +81 -0
  49. awslabs/cdk_mcp_server/static/genai_cdk/bedrock/knowledgebases/overview.md +116 -0
  50. awslabs/cdk_mcp_server/static/genai_cdk/bedrock/knowledgebases/parsing.md +36 -0
  51. awslabs/cdk_mcp_server/static/genai_cdk/bedrock/knowledgebases/transformation.md +30 -0
  52. awslabs/cdk_mcp_server/static/genai_cdk/bedrock/knowledgebases/vector/aurora.md +185 -0
  53. awslabs/cdk_mcp_server/static/genai_cdk/bedrock/knowledgebases/vector/creation.md +80 -0
  54. awslabs/cdk_mcp_server/static/genai_cdk/bedrock/knowledgebases/vector/opensearch.md +56 -0
  55. awslabs/cdk_mcp_server/static/genai_cdk/bedrock/knowledgebases/vector/pinecone.md +66 -0
  56. awslabs/cdk_mcp_server/static/genai_cdk/bedrock/profiles.md +153 -0
  57. awslabs/cdk_mcp_server/static/genai_cdk/opensearch-vectorindex/overview.md +135 -0
  58. awslabs/cdk_mcp_server/static/genai_cdk/opensearchserverless/overview.md +17 -0
  59. awslabs/cdk_mcp_server/static/lambda_powertools/bedrock.md +127 -0
  60. awslabs/cdk_mcp_server/static/lambda_powertools/cdk.md +99 -0
  61. awslabs/cdk_mcp_server/static/lambda_powertools/dependencies.md +45 -0
  62. awslabs/cdk_mcp_server/static/lambda_powertools/index.md +36 -0
  63. awslabs/cdk_mcp_server/static/lambda_powertools/insights.md +95 -0
  64. awslabs/cdk_mcp_server/static/lambda_powertools/logging.md +43 -0
  65. awslabs/cdk_mcp_server/static/lambda_powertools/metrics.md +93 -0
  66. awslabs/cdk_mcp_server/static/lambda_powertools/tracing.md +63 -0
  67. awslabs/cdk_mcp_server/static/opensearch-vectorindex/overview.md +135 -0
  68. awslabs/cdk_mcp_server/static/opensearchserverless/overview.md +17 -0
  69. awslabs_cdk_mcp_server-0.0.10417.dist-info/METADATA +14 -0
  70. awslabs_cdk_mcp_server-0.0.10417.dist-info/RECORD +72 -0
  71. awslabs_cdk_mcp_server-0.0.10417.dist-info/WHEEL +4 -0
  72. awslabs_cdk_mcp_server-0.0.10417.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,423 @@
1
+ """GenAI CDK constructs static content loader."""
2
+
3
+ import logging
4
+ import os
5
+ from awslabs.cdk_mcp_server.data.construct_descriptions import get_construct_descriptions
6
+ from enum import Enum
7
+ from typing import Any, Dict, List, Optional
8
+
9
+
10
+ # Set up logging
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class ConstructType(str, Enum):
15
+ """GenAI CDK construct types."""
16
+
17
+ BEDROCK = 'bedrock'
18
+ OPENSEARCH_SERVERLESS = 'opensearchserverless'
19
+ OPENSEARCH_VECTOR_INDEX = 'opensearch-vectorindex'
20
+
21
+
22
+ def get_construct_types() -> List[str]:
23
+ """Get a list of available construct types."""
24
+ return [ct.value for ct in ConstructType]
25
+
26
+
27
+ def get_construct_map() -> Dict[str, str]:
28
+ """Get a dictionary mapping construct types to their descriptions."""
29
+ return {
30
+ 'bedrock': 'Amazon Bedrock constructs for agents, knowledge bases, and more',
31
+ 'opensearchserverless': 'Amazon OpenSearch Serverless constructs for vector search',
32
+ 'opensearch-vectorindex': 'Amazon OpenSearch vector index constructs',
33
+ }
34
+
35
+
36
+ def get_genai_cdk_overview(construct_type: str = '') -> str:
37
+ """Get an overview of GenAI CDK constructs.
38
+
39
+ Args:
40
+ construct_type: Optional construct type to get overview for.
41
+ If empty, returns the best practices.
42
+
43
+ Returns:
44
+ The overview content as a string.
45
+ """
46
+ # Normalize construct type
47
+ construct_type = construct_type.lower()
48
+
49
+ # Validate construct type
50
+ if construct_type not in get_construct_types():
51
+ construct_list = '\n'.join([f'- {t}: {desc}' for t, desc in get_construct_map().items()])
52
+ return f"# GenAI CDK Constructs\n\nConstruct type '{construct_type}' not found. Available types:\n\n{construct_list}"
53
+
54
+ # Get overview file
55
+ file_path = os.path.join(
56
+ os.path.dirname(os.path.dirname(__file__)), # Fix path to use parent directory
57
+ 'static',
58
+ 'genai_cdk',
59
+ construct_type,
60
+ 'overview.md',
61
+ )
62
+ try:
63
+ with open(file_path, 'r') as f:
64
+ return f.read()
65
+ except FileNotFoundError:
66
+ return f"Error: Overview file for '{construct_type}' not found."
67
+
68
+
69
+ def list_available_sections(construct_type: str, construct_name: str) -> List[str]:
70
+ """List available sections for a specific construct.
71
+
72
+ Args:
73
+ construct_type: The construct type (e.g., 'bedrock')
74
+ construct_name: The name of the construct (e.g., 'agent', 'knowledgebases')
75
+
76
+ Returns:
77
+ List of available sections.
78
+ """
79
+ sections = []
80
+ base_path = os.path.join(
81
+ os.path.dirname(os.path.dirname(__file__)), # Fix path to use parent directory
82
+ 'static',
83
+ 'genai_cdk',
84
+ construct_type,
85
+ construct_name,
86
+ )
87
+
88
+ if not os.path.exists(base_path):
89
+ return sections
90
+
91
+ # Walk through the directory structure
92
+ for root, dirs, files in os.walk(base_path):
93
+ rel_path = os.path.relpath(root, base_path)
94
+
95
+ for file in files:
96
+ if file.endswith('.md') and file != 'overview.md':
97
+ section_name = file[:-3] # Remove .md extension
98
+
99
+ # For files in the base directory
100
+ if rel_path == '.':
101
+ sections.append(section_name)
102
+ else:
103
+ # For files in subdirectories
104
+ if rel_path != '.':
105
+ section_path = os.path.join(rel_path, section_name)
106
+ # Replace backslashes with forward slashes for consistency
107
+ section_path = section_path.replace('\\', '/')
108
+ sections.append(section_path)
109
+
110
+ return sections
111
+
112
+
113
+ def get_genai_cdk_construct_section(construct_type: str, construct_name: str, section: str) -> str:
114
+ """Get a specific section of documentation for a GenAI CDK construct.
115
+
116
+ Args:
117
+ construct_type: The construct type (e.g., 'bedrock')
118
+ construct_name: The name of the construct (e.g., 'agent', 'knowledgebases')
119
+ section: The section name (e.g., 'actiongroups', 'vector/opensearch')
120
+
121
+ Returns:
122
+ The section documentation as a string.
123
+ """
124
+ # Normalize inputs
125
+ construct_type = construct_type.lower()
126
+ construct_name_lower = construct_name.lower()
127
+
128
+ # Special handling for Agent_* and Knowledgebases_* constructs
129
+ if construct_name_lower.startswith('agent_'):
130
+ # Convert Agent_actiongroups to agent/actiongroups
131
+ construct_name_lower = 'agent'
132
+ section = construct_name_lower.split('_', 1)[1]
133
+ elif construct_name_lower.startswith('knowledgebases_'):
134
+ # Convert Knowledgebases_vector_opensearch to knowledgebases/vector/opensearch
135
+ parts = construct_name_lower.split('_', 1)
136
+ if len(parts) > 1:
137
+ construct_name_lower = parts[0]
138
+ # Handle nested paths with underscores (e.g., vector_opensearch -> vector/opensearch)
139
+ section_parts = parts[1].split('_')
140
+ if len(section_parts) > 1 and section_parts[0] == 'vector':
141
+ # Special case for vector/* sections which are in a nested directory
142
+ section = f'vector/{section_parts[1]}'
143
+ else:
144
+ section = parts[1]
145
+
146
+ # Validate construct type
147
+ if construct_type not in get_construct_types():
148
+ return f"Error: Construct type '{construct_type}' not found."
149
+
150
+ # Handle nested sections (e.g., vector/opensearch)
151
+ if '/' in section:
152
+ section_parts = section.split('/')
153
+ file_path = (
154
+ os.path.join(
155
+ os.path.dirname(os.path.dirname(__file__)), # Fix path to use parent directory
156
+ 'static',
157
+ 'genai_cdk',
158
+ construct_type,
159
+ construct_name_lower,
160
+ *section_parts,
161
+ )
162
+ + '.md'
163
+ )
164
+ else:
165
+ # Regular section
166
+ file_path = os.path.join(
167
+ os.path.dirname(os.path.dirname(__file__)), # Fix path to use parent directory
168
+ 'static',
169
+ 'genai_cdk',
170
+ construct_type,
171
+ construct_name_lower,
172
+ f'{section}.md',
173
+ )
174
+
175
+ try:
176
+ with open(file_path, 'r') as f:
177
+ return f.read()
178
+ except FileNotFoundError:
179
+ return (
180
+ f"Error: Section '{section}' for '{construct_name}' in '{construct_type}' not found."
181
+ )
182
+
183
+
184
+ def get_genai_cdk_construct(construct_type: str, construct_name: str) -> str:
185
+ """Get documentation for a specific GenAI CDK construct.
186
+
187
+ Args:
188
+ construct_type: The construct type (e.g., 'bedrock')
189
+ construct_name: The name of the construct (e.g., 'Agent')
190
+
191
+ Returns:
192
+ The construct documentation as a string.
193
+ """
194
+ # Normalize inputs
195
+ construct_type = construct_type.lower()
196
+ construct_name_lower = construct_name.lower()
197
+
198
+ # Special handling for Agent_* and Knowledgebases_* constructs
199
+ if construct_name_lower.startswith('agent_'):
200
+ # For Agent_actiongroups, redirect to agent/actiongroups section
201
+ parent = 'agent'
202
+ child = construct_name_lower.split('_', 1)[1]
203
+ return get_genai_cdk_construct_section(construct_type, parent, child)
204
+ elif construct_name_lower.startswith('knowledgebases_'):
205
+ # For Knowledgebases_vector_opensearch, redirect to knowledgebases/vector/opensearch section
206
+ parts = construct_name_lower.split('_', 1)
207
+ if len(parts) > 1:
208
+ parent = parts[0]
209
+ # Handle nested paths with underscores (e.g., vector_opensearch -> vector/opensearch)
210
+ section_parts = parts[1].split('_')
211
+ if len(section_parts) > 1 and section_parts[0] == 'vector':
212
+ # Special case for vector/* sections which are in a nested directory
213
+ child = f'vector/{section_parts[1]}'
214
+ else:
215
+ child = parts[1]
216
+ return get_genai_cdk_construct_section(construct_type, parent, child)
217
+
218
+ # Special handling for agent and knowledgebases
219
+ if construct_name_lower in ['agent', 'knowledgebases']:
220
+ # For these special cases, return an overview or index of available sections
221
+ base_path = os.path.join(
222
+ os.path.dirname(os.path.dirname(__file__)), # Fix path to use parent directory
223
+ 'static',
224
+ 'genai_cdk',
225
+ construct_type,
226
+ construct_name_lower,
227
+ )
228
+
229
+ # Check if directory exists
230
+ if not os.path.exists(base_path):
231
+ return f"Error: Documentation for '{construct_name}' in '{construct_type}' not found."
232
+
233
+ # List files in directory
234
+ sections = []
235
+ for file_name in os.listdir(base_path):
236
+ if file_name.endswith('.md') and file_name != 'overview.md':
237
+ sections.append(file_name[:-3]) # Remove .md extension
238
+
239
+ # Also check subdirectories
240
+ for root, dirs, files in os.walk(base_path):
241
+ if root != base_path: # Skip the base directory
242
+ rel_path = os.path.relpath(root, base_path)
243
+ for file_name in files:
244
+ if file_name.endswith('.md'):
245
+ section_path = os.path.join(rel_path, file_name[:-3])
246
+ section_path = section_path.replace('\\', '/')
247
+ sections.append(section_path)
248
+
249
+ result = f'# {construct_name.capitalize()} Documentation\n\n'
250
+ result += 'This documentation is split into sections for easier consumption.\n\n'
251
+ result += '## Available Sections\n\n'
252
+
253
+ for section in sorted(sections):
254
+ result += f'- [{section}](genai-cdk-constructs://{construct_type}/{construct_name_lower}/{section})\n'
255
+
256
+ return result
257
+
258
+ # Special handling for key constructs
259
+ key_construct_mapping = {
260
+ 'agent': 'agent',
261
+ 'agents': 'agent',
262
+ 'knowledgebase': 'knowledgebases',
263
+ 'knowledgebases': 'knowledgebases',
264
+ 'knowledge-base': 'knowledgebases',
265
+ 'knowledge-bases': 'knowledgebases',
266
+ 'agentactiongroup': 'agent/actiongroups',
267
+ 'action-group': 'agent/actiongroups',
268
+ 'actiongroup': 'agent/actiongroups',
269
+ 'agentalias': 'agent/alias',
270
+ 'guardrail': 'bedrockguardrails',
271
+ 'guardrails': 'bedrockguardrails',
272
+ 'bedrock-guardrails': 'bedrockguardrails',
273
+ }
274
+
275
+ # Normalize construct name
276
+ if construct_name_lower in key_construct_mapping:
277
+ mapped_name = key_construct_mapping[construct_name_lower]
278
+ if '/' in mapped_name:
279
+ # Handle redirects to sections
280
+ parent, section = mapped_name.split('/', 1)
281
+ return get_genai_cdk_construct_section(construct_type, parent, section)
282
+ else:
283
+ construct_name_lower = mapped_name
284
+
285
+ # Validate construct type
286
+ if construct_type not in get_construct_types():
287
+ construct_list = '\n'.join([f'- {t}: {desc}' for t, desc in get_construct_map().items()])
288
+ return f"# GenAI CDK Constructs\n\nConstruct type '{construct_type}' not found. Available types:\n\n{construct_list}"
289
+
290
+ # Get construct file (flat structure)
291
+ file_path = os.path.join(
292
+ os.path.dirname(os.path.dirname(__file__)), # Fix path to use parent directory
293
+ 'static',
294
+ 'genai_cdk',
295
+ construct_type,
296
+ f'{construct_name_lower}.md',
297
+ )
298
+ try:
299
+ with open(file_path, 'r') as f:
300
+ return f.read()
301
+ except FileNotFoundError:
302
+ # Try to see if this is a directory with an overview.md file
303
+ overview_path = os.path.join(
304
+ os.path.dirname(os.path.dirname(__file__)),
305
+ 'static',
306
+ 'genai_cdk',
307
+ construct_type,
308
+ construct_name_lower,
309
+ 'overview.md',
310
+ )
311
+ try:
312
+ with open(overview_path, 'r') as f:
313
+ return f.read()
314
+ except FileNotFoundError:
315
+ return f"Error: Documentation for '{construct_name}' in '{construct_type}' not found."
316
+
317
+
318
+ def list_available_constructs(construct_type: Optional[str] = None) -> List[Dict[str, Any]]:
319
+ """List available constructs.
320
+
321
+ Args:
322
+ construct_type: Optional construct type to filter by.
323
+
324
+ Returns:
325
+ List of constructs with name, type, and description.
326
+ """
327
+ constructs = []
328
+
329
+ # Determine which construct types to search
330
+ if construct_type is not None:
331
+ construct_types = [construct_type.lower()]
332
+ else:
333
+ construct_types = get_construct_types()
334
+
335
+ # For each construct type, list files in the directory
336
+ for ct in construct_types:
337
+ if ct not in get_construct_types():
338
+ continue
339
+
340
+ # Get directory path - fix path to use parent directory
341
+ dir_path = os.path.join(
342
+ os.path.dirname(os.path.dirname(__file__)), 'static', 'genai_cdk', ct
343
+ )
344
+
345
+ # Skip if directory doesn't exist
346
+ if not os.path.exists(dir_path):
347
+ continue
348
+
349
+ # Process files in the main directory
350
+ process_directory_files(dir_path, ct, constructs)
351
+
352
+ # Process subdirectories recursively
353
+ for root, dirs, files in os.walk(dir_path):
354
+ # Skip the main directory as it's already processed
355
+ if root == dir_path:
356
+ continue
357
+
358
+ # Get the relative path from the main directory
359
+ rel_path = os.path.relpath(root, dir_path)
360
+ # Use the relative path as the parent
361
+ process_directory_files(root, ct, constructs, parent=rel_path.replace(os.sep, '_'))
362
+
363
+ return constructs
364
+
365
+
366
+ def process_directory_files(
367
+ dir_path: str, construct_type: str, constructs: List[Dict[str, Any]], parent: Optional[str] = None
368
+ ):
369
+ """Process files in a directory and add them to the constructs list.
370
+
371
+ Args:
372
+ dir_path: Path to the directory
373
+ construct_type: Type of construct
374
+ constructs: List to add constructs to
375
+ parent: Optional parent directory name
376
+ """
377
+ # List files in directory
378
+ for file_name in os.listdir(dir_path):
379
+ # Skip overview file, directories, and non-markdown files
380
+ if (
381
+ file_name == 'overview.md'
382
+ or not file_name.endswith('.md')
383
+ or os.path.isdir(os.path.join(dir_path, file_name))
384
+ ):
385
+ continue
386
+
387
+ # Extract construct name from file name
388
+ base_name = file_name[:-3]
389
+
390
+ # Format the construct name
391
+ if parent:
392
+ construct_name = f'{parent}_{base_name}'
393
+ else:
394
+ construct_name = base_name
395
+
396
+ display_name = construct_name.capitalize()
397
+
398
+ # Define file_path here, before it's used
399
+ file_path = os.path.join(dir_path, file_name)
400
+
401
+ # Get description from fixed mapping or use default
402
+ descriptions = get_construct_descriptions()
403
+ description = descriptions.get(display_name, "")
404
+
405
+ # If no fixed description, fall back to current behavior
406
+ if not description:
407
+ try:
408
+ with open(file_path, 'r') as f:
409
+ first_line = f.readline().strip()
410
+ description = (
411
+ first_line[1:].strip() if first_line.startswith('#') else display_name
412
+ )
413
+ except Exception:
414
+ description = f'A {construct_type} construct.'
415
+
416
+ # Add to list
417
+ constructs.append(
418
+ {
419
+ 'name': display_name,
420
+ 'type': construct_type,
421
+ 'description': description,
422
+ }
423
+ )
@@ -0,0 +1,48 @@
1
+ """Lambda Powertools guidance loader module."""
2
+
3
+ import os
4
+ from typing import Dict
5
+
6
+
7
+ def get_topic_map() -> Dict[str, str]:
8
+ """Get a dictionary mapping topic names to their descriptions."""
9
+ return {
10
+ 'index': 'Overview and table of contents',
11
+ 'logging': 'Structured logging implementation',
12
+ 'tracing': 'Tracing implementation',
13
+ 'metrics': 'Metrics implementation',
14
+ 'cdk': 'CDK integration patterns',
15
+ 'dependencies': 'Dependencies management',
16
+ 'insights': 'Lambda Insights integration',
17
+ 'bedrock': 'Bedrock Agent integration',
18
+ }
19
+
20
+
21
+ def get_lambda_powertools_section(topic: str = '') -> str:
22
+ """Get a specific section of the Lambda Powertools guidance.
23
+
24
+ Args:
25
+ topic: The topic to get guidance on. If empty or "index", returns the index.
26
+
27
+ Returns:
28
+ The guidance for the specified topic
29
+ """
30
+ topic_map = get_topic_map()
31
+
32
+ # Handle the index case
33
+ if not topic or topic.lower() == 'index':
34
+ topic = 'index'
35
+
36
+ if topic.lower() in topic_map:
37
+ file_path = os.path.join(
38
+ os.path.dirname(__file__), 'static', 'lambda_powertools', f'{topic.lower()}.md'
39
+ )
40
+ try:
41
+ with open(file_path, 'r') as f:
42
+ return f.read()
43
+ except FileNotFoundError:
44
+ return f"Error: File for topic '{topic}' not found."
45
+ else:
46
+ # Topic not found
47
+ topic_list = '\n'.join([f'- {t}: {desc}' for t, desc in topic_map.items() if t != 'index'])
48
+ return f"# Lambda Powertools Guidance\n\nTopic '{topic}' not found. Available topics:\n\n{topic_list}"