ic-code 1.0.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.
ic/core/mcp_manager.py ADDED
@@ -0,0 +1,856 @@
1
+ """
2
+ MCP (Model Context Protocol) Manager for IC.
3
+
4
+ This module provides secure MCP server integration with query capabilities
5
+ for AWS, Azure, Terraform, and GitHub operations.
6
+ """
7
+
8
+ import json
9
+ import logging
10
+ import os
11
+ from pathlib import Path
12
+ from typing import Dict, List, Any, Optional, Union
13
+ from dataclasses import dataclass
14
+
15
+ from ..config.security import SecurityManager
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ @dataclass
21
+ class MCPServerConfig:
22
+ """Configuration for an MCP server."""
23
+ name: str
24
+ command: str
25
+ args: List[str]
26
+ env: Dict[str, str]
27
+ disabled: bool
28
+ auto_approve: List[str]
29
+
30
+
31
+ @dataclass
32
+ class MCPQueryResult:
33
+ """Result from an MCP query."""
34
+ success: bool
35
+ data: Any
36
+ error: Optional[str] = None
37
+ server_name: Optional[str] = None
38
+
39
+
40
+ class MCPManager:
41
+ """
42
+ Manages MCP server configurations and provides query capabilities.
43
+
44
+ This class handles secure loading of MCP server configurations,
45
+ sensitive data masking, and provides query methods for different
46
+ cloud platforms and services.
47
+ """
48
+
49
+ def __init__(self, config: Optional[Dict[str, Any]] = None, security_manager: Optional[SecurityManager] = None):
50
+ """
51
+ Initialize MCPManager.
52
+
53
+ Args:
54
+ config: MCP configuration dictionary
55
+ security_manager: SecurityManager instance for data masking
56
+ """
57
+ self.config = config or {}
58
+ self.security = security_manager or SecurityManager()
59
+ self.servers: Dict[str, MCPServerConfig] = {}
60
+ self._load_mcp_servers()
61
+
62
+ def _load_mcp_servers(self) -> None:
63
+ """
64
+ Load MCP server configurations from various sources.
65
+
66
+ Loads from:
67
+ 1. Workspace .kiro/settings/mcp.json
68
+ 2. User ~/.kiro/settings/mcp.json
69
+ 3. Configuration passed to constructor
70
+ """
71
+ # Load from workspace config
72
+ workspace_config = self._load_workspace_mcp_config()
73
+
74
+ # Load from user config
75
+ user_config = self._load_user_mcp_config()
76
+
77
+ # Merge configurations (workspace takes precedence)
78
+ merged_servers = {}
79
+ if user_config:
80
+ merged_servers.update(user_config.get('mcpServers', {}))
81
+ if workspace_config:
82
+ merged_servers.update(workspace_config.get('mcpServers', {}))
83
+
84
+ # Add config from constructor
85
+ if self.config.get('mcp', {}).get('servers'):
86
+ merged_servers.update(self.config['mcp']['servers'])
87
+
88
+ # Convert to MCPServerConfig objects
89
+ for name, server_config in merged_servers.items():
90
+ try:
91
+ self.servers[name] = MCPServerConfig(
92
+ name=name,
93
+ command=server_config.get('command', ''),
94
+ args=server_config.get('args', []),
95
+ env=server_config.get('env', {}),
96
+ disabled=server_config.get('disabled', False),
97
+ auto_approve=server_config.get('autoApprove', [])
98
+ )
99
+ logger.debug(f"Loaded MCP server config: {name}")
100
+ except Exception as e:
101
+ logger.warning(f"Failed to load MCP server config for {name}: {e}")
102
+
103
+ def _load_workspace_mcp_config(self) -> Optional[Dict[str, Any]]:
104
+ """
105
+ Load MCP configuration from workspace .kiro/settings/mcp.json.
106
+
107
+ Returns:
108
+ MCP configuration dictionary or None if not found
109
+ """
110
+ workspace_config_path = Path('.kiro/settings/mcp.json')
111
+ return self._load_mcp_config_file(workspace_config_path)
112
+
113
+ def _load_user_mcp_config(self) -> Optional[Dict[str, Any]]:
114
+ """
115
+ Load MCP configuration from user ~/.kiro/settings/mcp.json.
116
+
117
+ Returns:
118
+ MCP configuration dictionary or None if not found
119
+ """
120
+ user_config_path = Path.home() / '.kiro' / 'settings' / 'mcp.json'
121
+ return self._load_mcp_config_file(user_config_path)
122
+
123
+ def _load_mcp_config_file(self, config_path: Path) -> Optional[Dict[str, Any]]:
124
+ """
125
+ Load MCP configuration from a specific file.
126
+
127
+ Args:
128
+ config_path: Path to MCP configuration file
129
+
130
+ Returns:
131
+ MCP configuration dictionary or None if not found/invalid
132
+ """
133
+ try:
134
+ if config_path.exists():
135
+ with open(config_path, 'r', encoding='utf-8') as f:
136
+ config = json.load(f)
137
+ logger.debug(f"Loaded MCP config from {config_path}")
138
+ return config
139
+ except json.JSONDecodeError as e:
140
+ logger.error(f"Invalid JSON in MCP config file {config_path}: {e}")
141
+ except Exception as e:
142
+ logger.warning(f"Could not load MCP config from {config_path}: {e}")
143
+
144
+ return None
145
+
146
+ def get_server_config(self, server_name: str, mask_sensitive: bool = True) -> Optional[Dict[str, Any]]:
147
+ """
148
+ Get configuration for a specific MCP server.
149
+
150
+ Args:
151
+ server_name: Name of the MCP server
152
+ mask_sensitive: Whether to mask sensitive data in the config
153
+
154
+ Returns:
155
+ Server configuration dictionary or None if not found
156
+ """
157
+ server = self.servers.get(server_name)
158
+ if not server:
159
+ return None
160
+
161
+ config = {
162
+ 'name': server.name,
163
+ 'command': server.command,
164
+ 'args': server.args,
165
+ 'env': server.env,
166
+ 'disabled': server.disabled,
167
+ 'auto_approve': server.auto_approve
168
+ }
169
+
170
+ if mask_sensitive:
171
+ config = self.security.mask_sensitive_data(config)
172
+
173
+ return config
174
+
175
+ def list_servers(self, include_disabled: bool = False, mask_sensitive: bool = True) -> Dict[str, Dict[str, Any]]:
176
+ """
177
+ List all configured MCP servers.
178
+
179
+ Args:
180
+ include_disabled: Whether to include disabled servers
181
+ mask_sensitive: Whether to mask sensitive data in configurations
182
+
183
+ Returns:
184
+ Dictionary of server configurations
185
+ """
186
+ servers = {}
187
+ for name, server in self.servers.items():
188
+ if not include_disabled and server.disabled:
189
+ continue
190
+
191
+ config = self.get_server_config(name, mask_sensitive)
192
+ if config:
193
+ servers[name] = config
194
+
195
+ return servers
196
+
197
+ def is_server_available(self, server_name: str) -> bool:
198
+ """
199
+ Check if an MCP server is available and enabled.
200
+
201
+ Args:
202
+ server_name: Name of the MCP server
203
+
204
+ Returns:
205
+ True if server is available and enabled
206
+ """
207
+ server = self.servers.get(server_name)
208
+ return server is not None and not server.disabled
209
+
210
+ def query_aws_best_practices(self, service: str, operation: str = "", search_phrase: str = "") -> MCPQueryResult:
211
+ """
212
+ Query AWS documentation for best practices.
213
+
214
+ Args:
215
+ service: AWS service name (e.g., 's3', 'ec2', 'lambda')
216
+ operation: Specific operation (e.g., 'create-bucket', 'launch-instance')
217
+ search_phrase: Custom search phrase (overrides service/operation)
218
+
219
+ Returns:
220
+ MCPQueryResult with AWS documentation data
221
+ """
222
+ server_names = [
223
+ 'awslabs.aws-documentation-mcp-server',
224
+ 'aws-docs',
225
+ 'aws_docs'
226
+ ]
227
+
228
+ # Find available AWS documentation server
229
+ aws_server = None
230
+ for server_name in server_names:
231
+ if self.is_server_available(server_name):
232
+ aws_server = server_name
233
+ break
234
+
235
+ if not aws_server:
236
+ return self._create_fallback_result(
237
+ "AWS documentation MCP server not available",
238
+ self._get_aws_fallback_data(service, operation)
239
+ )
240
+
241
+ # Build search phrase
242
+ if not search_phrase:
243
+ if operation:
244
+ search_phrase = f"{service} {operation} best practices"
245
+ else:
246
+ search_phrase = f"{service} best practices"
247
+
248
+ logger.info(f"Querying AWS documentation for: {search_phrase}")
249
+
250
+ try:
251
+ # This would be the actual MCP query implementation
252
+ # For now, return a structured result with fallback data
253
+ return MCPQueryResult(
254
+ success=True,
255
+ data={
256
+ 'query': search_phrase,
257
+ 'server': aws_server,
258
+ 'service': service,
259
+ 'operation': operation,
260
+ 'search_type': 'aws_documentation',
261
+ 'recommendations': self._get_aws_recommendations(service, operation),
262
+ 'documentation_urls': self._get_aws_documentation_urls(service),
263
+ 'best_practices': self._get_aws_best_practices(service)
264
+ },
265
+ server_name=aws_server
266
+ )
267
+ except Exception as e:
268
+ logger.error(f"Error querying AWS documentation: {e}")
269
+ return self._create_fallback_result(
270
+ f"AWS documentation query failed: {str(e)}",
271
+ self._get_aws_fallback_data(service, operation)
272
+ )
273
+
274
+ def query_terraform_module(self, provider: str, service: str, module_name: str = "") -> MCPQueryResult:
275
+ """
276
+ Query Terraform registry for modules.
277
+
278
+ Args:
279
+ provider: Terraform provider (e.g., 'aws', 'azure', 'google')
280
+ service: Service name (e.g., 's3', 'vm', 'storage')
281
+ module_name: Specific module name (optional)
282
+
283
+ Returns:
284
+ MCPQueryResult with Terraform module data
285
+ """
286
+ server_names = [
287
+ 'terraform',
288
+ 'terraform-mcp-server',
289
+ 'hashicorp/terraform-mcp-server'
290
+ ]
291
+
292
+ # Find available Terraform server
293
+ terraform_server = None
294
+ for server_name in server_names:
295
+ if self.is_server_available(server_name):
296
+ terraform_server = server_name
297
+ break
298
+
299
+ if not terraform_server:
300
+ return self._create_fallback_result(
301
+ "Terraform MCP server not available",
302
+ self._get_terraform_fallback_data(provider, service, module_name)
303
+ )
304
+
305
+ # Build query
306
+ if module_name:
307
+ query = module_name
308
+ else:
309
+ query = f"{provider} {service}"
310
+
311
+ logger.info(f"Querying Terraform registry for: {query}")
312
+
313
+ try:
314
+ # This would be the actual MCP query implementation
315
+ return MCPQueryResult(
316
+ success=True,
317
+ data={
318
+ 'query': query,
319
+ 'server': terraform_server,
320
+ 'provider': provider,
321
+ 'service': service,
322
+ 'module_name': module_name,
323
+ 'search_type': 'terraform_modules',
324
+ 'recommended_modules': self._get_terraform_module_recommendations(provider, service),
325
+ 'provider_info': self._get_terraform_provider_info(provider),
326
+ 'usage_examples': self._get_terraform_usage_examples(provider, service)
327
+ },
328
+ server_name=terraform_server
329
+ )
330
+ except Exception as e:
331
+ logger.error(f"Error querying Terraform registry: {e}")
332
+ return self._create_fallback_result(
333
+ f"Terraform registry query failed: {str(e)}",
334
+ self._get_terraform_fallback_data(provider, service, module_name)
335
+ )
336
+
337
+ def query_azure_documentation(self, service: str, intent: str, operation: str = "") -> MCPQueryResult:
338
+ """
339
+ Query Azure documentation and best practices.
340
+
341
+ Args:
342
+ service: Azure service name (e.g., 'vm', 'storage', 'aks')
343
+ intent: Query intent (e.g., 'documentation', 'best-practices')
344
+ operation: Specific operation (optional)
345
+
346
+ Returns:
347
+ MCPQueryResult with Azure documentation data
348
+ """
349
+ server_names = [
350
+ 'Azure MCP Server',
351
+ 'azure',
352
+ 'azure-mcp-server'
353
+ ]
354
+
355
+ # Find available Azure server
356
+ azure_server = None
357
+ for server_name in server_names:
358
+ if self.is_server_available(server_name):
359
+ azure_server = server_name
360
+ break
361
+
362
+ if not azure_server:
363
+ return self._create_fallback_result(
364
+ "Azure MCP server not available",
365
+ self._get_azure_fallback_data(service, intent, operation)
366
+ )
367
+
368
+ logger.info(f"Querying Azure documentation for service: {service}, intent: {intent}")
369
+
370
+ try:
371
+ # This would be the actual MCP query implementation
372
+ return MCPQueryResult(
373
+ success=True,
374
+ data={
375
+ 'service': service,
376
+ 'intent': intent,
377
+ 'operation': operation,
378
+ 'server': azure_server,
379
+ 'search_type': 'azure_documentation',
380
+ 'service_info': self._get_azure_service_info(service),
381
+ 'best_practices': self._get_azure_best_practices(service),
382
+ 'documentation_links': self._get_azure_documentation_links(service),
383
+ 'cli_examples': self._get_azure_cli_examples(service, operation)
384
+ },
385
+ server_name=azure_server
386
+ )
387
+ except Exception as e:
388
+ logger.error(f"Error querying Azure documentation: {e}")
389
+ return self._create_fallback_result(
390
+ f"Azure documentation query failed: {str(e)}",
391
+ self._get_azure_fallback_data(service, intent, operation)
392
+ )
393
+
394
+ def query_github_operations(self, operation: str, repository: str = "", **kwargs) -> MCPQueryResult:
395
+ """
396
+ Query GitHub MCP server for repository operations.
397
+
398
+ Args:
399
+ operation: GitHub operation (e.g., 'list_issues', 'create_pr', 'get_repo')
400
+ repository: Repository name in format 'owner/repo' (optional)
401
+ **kwargs: Additional parameters for the operation
402
+
403
+ Returns:
404
+ MCPQueryResult with GitHub operation data
405
+ """
406
+ server_names = [
407
+ 'github',
408
+ 'github-mcp-server'
409
+ ]
410
+
411
+ # Find available GitHub server
412
+ github_server = None
413
+ for server_name in server_names:
414
+ if self.is_server_available(server_name):
415
+ github_server = server_name
416
+ break
417
+
418
+ if not github_server:
419
+ return self._create_fallback_result(
420
+ "GitHub MCP server not available",
421
+ self._get_github_fallback_data(operation, repository, **kwargs)
422
+ )
423
+
424
+ logger.info(f"Querying GitHub for operation: {operation}")
425
+
426
+ try:
427
+ # Mask sensitive data in parameters
428
+ masked_kwargs = self.security.mask_sensitive_data(kwargs)
429
+
430
+ # This would be the actual MCP query implementation
431
+ return MCPQueryResult(
432
+ success=True,
433
+ data={
434
+ 'operation': operation,
435
+ 'repository': repository,
436
+ 'parameters': masked_kwargs,
437
+ 'server': github_server,
438
+ 'search_type': 'github_operations',
439
+ 'operation_info': self._get_github_operation_info(operation),
440
+ 'repository_info': self._get_github_repository_info(repository) if repository else None,
441
+ 'api_endpoints': self._get_github_api_endpoints(operation),
442
+ 'examples': self._get_github_operation_examples(operation)
443
+ },
444
+ server_name=github_server
445
+ )
446
+ except Exception as e:
447
+ logger.error(f"Error querying GitHub: {e}")
448
+ return self._create_fallback_result(
449
+ f"GitHub operation query failed: {str(e)}",
450
+ self._get_github_fallback_data(operation, repository, **kwargs)
451
+ )
452
+
453
+ def validate_server_security(self, server_name: str) -> List[str]:
454
+ """
455
+ Validate MCP server configuration for security issues.
456
+
457
+ Args:
458
+ server_name: Name of the MCP server to validate
459
+
460
+ Returns:
461
+ List of security warnings
462
+ """
463
+ server = self.servers.get(server_name)
464
+ if not server:
465
+ return [f"Server '{server_name}' not found"]
466
+
467
+ warnings = []
468
+
469
+ # Check environment variables for sensitive data
470
+ if server.env:
471
+ env_warnings = self.security.validate_config_security({'env': server.env})
472
+ warnings.extend([f"Server '{server_name}': {w}" for w in env_warnings])
473
+
474
+ # Check for sensitive data in command arguments
475
+ for i, arg in enumerate(server.args):
476
+ if self.security._looks_like_secret(arg):
477
+ warnings.append(
478
+ f"Server '{server_name}': Potential secret in args[{i}]. "
479
+ f"Consider using environment variables."
480
+ )
481
+
482
+ return warnings
483
+
484
+ def get_security_summary(self) -> Dict[str, Any]:
485
+ """
486
+ Get security summary for all MCP servers.
487
+
488
+ Returns:
489
+ Dictionary with security information for all servers
490
+ """
491
+ summary = {
492
+ 'total_servers': len(self.servers),
493
+ 'enabled_servers': len([s for s in self.servers.values() if not s.disabled]),
494
+ 'servers_with_env_vars': len([s for s in self.servers.values() if s.env]),
495
+ 'security_warnings': {},
496
+ 'masked_configs': {}
497
+ }
498
+
499
+ for server_name in self.servers:
500
+ # Get security warnings
501
+ warnings = self.validate_server_security(server_name)
502
+ if warnings:
503
+ summary['security_warnings'][server_name] = warnings
504
+
505
+ # Get masked configuration
506
+ masked_config = self.get_server_config(server_name, mask_sensitive=True)
507
+ if masked_config:
508
+ summary['masked_configs'][server_name] = masked_config
509
+
510
+ return summary
511
+
512
+ def _create_fallback_result(self, error_message: str, fallback_data: Dict[str, Any]) -> MCPQueryResult:
513
+ """
514
+ Create a fallback result when MCP server is unavailable.
515
+
516
+ Args:
517
+ error_message: Error message describing the issue
518
+ fallback_data: Fallback data to provide
519
+
520
+ Returns:
521
+ MCPQueryResult with fallback data
522
+ """
523
+ return MCPQueryResult(
524
+ success=False,
525
+ data=fallback_data,
526
+ error=error_message,
527
+ server_name=None
528
+ )
529
+
530
+ def _get_aws_fallback_data(self, service: str, operation: str) -> Dict[str, Any]:
531
+ """Get fallback data for AWS queries."""
532
+ return {
533
+ 'service': service,
534
+ 'operation': operation,
535
+ 'fallback': True,
536
+ 'recommendations': self._get_aws_recommendations(service, operation),
537
+ 'documentation_urls': self._get_aws_documentation_urls(service),
538
+ 'best_practices': self._get_aws_best_practices(service)
539
+ }
540
+
541
+ def _get_aws_recommendations(self, service: str, operation: str) -> List[str]:
542
+ """Get AWS service recommendations."""
543
+ recommendations = {
544
+ 's3': [
545
+ "Enable versioning for data protection",
546
+ "Use server-side encryption",
547
+ "Configure lifecycle policies",
548
+ "Enable access logging"
549
+ ],
550
+ 'ec2': [
551
+ "Use latest AMIs with security patches",
552
+ "Configure security groups with least privilege",
553
+ "Enable detailed monitoring",
554
+ "Use IAM roles instead of access keys"
555
+ ],
556
+ 'lambda': [
557
+ "Set appropriate timeout values",
558
+ "Use environment variables for configuration",
559
+ "Enable X-Ray tracing for debugging",
560
+ "Configure dead letter queues"
561
+ ]
562
+ }
563
+ return recommendations.get(service.lower(), [f"Follow AWS best practices for {service}"])
564
+
565
+ def _get_aws_documentation_urls(self, service: str) -> List[str]:
566
+ """Get AWS documentation URLs."""
567
+ base_url = "https://docs.aws.amazon.com"
568
+ urls = {
569
+ 's3': [f"{base_url}/s3/", f"{base_url}/s3/latest/userguide/"],
570
+ 'ec2': [f"{base_url}/ec2/", f"{base_url}/AWSEC2/latest/UserGuide/"],
571
+ 'lambda': [f"{base_url}/lambda/", f"{base_url}/lambda/latest/dg/"]
572
+ }
573
+ return urls.get(service.lower(), [f"{base_url}/{service.lower()}/"])
574
+
575
+ def _get_aws_best_practices(self, service: str) -> List[str]:
576
+ """Get AWS best practices."""
577
+ practices = {
578
+ 's3': [
579
+ "Use bucket policies and ACLs appropriately",
580
+ "Enable MFA delete for critical buckets",
581
+ "Monitor access with CloudTrail"
582
+ ],
583
+ 'ec2': [
584
+ "Use Auto Scaling for high availability",
585
+ "Implement proper backup strategies",
586
+ "Regular security updates"
587
+ ],
588
+ 'lambda': [
589
+ "Keep functions small and focused",
590
+ "Use layers for shared code",
591
+ "Monitor with CloudWatch"
592
+ ]
593
+ }
594
+ return practices.get(service.lower(), [f"Follow AWS Well-Architected Framework for {service}"])
595
+
596
+ def _get_terraform_fallback_data(self, provider: str, service: str, module_name: str) -> Dict[str, Any]:
597
+ """Get fallback data for Terraform queries."""
598
+ return {
599
+ 'provider': provider,
600
+ 'service': service,
601
+ 'module_name': module_name,
602
+ 'fallback': True,
603
+ 'recommended_modules': self._get_terraform_module_recommendations(provider, service),
604
+ 'provider_info': self._get_terraform_provider_info(provider),
605
+ 'usage_examples': self._get_terraform_usage_examples(provider, service)
606
+ }
607
+
608
+ def _get_terraform_module_recommendations(self, provider: str, service: str) -> List[Dict[str, str]]:
609
+ """Get Terraform module recommendations."""
610
+ modules = {
611
+ 'aws': {
612
+ 's3': [
613
+ {'name': 'terraform-aws-modules/s3-bucket/aws', 'description': 'AWS S3 bucket module'},
614
+ {'name': 'cloudposse/s3-bucket/aws', 'description': 'S3 bucket with additional features'}
615
+ ],
616
+ 'ec2': [
617
+ {'name': 'terraform-aws-modules/ec2-instance/aws', 'description': 'AWS EC2 instance module'},
618
+ {'name': 'terraform-aws-modules/autoscaling/aws', 'description': 'Auto Scaling Group module'}
619
+ ]
620
+ },
621
+ 'azure': {
622
+ 'vm': [
623
+ {'name': 'Azure/compute/azurerm', 'description': 'Azure Virtual Machine module'},
624
+ {'name': 'Azure/vm/azurerm', 'description': 'Simplified VM module'}
625
+ ],
626
+ 'storage': [
627
+ {'name': 'Azure/storage/azurerm', 'description': 'Azure Storage Account module'}
628
+ ]
629
+ }
630
+ }
631
+ return modules.get(provider.lower(), {}).get(service.lower(), [])
632
+
633
+ def _get_terraform_provider_info(self, provider: str) -> Dict[str, str]:
634
+ """Get Terraform provider information."""
635
+ providers = {
636
+ 'aws': {
637
+ 'source': 'hashicorp/aws',
638
+ 'documentation': 'https://registry.terraform.io/providers/hashicorp/aws/latest/docs'
639
+ },
640
+ 'azure': {
641
+ 'source': 'hashicorp/azurerm',
642
+ 'documentation': 'https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs'
643
+ },
644
+ 'google': {
645
+ 'source': 'hashicorp/google',
646
+ 'documentation': 'https://registry.terraform.io/providers/hashicorp/google/latest/docs'
647
+ }
648
+ }
649
+ return providers.get(provider.lower(), {'source': f'hashicorp/{provider}'})
650
+
651
+ def _get_terraform_usage_examples(self, provider: str, service: str) -> List[str]:
652
+ """Get Terraform usage examples."""
653
+ examples = {
654
+ 'aws': {
655
+ 's3': [
656
+ 'resource "aws_s3_bucket" "example" { bucket = "my-bucket" }',
657
+ 'resource "aws_s3_bucket_versioning" "example" { bucket = aws_s3_bucket.example.id }'
658
+ ]
659
+ }
660
+ }
661
+ return examples.get(provider.lower(), {}).get(service.lower(), [])
662
+
663
+ def _get_azure_fallback_data(self, service: str, intent: str, operation: str) -> Dict[str, Any]:
664
+ """Get fallback data for Azure queries."""
665
+ return {
666
+ 'service': service,
667
+ 'intent': intent,
668
+ 'operation': operation,
669
+ 'fallback': True,
670
+ 'service_info': self._get_azure_service_info(service),
671
+ 'best_practices': self._get_azure_best_practices(service),
672
+ 'documentation_links': self._get_azure_documentation_links(service),
673
+ 'cli_examples': self._get_azure_cli_examples(service, operation)
674
+ }
675
+
676
+ def _get_azure_service_info(self, service: str) -> Dict[str, str]:
677
+ """Get Azure service information."""
678
+ services = {
679
+ 'vm': {
680
+ 'name': 'Virtual Machines',
681
+ 'description': 'Scalable computing resources in Azure'
682
+ },
683
+ 'storage': {
684
+ 'name': 'Storage Accounts',
685
+ 'description': 'Scalable cloud storage for data and applications'
686
+ },
687
+ 'aks': {
688
+ 'name': 'Azure Kubernetes Service',
689
+ 'description': 'Managed Kubernetes container orchestration'
690
+ }
691
+ }
692
+ return services.get(service.lower(), {'name': service, 'description': f'Azure {service} service'})
693
+
694
+ def _get_azure_best_practices(self, service: str) -> List[str]:
695
+ """Get Azure best practices."""
696
+ practices = {
697
+ 'vm': [
698
+ "Use managed disks for better reliability",
699
+ "Configure backup and disaster recovery",
700
+ "Apply security updates regularly"
701
+ ],
702
+ 'storage': [
703
+ "Enable encryption at rest",
704
+ "Configure access policies",
705
+ "Use private endpoints for security"
706
+ ],
707
+ 'aks': [
708
+ "Use Azure AD integration",
709
+ "Enable network policies",
710
+ "Configure monitoring and logging"
711
+ ]
712
+ }
713
+ return practices.get(service.lower(), [f"Follow Azure best practices for {service}"])
714
+
715
+ def _get_azure_documentation_links(self, service: str) -> List[str]:
716
+ """Get Azure documentation links."""
717
+ base_url = "https://docs.microsoft.com/en-us/azure"
718
+ links = {
719
+ 'vm': [f"{base_url}/virtual-machines/"],
720
+ 'storage': [f"{base_url}/storage/"],
721
+ 'aks': [f"{base_url}/aks/"]
722
+ }
723
+ return links.get(service.lower(), [f"{base_url}/{service}/"])
724
+
725
+ def _get_azure_cli_examples(self, service: str, operation: str) -> List[str]:
726
+ """Get Azure CLI examples."""
727
+ examples = {
728
+ 'vm': [
729
+ "az vm create --resource-group myResourceGroup --name myVM",
730
+ "az vm list --output table"
731
+ ],
732
+ 'storage': [
733
+ "az storage account create --name mystorageaccount",
734
+ "az storage account list --output table"
735
+ ]
736
+ }
737
+ return examples.get(service.lower(), [f"az {service} --help"])
738
+
739
+ def _get_github_fallback_data(self, operation: str, repository: str, **kwargs) -> Dict[str, Any]:
740
+ """Get fallback data for GitHub queries."""
741
+ return {
742
+ 'operation': operation,
743
+ 'repository': repository,
744
+ 'parameters': self.security.mask_sensitive_data(kwargs),
745
+ 'fallback': True,
746
+ 'operation_info': self._get_github_operation_info(operation),
747
+ 'repository_info': self._get_github_repository_info(repository) if repository else None,
748
+ 'api_endpoints': self._get_github_api_endpoints(operation),
749
+ 'examples': self._get_github_operation_examples(operation)
750
+ }
751
+
752
+ def _get_github_operation_info(self, operation: str) -> Dict[str, str]:
753
+ """Get GitHub operation information."""
754
+ operations = {
755
+ 'list_issues': {
756
+ 'description': 'List issues in a repository',
757
+ 'method': 'GET'
758
+ },
759
+ 'create_pr': {
760
+ 'description': 'Create a pull request',
761
+ 'method': 'POST'
762
+ },
763
+ 'get_repo': {
764
+ 'description': 'Get repository information',
765
+ 'method': 'GET'
766
+ }
767
+ }
768
+ return operations.get(operation, {'description': f'GitHub {operation} operation'})
769
+
770
+ def _get_github_repository_info(self, repository: str) -> Dict[str, str]:
771
+ """Get GitHub repository information."""
772
+ if not repository or '/' not in repository:
773
+ return {'error': 'Invalid repository format. Use owner/repo'}
774
+
775
+ owner, repo = repository.split('/', 1)
776
+ return {
777
+ 'owner': owner,
778
+ 'repo': repo,
779
+ 'full_name': repository,
780
+ 'url': f'https://github.com/{repository}'
781
+ }
782
+
783
+ def _get_github_api_endpoints(self, operation: str) -> List[str]:
784
+ """Get GitHub API endpoints."""
785
+ endpoints = {
786
+ 'list_issues': ['/repos/{owner}/{repo}/issues'],
787
+ 'create_pr': ['/repos/{owner}/{repo}/pulls'],
788
+ 'get_repo': ['/repos/{owner}/{repo}']
789
+ }
790
+ return endpoints.get(operation, [f'/repos/{{owner}}/{{repo}}/{operation}'])
791
+
792
+ def _get_github_operation_examples(self, operation: str) -> List[str]:
793
+ """Get GitHub operation examples."""
794
+ examples = {
795
+ 'list_issues': [
796
+ 'GET /repos/owner/repo/issues',
797
+ 'GET /repos/owner/repo/issues?state=open'
798
+ ],
799
+ 'create_pr': [
800
+ 'POST /repos/owner/repo/pulls',
801
+ '{"title": "New feature", "head": "feature-branch", "base": "main"}'
802
+ ]
803
+ }
804
+ return examples.get(operation, [f'Example for {operation} operation'])
805
+
806
+
807
+ def create_default_mcp_config() -> Dict[str, Any]:
808
+ """
809
+ Create default MCP configuration with security considerations.
810
+
811
+ Returns:
812
+ Default MCP configuration
813
+ """
814
+ return {
815
+ 'mcp': {
816
+ 'servers': {
817
+ 'aws_docs': {
818
+ 'command': 'uvx',
819
+ 'args': ['awslabs.aws-documentation-mcp-server@latest'],
820
+ 'env': {
821
+ 'AWS_DOCUMENTATION_PARTITION': 'aws'
822
+ },
823
+ 'disabled': False,
824
+ 'auto_approve': ['read_documentation', 'search_documentation']
825
+ },
826
+ 'terraform': {
827
+ 'command': 'docker',
828
+ 'args': ['run', '-i', '--rm', 'hashicorp/terraform-mcp-server'],
829
+ 'env': {},
830
+ 'disabled': False,
831
+ 'auto_approve': []
832
+ },
833
+ 'azure': {
834
+ 'command': 'npx',
835
+ 'args': ['-y', '@azure/mcp@latest', 'server', 'start'],
836
+ 'env': {},
837
+ 'disabled': False,
838
+ 'auto_approve': ['documentation']
839
+ },
840
+ 'github': {
841
+ 'command': 'docker',
842
+ 'args': [
843
+ 'run', '-i', '--rm',
844
+ '-e', 'GITHUB_PERSONAL_ACCESS_TOKEN',
845
+ 'ghcr.io/github/github-mcp-server'
846
+ ],
847
+ 'env': {
848
+ # Note: Actual token should be set via environment variable
849
+ 'GITHUB_PERSONAL_ACCESS_TOKEN': 'your-github-token-here'
850
+ },
851
+ 'disabled': True, # Disabled by default for security
852
+ 'auto_approve': []
853
+ }
854
+ }
855
+ }
856
+ }