iam-policy-validator 1.15.5__py3-none-any.whl → 1.16.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.
@@ -72,8 +72,7 @@ def get_shared_fetcher(ctx: Any) -> AWSServiceFetcher | None:
72
72
  return lifespan_ctx["fetcher"]
73
73
 
74
74
  logger.warning(
75
- "Shared fetcher unavailable from context. "
76
- "A new fetcher instance will be created, which may impact performance."
75
+ "Shared fetcher unavailable from context. A new fetcher instance will be created, which may impact performance."
77
76
  )
78
77
  return None
79
78
 
@@ -109,340 +108,85 @@ def _get_cached_checks() -> tuple[dict[str, Any], ...]:
109
108
  # =============================================================================
110
109
 
111
110
  BASE_INSTRUCTIONS = """
112
- You are an AWS IAM security expert. Your mission: generate secure, least-privilege IAM policies that protect organizations from privilege escalation, data breaches, and unauthorized access.
113
-
114
- ## CORE SECURITY PRINCIPLES
115
-
116
- 1. **LEAST PRIVILEGE** - Grant only permissions needed for the specific task
117
- 2. **EXPLICIT DENY** - Use Deny statements for critical restrictions
118
- 3. **RESOURCE SCOPING** - Always scope to specific ARNs, never wildcards for write operations
119
- 4. **CONDITION GUARDS** - Add conditions for sensitive actions (MFA, IP, time, service principals)
120
- 5. **DEFENSE IN DEPTH** - Layer multiple security controls
121
-
122
- ## ABSOLUTE RULES (NEVER VIOLATE)
123
-
124
- - NEVER generate `"Action": "*"` - this grants full admin access
125
- - NEVER generate `"Resource": "*"` with write/delete/modify actions
126
- - NEVER allow `iam:*`, `sts:AssumeRole`, or `kms:*` without conditions
127
- - NEVER guess ARN formats - use query_arn_formats to get correct patterns
128
- - ALWAYS validate actions exist in AWS - typos create security gaps
129
- - ALWAYS present security_notes from generation tools to the user
130
-
131
- ## CRITICAL: NO VALIDATION LOOPS
132
-
133
- **HARD LIMIT: Maximum 2 validate_policy calls per policy request**
134
-
135
- After 2 validations, you MUST present the policy to the user regardless of remaining issues but listing them to the user and asking for futher instructions.
136
- Warnings (high/medium/low) are INFORMATIONAL - present them, don't try to fix them.
137
-
138
- ### What to Fix vs Present
139
-
140
- | Severity | What to do |
141
- | ----------------------- | ------------------------------------------------ |
142
- | error | Fix it - policy won't work in AWS |
143
- | critical | Fix it - severe security risk |
144
- | high/medium/low/warning | **STOP** - Present policy with these as warnings |
145
-
146
- ### Workflow (STRICT)
147
- 1. Generate policy using template or build_minimal_policy
148
- 2. validate_policy (call #1)
149
- 3. If "error" or "critical": apply fix from `example` field
150
- 4. validate_policy (call #2) - **THIS IS YOUR LAST VALIDATION**
151
- 5. **STOP** - Present policy to user with any remaining warnings
152
-
153
- ### You MUST present the policy when:
154
- - You have called validate_policy twice
155
- - Only high/medium/low/warning severity issues remain
156
- - The policy has no error/critical issues
157
- - You need user input (e.g., "what resource ARN?")
158
-
159
- ### Signs you are stuck in a loop (STOP NOW):
160
- - You've called validate_policy more than twice
161
- - The same warning keeps appearing
162
- - You're trying to "fix" high/medium/low severity issues
163
-
164
- **When in doubt: PRESENT THE POLICY. Let the user decide.**
165
-
166
- ## SENSITIVE ACTION CATEGORIES (490+ actions tracked)
167
-
168
- | Category | Risk | Examples | Required Mitigation |
169
- | -------------------- | -------- | ---------------------------------------- | --------------------------- |
170
- | credential_exposure | CRITICAL | sts:AssumeRole, iam:CreateAccessKey | MFA, source IP, time limits |
171
- | privilege_escalation | CRITICAL | iam:AttachUserPolicy, iam:PassRole | Strict resource scope, MFA |
172
- | data_access | HIGH | s3:GetObject, dynamodb:Scan | Resource scope, encryption |
173
- | resource_exposure | HIGH | s3:PutBucketPolicy, lambda:AddPermission | Explicit deny patterns |
174
-
175
- ## POLICY GENERATION WORKFLOW
176
-
177
- ```
178
- ┌─────────────────────────────────────────────────────────────────────┐
179
- │ 1. UNDERSTAND THE REQUEST │
180
- │ → What AWS service(s)? │
181
- │ → What operations (read/write/admin)? │
182
- │ → What specific resources (ARNs)? │
183
- │ → What's the principal (Lambda, EC2, role)? |
184
- │ → Are there existing org restrictions? (get_organization_config) │
185
- └─────────────────────────────────────────────────────────────────────┘
186
-
187
- ┌─────────────────────────────────────────────────────────────────┐
188
- │ 2. CHOOSE GENERATION APPROACH │
189
- │ │
190
- │ Template exists? → generate_policy_from_template │
191
- │ Custom needs? → build_minimal_policy │
192
- │ Unknown actions? → suggest_actions → build_minimal_policy │
193
- │ │
194
- │ Use list_templates first to check for pre-built secure │
195
- │ templates (s3-read-only, lambda-basic-execution, etc.) │
196
- └─────────────────────────────────────────────────────────────────┘
197
-
198
- ┌─────────────────────────────────────────────────────────────────┐
199
- │ 3. VALIDATE ONCE │
200
- │ │
201
- │ validate_policy → check for issues │
202
- │ ⚠️ VALIDATE ONLY ONCE - DO NOT LOOP │
203
- │ │
204
- │ BLOCKING (must fix before presenting): │
205
- │ → severity="error" - Policy won't work in AWS │
206
- │ → severity="critical" - Severe security risk │
207
- │ │
208
- │ NON-BLOCKING (present with warnings): │
209
- │ → severity="high/medium/low/warning" - Security advice │
210
- │ → Present policy WITH these warnings, let user decide │
211
- └─────────────────────────────────────────────────────────────────┘
212
-
213
- ┌─────────────────────────────────────────────────────────────────┐
214
- │ 4. PRESENT TO USER (even with warnings) │
215
- │ │
216
- │ → Show the policy immediately after ONE validation │
217
- │ → List any warnings/suggestions for user awareness │
218
- │ → DO NOT keep fixing and re-validating in a loop │
219
- │ → Let the user decide if they want changes │
220
- └─────────────────────────────────────────────────────────────────┘
221
- ```
222
-
223
- ## TOOL SELECTION GUIDE
224
-
225
- | Task | Primary Tool | Fallback |
226
- | --------------------- | ---------------------------------------------- | ---------------------------- |
227
- | Create policy | list_templates → generate_policy_from_template | build_minimal_policy |
228
- | Validate policy | validate_policy | quick_validate (summary) |
229
- | Fix structural issues | fix_policy_issues | - |
230
- | Get fix guidance | get_issue_guidance | read issue.example field |
231
- | Find actions | query_service_actions | suggest_actions |
232
- | Check action risks | check_sensitive_actions | get_required_conditions |
233
- | Get ARN formats | query_arn_formats | query_action_details |
234
- | Expand wildcards | expand_wildcard_action | - |
235
- | Batch operations | validate_policies_batch, query_actions_batch | - |
236
- | Session config | set_organization_config, check_org_compliance | - |
237
-
238
- ### Tool Hierarchy (prefer tools higher in list)
239
- 1. **validate_policy** - Full validation with detailed issue information
240
- 2. **fix_policy_issues** - Structural fixes only (Version, SIDs, action case)
241
- 3. **get_issue_guidance** - Detailed fix instructions for specific check_ids
242
-
243
- ## ANTI-PATTERNS TO PREVENT
244
-
245
- 1. **Overly Broad Resources**
246
- BAD: `"Resource": "arn:aws:s3:::*"`
247
- GOOD: `"Resource": "arn:aws:s3:::my-specific-bucket/*"`
248
-
249
- 2. **Service Wildcards Without Conditions**
250
- BAD: `"Action": "s3:*"`
251
- GOOD: `"Action": ["s3:GetObject", "s3:ListBucket"]` with specific resources
252
-
253
- 3. **PassRole Without Service Restriction**
254
- BAD: `"Action": "iam:PassRole", "Resource": "*"`
255
- GOOD: Add `"Condition": {"StringEquals": {"iam:PassedToService": "lambda.amazonaws.com"}}`
256
-
257
- 4. **Missing Secure Transport**
258
- For S3, always add: `"Condition": {"StringLike": {"aws:ResourceAccount": "<aws-account-id> OR ${aws:PrincipalAccount}"}}`
259
-
260
- 5. **Cross-Account Without Controls**
261
- If Principal includes external accounts, require: source IP, or org restrictions
262
-
263
- ## TRUST POLICY SPECIFICS
264
-
265
- Trust policies control WHO can assume a role. Key differences:
266
- - Principal is REQUIRED (AWS account, service, or federated user)
267
- - Resource is NOT used (the role itself is the resource)
268
- - Action is typically `sts:AssumeRole` only
269
-
270
- Use validate_policy with auto-detection - it recognizes trust policies automatically.
271
- For cross-account: generate_policy_from_template("cross-account-assume-role")
272
-
273
- ## HANDLING USER REQUESTS
274
-
275
- **"Give me full access to..."**
276
- → Explain the security risks
277
- → Ask: "What specific operations do you need?"
278
- → Use suggest_actions to find minimal permissions
279
- → Never generate `"Action": "*"`
280
-
281
- **"Just make it work"**
282
- → Still apply least privilege
283
- → Validate thoroughly
284
- → Present with security_notes explaining any risks
285
-
286
- **After 3 failed fix attempts**
287
- → Stop and ask user for clarification
288
- → Present the specific blockers clearly
289
- → Suggest alternatives
290
-
291
- ## EXAMPLE INTERACTIONS
292
-
293
- ### Example 1: Lambda needs S3 access
294
- User: "Create a policy for my Lambda to read from S3 bucket my-data-bucket"
295
-
296
- Your workflow:
297
- 1. list_templates → find "s3-read-only" template
298
- 2. generate_policy_from_template("s3-read-only", {"bucket_name": "my-data-bucket"})
299
- 3. validate_policy ONCE → check result
300
- 4. Present policy to user with any warnings (DO NOT re-validate)
301
-
302
- ### Example 2: User requests overly broad access
303
- User: "Give me full S3 access"
304
-
305
- Your workflow:
306
- 1. Ask: "What specific S3 operations do you need? (read, write, delete, list)"
307
- 2. After clarification → query_service_actions("s3", access_level="read")
308
- 3. build_minimal_policy with specific actions and resources
309
- 4. validate_policy ONCE and present immediately (warnings are informational)
310
-
311
- ### Example 3: Validation returns warnings (DO NOT LOOP)
312
- After validate_policy returns warnings like "wildcard_resource" or "sensitive_action":
313
-
314
- WRONG approach (causes infinite loop):
315
- ❌ validate → fix → validate → fix → validate...
316
-
317
- CORRECT approach:
318
- ✅ validate ONCE → present policy WITH warnings → let user decide
319
- ✅ Say: "Here's your policy. Note: it has these security considerations: [list warnings]"
320
- ✅ Only fix if user explicitly asks for changes
321
-
322
- ## VALIDATION ISSUE FIELDS
323
-
324
- Each issue contains actionable guidance:
325
- - `severity`: error/warning/critical/high/medium/low
326
- - `message`: What's wrong
327
- - `suggestion`: How to fix it
328
- - `example`: **USE THIS** - shows the exact correct format
329
- - `check_id`: For get_issue_guidance lookup
330
- - `risk_explanation`: Why this matters
331
- - `remediation_steps`: Step-by-step fix
332
-
333
- ## RESOURCES AND PROMPTS AVAILABLE
334
-
335
- ### Prompts (use for guided workflows)
336
- - `generate_secure_policy` - Step-by-step policy creation with validation
337
- - `fix_policy_issues_workflow` - Systematic issue fixing (max 2 iterations)
338
- - `review_policy_security` - Security analysis without modification
339
-
340
- ### Resources (reference data)
341
- - `iam://templates` - Pre-built secure templates
342
- - `iam://checks` - All 19 validation checks
343
- - `iam://sensitive-categories` - Sensitive action categories
344
- - `iam://config-schema` - Configuration settings schema
345
- - `iam://config-examples` - Example configurations
346
- - `iam://workflow-examples` - Detailed step-by-step examples
347
-
348
- Read iam://workflow-examples for comprehensive usage patterns.
349
-
350
- ## IAM ACTION AND POLICY FORMATTING RULES
351
-
352
- ### Action Formatting (CRITICAL)
353
- - **Service prefix MUST be lowercase**: `s3:GetObject` ✓, `S3:GetObject` ✗
354
- - **Action name uses PascalCase**: `s3:GetObject` ✓, `s3:getobject` ✗
355
- - **Full format**: `<service>:<ActionName>` (e.g., `lambda:InvokeFunction`)
356
- - **Wildcards**: Use `*` for patterns (`s3:Get*`, `s3:*Object`, `s3:*`)
357
- - **Common mistakes to avoid**:
358
- - `S3:GetObject` → should be `s3:GetObject` (lowercase service)
359
- - `s3:getObject` → should be `s3:GetObject` (PascalCase action)
360
- - `s3.GetObject` → should be `s3:GetObject` (colon separator)
361
- - `arn:aws:s3:::bucket` in Action → Actions are not ARNs
362
-
363
- ### Policy Structure (REQUIRED FORMAT)
364
- ```json
365
- {
366
- "Version": "2012-10-17",
367
- "Statement": [
368
- {
369
- "Sid": "UniqueStatementId",
370
- "Effect": "Allow",
371
- "Action": ["service:ActionName"],
372
- "Resource": ["arn:aws:service:region:account:resource"]
373
- }
374
- ]
375
- }
376
- ```
377
-
378
- ### Version Field
379
- - ALWAYS use `"Version": "2012-10-17"` (current version)
380
- - `"2008-10-17"` is deprecated and lacks features like policy variables
381
-
382
- ### Statement Fields
383
- | Field | Required | Type | Valid Values |
384
- | ----------- | ----------- | ------------- | ------------------------------------------- |
385
- | Effect | Yes | string | `"Allow"` or `"Deny"` |
386
- | Action | Yes* | string/array | Service actions like `"s3:GetObject"` |
387
- | NotAction | No* | string/array | Actions to exclude |
388
- | Resource | Yes* | string/array | ARNs like `"arn:aws:s3:::bucket/*"` |
389
- | NotResource | No* | string/array | Resources to exclude |
390
- | Principal | Conditional | string/object | For resource policies only |
391
- | Condition | No | object | Condition operators and keys |
392
- | Sid | No | string | Statement identifier (unique within policy) |
393
-
394
- *Either Action or NotAction required; Either Resource or NotResource required
395
-
396
- ### Resource ARN Formatting
397
- - **Format**: `arn:aws:<service>:<region>:<account>:<resource>`
398
- - **S3 buckets**: `arn:aws:s3:::<bucket-name>` (no region/account)
399
- - **S3 objects**: `arn:aws:s3:::<bucket-name>/<key-path>`
400
- - **DynamoDB tables**: `arn:aws:dynamodb:<region>:<account>:table/<table-name>`
401
- - **Lambda functions**: `arn:aws:lambda:<region>:<account>:function:<function-name>`
402
- - **Wildcards**: Use `*` for patterns (`arn:aws:s3:::my-bucket/*`)
403
- - Use query_arn_formats to get correct ARN patterns for any service
404
-
405
- ### Condition Block Formatting
111
+ You are an AWS IAM security expert generating secure, least-privilege policies.
112
+
113
+ ## CORE PRINCIPLES
114
+ - LEAST PRIVILEGE: Only permissions needed for the task
115
+ - RESOURCE SCOPING: Specific ARNs, never wildcards for write operations
116
+ - CONDITION GUARDS: Add conditions for sensitive actions (MFA, IP, time)
117
+
118
+ ## ABSOLUTE RULES (GUARDRAIL: DO NOT REMOVE)
119
+ - NEVER generate `"Action": "*"` or `"Resource": "*"` with write actions
120
+ - NEVER allow `iam:*`, `sts:AssumeRole`, `kms:*` without conditions
121
+ - NEVER guess ARN formats - use query_arn_formats
122
+ - ALWAYS validate actions exist - typos create security gaps
123
+ - ALWAYS present security_notes from generation tools
124
+
125
+ ## VALIDATION LOOP PREVENTION (GUARDRAIL: DO NOT REMOVE)
126
+
127
+ **HARD LIMIT: Maximum 2 validate_policy calls per request**
128
+
129
+ | Severity | Action |
130
+ |----------|--------|
131
+ | error/critical | Fix using `example` field |
132
+ | high/medium/low/warning | **PRESENT AS-IS** - informational only |
133
+
134
+ **Workflow:**
135
+ 1. Generate policy (template or build_minimal_policy)
136
+ 2. validate_policy (call #1) → fix error/critical only
137
+ 3. validate_policy (call #2) **FINAL - present policy with any warnings**
138
+
139
+ **Stop signs:** Called validate >2 times, same warning repeating, trying to "fix" warnings.
140
+ **When in doubt: PRESENT THE POLICY.**
141
+
142
+ ## SENSITIVE ACTIONS (490+ tracked)
143
+ - credential_exposure (CRITICAL): sts:AssumeRole, iam:CreateAccessKey MFA, IP limits
144
+ - privilege_escalation (CRITICAL): iam:AttachUserPolicy, iam:PassRole → resource scope
145
+ - data_access/resource_exposure (HIGH): s3:GetObject, s3:PutBucketPolicy → scope, encryption
146
+
147
+ ## TOOL QUICK REFERENCE
148
+ | Task | Tool |
149
+ |------|------|
150
+ | Create policy | list_templates generate_policy_from_template, or build_minimal_policy |
151
+ | Validate | validate_policy (full) or quick_validate (summary) |
152
+ | Fix structure | fix_policy_issues (Version, SIDs, case) |
153
+ | Fix guidance | get_issue_guidance or issue.example field |
154
+ | Find actions | query_service_actions or suggest_actions |
155
+ | ARN formats | query_arn_formats |
156
+ | Batch ops | validate_policies_batch, query_actions_batch |
157
+
158
+ ## CONDITION KEYS (IMPORTANT)
159
+ When adding conditions, check BOTH:
160
+ 1. **Action conditions**: Use get_required_conditions or query_action_details for action-specific keys
161
+ 2. **Resource conditions**: Use query_condition_keys(service) - resources have their own condition keys
162
+ Example: s3:GetObject supports action conditions AND s3 bucket/object resource conditions (s3:prefix, etc.)
163
+
164
+ ## ANTI-PATTERNS
165
+ - `"Resource": "arn:aws:s3:::*"` use specific bucket ARN
166
+ - `"Action": "s3:*"` → use specific actions with resources
167
+ - `iam:PassRole` without `iam:PassedToService` condition
168
+
169
+ ## TRUST POLICIES
170
+ Principal required, Resource not used. Auto-detected by validate_policy.
171
+ Use generate_policy_from_template("cross-account-assume-role") for cross-account.
172
+
173
+ ## IAM ACTION FORMAT (CRITICAL)
174
+ Format: `<service>:<ActionName>` (e.g., `s3:GetObject`, `lambda:InvokeFunction`)
175
+ - Service: lowercase (`s3`, not `S3`)
176
+ - Action: PascalCase (`GetObject`, not `getobject`)
177
+ - Wildcards: `s3:Get*`, `s3:*`
178
+
179
+ ## POLICY STRUCTURE
406
180
  ```json
407
- "Condition": {
408
- "<ConditionOperator>": {
409
- "<ConditionKey>": "<value>"
410
- }
411
- }
181
+ {"Version": "2012-10-17", "Statement": [{"Effect": "Allow", "Action": ["s3:GetObject"], "Resource": ["arn:aws:s3:::bucket/*"]}]}
412
182
  ```
183
+ - Version: Always "2012-10-17"
184
+ - Effect: "Allow" or "Deny" (exact case)
185
+ - Use query_arn_formats for correct ARN patterns
413
186
 
414
- **Common operators**:
415
- - `StringEquals`, `StringNotEquals`, `StringLike`, `StringNotLike`
416
- - `ArnEquals`, `ArnLike`, `ArnNotEquals`, `ArnNotLike`
417
- - `NumericEquals`, `NumericLessThan`, `NumericGreaterThan`
418
- - `DateEquals`, `DateLessThan`, `DateGreaterThan`
419
- - `Bool` (for boolean conditions like `aws:SecureTransport`)
420
- - `IpAddress`, `NotIpAddress` (for source IP restrictions)
421
-
422
- **Set operators** (for multi-value keys):
423
- - `ForAllValues:StringEquals` - All values must match
424
- - `ForAnyValue:StringEquals` - At least one value must match
425
-
426
- ### Principal Formatting (Resource Policies Only)
427
- ```json
428
- "Principal": {
429
- "AWS": "arn:aws:iam::123456789012:role/RoleName"
430
- }
431
- ```
432
- Or for service principals:
433
- ```json
434
- "Principal": {
435
- "Service": "lambda.amazonaws.com"
436
- }
437
- ```
438
-
439
- ### Common Formatting Errors to Catch
440
- 1. **Missing Version**: Always include `"Version": "2012-10-17"`
441
- 2. **Effect typos**: `"allow"` → `"Allow"`, `"DENY"` → `"Deny"`
442
- 3. **Invalid ARN format**: Missing colons or wrong segment count
443
- 4. **Single string vs array**: `"Action": "s3:GetObject"` works but `["s3:GetObject"]` preferred
444
-
445
- ALWAYS validate actions exist using query_action_details or validate_policy before presenting to users.
187
+ ## RESOURCES
188
+ - iam://templates, iam://checks, iam://workflow-examples
189
+ - Prompts: generate_secure_policy, fix_policy_issues_workflow, review_policy_security
446
190
  """
447
191
 
448
192
 
@@ -480,37 +224,18 @@ async def validate_policy(
480
224
  verbose: bool = True,
481
225
  use_org_config: bool = True,
482
226
  ) -> dict[str, Any]:
483
- """Validate an IAM policy.
484
-
485
- Validates a policy against AWS IAM rules and security best practices.
486
- Runs all enabled checks and returns validation results.
487
-
488
- Policy Type Auto-Detection:
489
- If policy_type is None (default), the policy type is automatically detected:
490
- - "trust" if contains sts:AssumeRole action (trust/assume role policy)
491
- - "resource" if contains Principal/NotPrincipal (resource-based policy)
492
- - "identity" otherwise (identity-based policy attached to users/roles/groups)
227
+ """Validate an IAM policy against AWS rules and security best practices.
493
228
 
494
- If an organization config is set and use_org_config=True, the validation
495
- will use organization-specific check overrides, ignore patterns, and
496
- severity settings.
229
+ Auto-detects policy type (identity/resource/trust) from structure if not specified.
497
230
 
498
231
  Args:
499
- policy: IAM policy as a dictionary
500
- policy_type: Type of policy to validate. If None (default), auto-detects from structure.
501
- Explicit options:
502
- - "identity": Identity-based policy (attached to users/roles/groups)
503
- - "resource": Resource-based policy (attached to resources like S3 buckets)
504
- - "trust": Trust policy (role assumption policy)
505
- verbose: If True (default), return all issue fields. If False, return only
506
- essential fields (severity, message, suggestion, check_id) to reduce tokens.
507
- use_org_config: Whether to apply session organization config (default: True)
232
+ policy: IAM policy dictionary
233
+ policy_type: "identity", "resource", or "trust" (auto-detected if None)
234
+ verbose: Return all fields (True) or essential only (False)
235
+ use_org_config: Apply session org config (default: True)
508
236
 
509
237
  Returns:
510
- Dictionary with:
511
- - is_valid: True if no errors/warnings found
512
- - issues: List of validation issues
513
- - policy_file: Source identifier
238
+ {is_valid, issues, policy_file}
514
239
  """
515
240
  from iam_validator.mcp.tools.validation import validate_policy as _validate
516
241
 
@@ -556,19 +281,13 @@ async def validate_policy(
556
281
 
557
282
  @mcp.tool()
558
283
  async def quick_validate(policy: dict[str, Any]) -> dict[str, Any]:
559
- """Quick pass/fail validation check for a policy.
560
-
561
- Lightweight validation that returns essential information:
562
- whether the policy is valid, number of issues, and critical issues.
284
+ """Quick pass/fail validation returning only essential info.
563
285
 
564
286
  Args:
565
- policy: IAM policy as a Python dictionary
287
+ policy: IAM policy dictionary
566
288
 
567
289
  Returns:
568
- Dictionary with:
569
- - is_valid: Whether the policy passed validation
570
- - issue_count: Total number of issues found
571
- - critical_issues: List of critical/high severity issue messages
290
+ {is_valid, issue_count, critical_issues}
572
291
  """
573
292
  from iam_validator.mcp.tools.validation import quick_validate as _quick_validate
574
293
 
@@ -584,71 +303,54 @@ async def quick_validate(policy: dict[str, Any]) -> dict[str, Any]:
584
303
  async def generate_policy_from_template(
585
304
  template_name: str,
586
305
  variables: dict[str, str],
306
+ verbose: bool = False,
587
307
  ) -> dict[str, Any]:
588
308
  """Generate an IAM policy from a built-in template.
589
309
 
590
- IMPORTANT: Call list_templates first to see available templates and their
591
- required variables with descriptions.
310
+ Call list_templates first to see available templates and required variables.
592
311
 
593
312
  Args:
594
- template_name: Template name from list_templates. Common templates:
595
- - s3-read-only: Read from S3 bucket
596
- - s3-read-write: Read/write to S3 bucket
597
- - lambda-basic-execution: Basic Lambda with CloudWatch logs
598
- - lambda-s3-trigger: Lambda triggered by S3 events
599
- - dynamodb-crud: DynamoDB table operations
600
- - cloudwatch-logs: Write to CloudWatch Logs
601
- variables: Dictionary of variable values. Get required variables from
602
- list_templates. Common variables:
603
- - bucket_name: S3 bucket name (without arn: prefix)
604
- - function_name: Lambda function name
605
- - table_name: DynamoDB table name
606
- - account_id: 12-digit AWS account ID
607
- - region: AWS region (e.g., us-east-1)
313
+ template_name: Template name (e.g., "s3-read-only", "lambda-basic-execution")
314
+ variables: Template variables (e.g., {"bucket_name": "my-bucket", "account_id": "123456789012"})
315
+ verbose: Return all fields (True) or essential only (False)
608
316
 
609
317
  Returns:
610
- Dictionary with:
611
- - policy: The generated IAM policy (ready to use)
612
- - validation: Validation results with any issues found
613
- - security_notes: Security warnings to review
614
- - template_used: Template name for reference
615
-
616
- Example:
617
- # First check what variables lambda-s3-trigger needs:
618
- templates = await list_templates()
619
-
620
- # Then generate with all required variables:
621
- result = await generate_policy_from_template(
622
- template_name="lambda-s3-trigger",
623
- variables={
624
- "bucket_name": "my-bucket",
625
- "function_name": "my-function",
626
- "account_id": "123456789012",
627
- "region": "us-east-1"
628
- }
629
- )
318
+ {policy, validation, security_notes, template_used}
630
319
  """
631
320
  from iam_validator.mcp.tools.generation import (
632
321
  generate_policy_from_template as _generate,
633
322
  )
634
323
 
635
324
  result = await _generate(template_name=template_name, variables=variables)
325
+
326
+ if verbose:
327
+ issues = [
328
+ {
329
+ "severity": issue.severity,
330
+ "message": issue.message,
331
+ "suggestion": issue.suggestion,
332
+ "example": issue.example,
333
+ "check_id": issue.check_id,
334
+ "risk_explanation": issue.risk_explanation,
335
+ "remediation_steps": issue.remediation_steps,
336
+ }
337
+ for issue in result.validation.issues
338
+ ]
339
+ else:
340
+ issues = [
341
+ {
342
+ "severity": issue.severity,
343
+ "message": issue.message,
344
+ "check_id": issue.check_id,
345
+ }
346
+ for issue in result.validation.issues
347
+ ]
348
+
636
349
  return {
637
350
  "policy": result.policy,
638
351
  "validation": {
639
352
  "is_valid": result.validation.is_valid,
640
- "issues": [
641
- {
642
- "severity": issue.severity,
643
- "message": issue.message,
644
- "suggestion": issue.suggestion,
645
- "example": issue.example,
646
- "check_id": issue.check_id,
647
- "risk_explanation": issue.risk_explanation,
648
- "remediation_steps": issue.remediation_steps,
649
- }
650
- for issue in result.validation.issues
651
- ],
353
+ "issues": issues,
652
354
  },
653
355
  "security_notes": result.security_notes,
654
356
  "template_used": result.template_used,
@@ -660,43 +362,51 @@ async def build_minimal_policy(
660
362
  actions: list[str],
661
363
  resources: list[str],
662
364
  conditions: dict[str, Any] | None = None,
365
+ verbose: bool = False,
663
366
  ) -> dict[str, Any]:
664
367
  """Build a minimal IAM policy from explicit actions and resources.
665
368
 
666
- Constructs a policy statement from provided actions and resources.
667
- Validates that actions exist in AWS using built-in checks, warns about
668
- sensitive actions, and returns security notes from validation.
669
-
670
369
  Args:
671
- actions: List of AWS actions (e.g., ["s3:GetObject", "s3:ListBucket"])
672
- resources: List of resource ARNs (e.g., ["arn:aws:s3:::my-bucket/*"])
673
- conditions: Optional conditions to add to the statement
370
+ actions: AWS actions (e.g., ["s3:GetObject", "s3:ListBucket"])
371
+ resources: Resource ARNs (e.g., ["arn:aws:s3:::my-bucket/*"])
372
+ conditions: Optional conditions to add
373
+ verbose: Return all fields (True) or essential only (False)
674
374
 
675
375
  Returns:
676
- Dictionary with:
677
- - policy: The generated IAM policy
678
- - validation: Validation results from built-in checks
679
- - security_notes: Security warnings from validation
376
+ {policy, validation, security_notes}
680
377
  """
681
378
  from iam_validator.mcp.tools.generation import build_minimal_policy as _build
682
379
 
683
380
  result = await _build(actions=actions, resources=resources, conditions=conditions)
381
+
382
+ if verbose:
383
+ issues = [
384
+ {
385
+ "severity": issue.severity,
386
+ "message": issue.message,
387
+ "suggestion": issue.suggestion,
388
+ "example": issue.example,
389
+ "check_id": issue.check_id,
390
+ "risk_explanation": issue.risk_explanation,
391
+ "remediation_steps": issue.remediation_steps,
392
+ }
393
+ for issue in result.validation.issues
394
+ ]
395
+ else:
396
+ issues = [
397
+ {
398
+ "severity": issue.severity,
399
+ "message": issue.message,
400
+ "check_id": issue.check_id,
401
+ }
402
+ for issue in result.validation.issues
403
+ ]
404
+
684
405
  return {
685
406
  "policy": result.policy,
686
407
  "validation": {
687
408
  "is_valid": result.validation.is_valid,
688
- "issues": [
689
- {
690
- "severity": issue.severity,
691
- "message": issue.message,
692
- "suggestion": issue.suggestion,
693
- "example": issue.example,
694
- "check_id": issue.check_id,
695
- "risk_explanation": issue.risk_explanation,
696
- "remediation_steps": issue.remediation_steps,
697
- }
698
- for issue in result.validation.issues
699
- ],
409
+ "issues": issues,
700
410
  },
701
411
  "security_notes": result.security_notes,
702
412
  }
@@ -704,19 +414,12 @@ async def build_minimal_policy(
704
414
 
705
415
  @mcp.tool()
706
416
  async def list_templates() -> list[dict[str, Any]]:
707
- """List all available policy templates with their required variables.
417
+ """List available policy templates and their required variables.
708
418
 
709
- IMPORTANT: Always call this BEFORE generate_policy_from_template to see
710
- what variables each template requires.
419
+ Call this before generate_policy_from_template.
711
420
 
712
421
  Returns:
713
- List of template dictionaries, each containing:
714
- - name: Template identifier (pass to generate_policy_from_template)
715
- - description: What the template does
716
- - variables: List of required variables with:
717
- - name: Variable name (key for the variables dict)
718
- - description: What value to provide (e.g., "AWS account ID (12-digit number)")
719
- - required: Whether the variable must be provided
422
+ List of {name, description, variables}
720
423
  """
721
424
  from iam_validator.mcp.tools.generation import list_templates as _list_templates
722
425
 
@@ -728,14 +431,11 @@ async def suggest_actions(
728
431
  description: str,
729
432
  service: str | None = None,
730
433
  ) -> list[str]:
731
- """Suggest AWS actions based on a natural language description.
732
-
733
- Uses keyword pattern matching to suggest appropriate AWS actions.
734
- Useful for discovering actions when building policies.
434
+ """Suggest AWS actions based on natural language description.
735
435
 
736
436
  Args:
737
- description: Natural language description (e.g., "read files from S3")
738
- service: Optional AWS service to limit suggestions (e.g., "s3", "lambda")
437
+ description: What you need (e.g., "read files from S3")
438
+ service: Optional service filter (e.g., "s3", "lambda")
739
439
 
740
440
  Returns:
741
441
  List of suggested action names
@@ -747,16 +447,15 @@ async def suggest_actions(
747
447
 
748
448
  @mcp.tool()
749
449
  async def get_required_conditions(actions: list[str]) -> dict[str, Any]:
750
- """Get the conditions required for a list of actions.
450
+ """Get recommended IAM conditions for actions based on security best practices.
751
451
 
752
- Analyzes actions and returns condition requirements based on security
753
- best practices (e.g., MFA for sensitive actions, IP restrictions).
452
+ NOTE: Also check query_condition_keys(service) for resource-level conditions.
754
453
 
755
454
  Args:
756
- actions: List of AWS actions to analyze
455
+ actions: AWS actions to analyze (e.g., ["iam:PassRole"])
757
456
 
758
457
  Returns:
759
- Dictionary mapping condition keys to required values, grouped by type
458
+ Condition requirements grouped by type
760
459
  """
761
460
  from iam_validator.mcp.tools.generation import (
762
461
  get_required_conditions as _get_conditions,
@@ -766,53 +465,40 @@ async def get_required_conditions(actions: list[str]) -> dict[str, Any]:
766
465
 
767
466
 
768
467
  @mcp.tool()
769
- async def check_sensitive_actions(actions: list[str]) -> dict[str, Any]:
770
- """Check if any actions are sensitive and get remediation guidance.
468
+ async def check_sensitive_actions(
469
+ actions: list[str],
470
+ verbose: bool = False,
471
+ ) -> dict[str, Any]:
472
+ """Check if actions are sensitive and get remediation guidance.
771
473
 
772
- Analyzes actions against the sensitive actions catalog (490+ actions)
773
- and returns risk category, severity, and **REMEDIATION GUIDANCE** including
774
- recommended IAM conditions and mitigation steps.
474
+ Analyzes actions against 490+ sensitive actions catalog. Also verify
475
+ resource-level conditions with query_condition_keys(service).
775
476
 
776
477
  Args:
777
- actions: List of AWS actions to check (e.g., ["iam:PassRole", "s3:GetObject"])
478
+ actions: Actions to check (e.g., ["iam:PassRole", "s3:GetObject"])
479
+ verbose: Return all fields (True) or essential only (False)
778
480
 
779
481
  Returns:
780
- Dictionary with:
781
- - sensitive_actions: List of dictionaries for each sensitive action found
782
- - action: The action name
783
- - category: Risk category (credential_exposure, data_access, priv_esc, resource_exposure)
784
- - severity: Severity level (critical or high)
785
- - description: Category description
786
- - remediation: Mitigation guidance including:
787
- - risk_level: CRITICAL or HIGH
788
- - why_dangerous: Explanation of the risk
789
- - recommended_conditions: List of IAM conditions to add
790
- - mitigation_steps: Steps to reduce risk
791
- - condition_example: JSON example of the condition block to add
792
- - specific_guidance: Action-specific advice (for iam:PassRole, sts:AssumeRole, etc.)
793
- - total_checked: Number of actions checked
794
- - sensitive_count: Number of sensitive actions found
795
- - categories_found: List of unique risk categories found
796
- - has_critical: Whether any critical severity actions were found
797
- - summary: Quick summary with top recommendations
798
-
799
- Example response for iam:PassRole:
800
- {
801
- "action": "iam:PassRole",
802
- "category": "priv_esc",
803
- "severity": "critical",
804
- "remediation": {
805
- "recommended_conditions": [{"condition": "iam:PassedToService", ...}],
806
- "condition_example": {"Condition": {"StringEquals": {"iam:PassedToService": "lambda.amazonaws.com"}}},
807
- "specific_guidance": "Always restrict iam:PassRole to specific services..."
808
- }
809
- }
482
+ {sensitive_actions, total_checked, sensitive_count, categories_found, has_critical, summary}
810
483
  """
811
484
  from iam_validator.mcp.tools.generation import (
812
485
  check_sensitive_actions as _check_sensitive,
813
486
  )
814
487
 
815
- return await _check_sensitive(actions=actions)
488
+ result = await _check_sensitive(actions=actions)
489
+
490
+ if not verbose and "sensitive_actions" in result:
491
+ # Lean response: only essential fields per action
492
+ result["sensitive_actions"] = [
493
+ {
494
+ "action": sa.get("action"),
495
+ "category": sa.get("category"),
496
+ "severity": sa.get("severity"),
497
+ }
498
+ for sa in result.get("sensitive_actions", [])
499
+ ]
500
+
501
+ return result
816
502
 
817
503
 
818
504
  # =============================================================================
@@ -826,20 +512,19 @@ async def query_service_actions(
826
512
  access_level: str | None = None,
827
513
  limit: int | None = None,
828
514
  offset: int = 0,
515
+ verbose: bool = False,
829
516
  ) -> dict[str, Any]:
830
517
  """Get all actions for a service, optionally filtered by access level.
831
518
 
832
519
  Args:
833
- service: AWS service prefix (e.g., "s3", "iam", "ec2")
834
- access_level: Optional filter (read|write|list|tagging|permissions-management)
835
- limit: Maximum number of actions to return (default: all)
836
- offset: Number of actions to skip for pagination (default: 0)
520
+ service: Service prefix (e.g., "s3", "iam", "ec2")
521
+ access_level: Filter: read|write|list|tagging|permissions-management
522
+ limit: Max actions to return
523
+ offset: Skip N actions for pagination
524
+ verbose: Return full action details (True) or names only (False)
837
525
 
838
526
  Returns:
839
- Dictionary with:
840
- - actions: List of action names
841
- - total: Total number of actions available
842
- - has_more: Whether more actions are available
527
+ {actions, total, has_more}
843
528
  """
844
529
  from iam_validator.mcp.tools.query import query_service_actions as _query
845
530
 
@@ -852,6 +537,10 @@ async def query_service_actions(
852
537
  if limit:
853
538
  all_actions = all_actions[:limit]
854
539
 
540
+ # Lean response: just action names as strings if not verbose
541
+ if not verbose and all_actions and isinstance(all_actions[0], dict):
542
+ all_actions = [a.get("name", a) if isinstance(a, dict) else a for a in all_actions]
543
+
855
544
  return {
856
545
  "actions": all_actions,
857
546
  "total": total,
@@ -861,14 +550,13 @@ async def query_service_actions(
861
550
 
862
551
  @mcp.tool()
863
552
  async def query_action_details(action: str) -> dict[str, Any] | None:
864
- """Get detailed information about a specific action.
553
+ """Get metadata for a specific action.
865
554
 
866
555
  Args:
867
556
  action: Full action name (e.g., "s3:GetObject", "iam:CreateUser")
868
557
 
869
558
  Returns:
870
- Dictionary with action metadata (access_level, resource_types, condition_keys),
871
- or None if not found
559
+ {action, service, access_level, resource_types, condition_keys, description} or None
872
560
  """
873
561
  from iam_validator.mcp.tools.query import query_action_details as _query
874
562
 
@@ -887,10 +575,10 @@ async def query_action_details(action: str) -> dict[str, Any] | None:
887
575
 
888
576
  @mcp.tool()
889
577
  async def expand_wildcard_action(pattern: str) -> list[str]:
890
- """Expand wildcards like "s3:Get*" to specific actions.
578
+ """Expand wildcard action pattern to specific actions.
891
579
 
892
580
  Args:
893
- pattern: Action pattern with wildcards (e.g., "s3:Get*", "iam:*User*")
581
+ pattern: Pattern with wildcards (e.g., "s3:Get*", "iam:*User*")
894
582
 
895
583
  Returns:
896
584
  List of matching action names
@@ -902,13 +590,15 @@ async def expand_wildcard_action(pattern: str) -> list[str]:
902
590
 
903
591
  @mcp.tool()
904
592
  async def query_condition_keys(service: str) -> list[str]:
905
- """Get all condition keys for a service.
593
+ """Get resource-level condition keys for a service.
594
+
595
+ Use with get_required_conditions for complete condition coverage (action + resource).
906
596
 
907
597
  Args:
908
- service: AWS service prefix (e.g., "s3", "iam")
598
+ service: Service prefix (e.g., "s3", "iam")
909
599
 
910
600
  Returns:
911
- List of condition key names (e.g., ["s3:prefix", "s3:x-amz-acl"])
601
+ List of condition keys (e.g., ["s3:prefix", "s3:x-amz-acl"])
912
602
  """
913
603
  from iam_validator.mcp.tools.query import query_condition_keys as _query
914
604
 
@@ -917,13 +607,13 @@ async def query_condition_keys(service: str) -> list[str]:
917
607
 
918
608
  @mcp.tool()
919
609
  async def query_arn_formats(service: str) -> list[dict[str, Any]]:
920
- """Get ARN formats for a service's resources.
610
+ """Get ARN format patterns for a service's resources.
921
611
 
922
612
  Args:
923
- service: AWS service prefix (e.g., "s3", "iam")
613
+ service: Service prefix (e.g., "s3", "iam")
924
614
 
925
615
  Returns:
926
- List of dictionaries with resource_type and arn_formats keys
616
+ List of {resource_type, arn_formats}
927
617
  """
928
618
  from iam_validator.mcp.tools.query import query_arn_formats as _query
929
619
 
@@ -935,7 +625,7 @@ async def list_checks() -> list[dict[str, Any]]:
935
625
  """List all available validation checks.
936
626
 
937
627
  Returns:
938
- List of dictionaries with check_id, description, and default_severity
628
+ List of {check_id, description, default_severity}
939
629
  """
940
630
  # Use cached registry instead of creating new one each call
941
631
  # Convert tuple back to list for API compatibility
@@ -944,20 +634,13 @@ async def list_checks() -> list[dict[str, Any]]:
944
634
 
945
635
  @mcp.tool()
946
636
  async def get_policy_summary(policy: dict[str, Any]) -> dict[str, Any]:
947
- """Analyze a policy and return summary statistics.
637
+ """Get summary statistics for a policy.
948
638
 
949
639
  Args:
950
- policy: IAM policy as a dictionary
640
+ policy: IAM policy dictionary
951
641
 
952
642
  Returns:
953
- Dictionary with:
954
- - total_statements: Number of statements
955
- - allow_statements: Number of Allow statements
956
- - deny_statements: Number of Deny statements
957
- - services_used: List of AWS services referenced
958
- - actions_count: Total number of actions
959
- - has_wildcards: Whether policy contains wildcards
960
- - has_conditions: Whether policy has conditions
643
+ {total_statements, allow_statements, deny_statements, services_used, actions_count, has_wildcards, has_conditions}
961
644
  """
962
645
  from iam_validator.mcp.tools.query import get_policy_summary as _get_summary
963
646
 
@@ -978,25 +661,18 @@ async def list_sensitive_actions(
978
661
  category: str | None = None,
979
662
  limit: int | None = None,
980
663
  offset: int = 0,
664
+ verbose: bool = False,
981
665
  ) -> dict[str, Any]:
982
- """List sensitive actions, optionally filtered by category.
983
-
984
- The sensitive actions catalog contains 490+ actions across 4 categories.
666
+ """List sensitive actions from the 490+ action catalog.
985
667
 
986
668
  Args:
987
- category: Optional filter:
988
- - credential_exposure: Actions that can expose credentials (46 actions)
989
- - data_access: Actions that access data (109 actions)
990
- - privilege_escalation: Actions that can escalate privileges (27 actions)
991
- - resource_exposure: Actions that can expose resources (321 actions)
992
- limit: Maximum number of actions to return (default: all)
993
- offset: Number of actions to skip for pagination (default: 0)
669
+ category: Filter by "credential_exposure", "data_access", "privilege_escalation", or "resource_exposure"
670
+ limit: Max actions to return
671
+ offset: Skip N actions for pagination
672
+ verbose: Return full action details (True) or names only (False)
994
673
 
995
674
  Returns:
996
- Dictionary with:
997
- - actions: List of sensitive action names
998
- - total: Total number of actions available
999
- - has_more: Whether more actions are available
675
+ {actions, total, has_more}
1000
676
  """
1001
677
  from iam_validator.mcp.tools.query import list_sensitive_actions as _list_sensitive
1002
678
 
@@ -1009,6 +685,10 @@ async def list_sensitive_actions(
1009
685
  if limit:
1010
686
  all_actions = all_actions[:limit]
1011
687
 
688
+ # Lean response: just action names if not verbose
689
+ if not verbose and all_actions and isinstance(all_actions[0], dict):
690
+ all_actions = [a.get("action", a) if isinstance(a, dict) else a for a in all_actions]
691
+
1012
692
  return {
1013
693
  "actions": all_actions,
1014
694
  "total": total,
@@ -1018,16 +698,13 @@ async def list_sensitive_actions(
1018
698
 
1019
699
  @mcp.tool()
1020
700
  async def get_condition_requirements_for_action(action: str) -> dict[str, Any] | None:
1021
- """Get required conditions for a specific action.
1022
-
1023
- Checks if the action has condition requirements based on the sensitive
1024
- actions catalog and condition requirements configuration.
701
+ """Get condition requirements for a specific action.
1025
702
 
1026
703
  Args:
1027
704
  action: Full action name (e.g., "iam:PassRole", "s3:GetObject")
1028
705
 
1029
706
  Returns:
1030
- Dictionary with condition requirements, or None if no requirements
707
+ Condition requirements dict, or None if no requirements
1031
708
  """
1032
709
  from iam_validator.mcp.tools.query import get_condition_requirements as _get_reqs
1033
710
 
@@ -1044,37 +721,20 @@ async def fix_policy_issues(
1044
721
  policy: dict[str, Any],
1045
722
  issues_to_fix: list[str] | None = None,
1046
723
  policy_type: str | None = None,
724
+ verbose: bool = False,
1047
725
  ) -> dict[str, Any]:
1048
- """Attempt to automatically fix common structural policy issues.
1049
-
1050
- This tool applies simple structural fixes. For security-related fixes
1051
- (conditions, sensitive actions), use the suggestion and example fields
1052
- from validate_policy to apply fixes manually.
1053
-
1054
- Auto-fixable issues (structural only):
1055
- - Missing Version field → adds "2012-10-17"
1056
- - Duplicate SIDs → makes them unique
1057
- - Action case normalization → converts "S3:GetObject" to "s3:GetObject"
726
+ """Auto-fix structural policy issues (Version, duplicate SIDs, action case).
1058
727
 
1059
- NOT auto-fixable (require user input):
1060
- - Action: "*" → requires user to specify which actions
1061
- - Resource: "*" → requires user to specify which resources
1062
- - Missing conditions → use validate_policy example field to see correct fix
1063
- - Invalid actions → use query_service_actions to find valid actions
728
+ Does NOT fix wildcards or missing conditions - those need user input.
1064
729
 
1065
730
  Args:
1066
- policy: The IAM policy to fix
1067
- issues_to_fix: Optional list of check_ids to fix. If None, attempts all fixes.
1068
- Example: ["policy_structure", "sid_uniqueness", "action_validation"]
1069
- policy_type: Type of policy. If None (default), auto-detects from structure.
1070
- Options: "identity", "resource", "trust"
731
+ policy: IAM policy to fix
732
+ issues_to_fix: Check IDs to fix (None = all structural fixes)
733
+ policy_type: "identity", "resource", or "trust" (auto-detected if None)
734
+ verbose: Return all fields (True) or essential only (False)
1071
735
 
1072
736
  Returns:
1073
- Dictionary with:
1074
- - fixed_policy: The policy with structural fixes applied
1075
- - fixes_applied: List of fixes that were applied
1076
- - unfixed_issues: Issues that require manual intervention (with guidance)
1077
- - validation: New validation result after fixes
737
+ {fixed_policy, fixes_applied, unfixed_issues, validation}
1078
738
  """
1079
739
  import copy
1080
740
 
@@ -1172,47 +832,48 @@ async def fix_policy_issues(
1172
832
  # Re-validate the fixed policy
1173
833
  final_result = await _validate(policy=fixed_policy, policy_type=effective_policy_type)
1174
834
 
835
+ if verbose:
836
+ validation_issues = [
837
+ {
838
+ "severity": issue.severity,
839
+ "message": issue.message,
840
+ "suggestion": issue.suggestion,
841
+ "example": issue.example,
842
+ "check_id": issue.check_id,
843
+ }
844
+ for issue in final_result.issues
845
+ ]
846
+ else:
847
+ validation_issues = [
848
+ {
849
+ "severity": issue.severity,
850
+ "message": issue.message,
851
+ "check_id": issue.check_id,
852
+ }
853
+ for issue in final_result.issues
854
+ ]
855
+
1175
856
  return {
1176
857
  "fixed_policy": fixed_policy,
1177
858
  "fixes_applied": fixes_applied,
1178
- "unfixed_issues": unfixed_issues,
859
+ "unfixed_issues": unfixed_issues if verbose else len(unfixed_issues),
1179
860
  "validation": {
1180
861
  "is_valid": final_result.is_valid,
1181
862
  "issue_count": len(final_result.issues),
1182
- "issues": [
1183
- {
1184
- "severity": issue.severity,
1185
- "message": issue.message,
1186
- "suggestion": issue.suggestion,
1187
- "example": issue.example,
1188
- "check_id": issue.check_id,
1189
- }
1190
- for issue in final_result.issues
1191
- ],
863
+ "issues": validation_issues,
1192
864
  },
1193
865
  }
1194
866
 
1195
867
 
1196
868
  @mcp.tool()
1197
869
  async def get_issue_guidance(check_id: str) -> dict[str, Any]:
1198
- """Get detailed guidance on how to fix a specific validation issue.
1199
-
1200
- Use this when you encounter a validation issue and need detailed
1201
- instructions on how to resolve it. Provides step-by-step fixes.
870
+ """Get step-by-step fix guidance for a validation issue.
1202
871
 
1203
872
  Args:
1204
- check_id: The check ID from the validation issue (e.g., "wildcard_action",
1205
- "action_validation", "sensitive_action")
873
+ check_id: Check ID (e.g., "wildcard_action", "sensitive_action")
1206
874
 
1207
875
  Returns:
1208
- Dictionary with:
1209
- - check_id: The check identifier
1210
- - description: What this check validates
1211
- - common_causes: Why this issue typically occurs
1212
- - fix_steps: Step-by-step instructions to fix
1213
- - example_before: Example of problematic policy
1214
- - example_after: Example of fixed policy
1215
- - related_tools: MCP tools that can help fix this issue
876
+ {check_id, description, common_causes, fix_steps, example_before, example_after, related_tools}
1216
877
  """
1217
878
  guidance_db: dict[str, dict[str, Any]] = {
1218
879
  "wildcard_action": {
@@ -1361,24 +1022,13 @@ async def get_issue_guidance(check_id: str) -> dict[str, Any]:
1361
1022
 
1362
1023
  @mcp.tool()
1363
1024
  async def get_check_details(check_id: str) -> dict[str, Any]:
1364
- """Get full documentation for a specific validation check.
1365
-
1366
- Returns comprehensive information about a check including its description,
1367
- severity, configuration options, example violations, and fixes.
1025
+ """Get full documentation for a validation check.
1368
1026
 
1369
1027
  Args:
1370
- check_id: The check ID (e.g., "wildcard_action", "sensitive_action")
1028
+ check_id: Check ID (e.g., "wildcard_action", "sensitive_action")
1371
1029
 
1372
1030
  Returns:
1373
- Dictionary with:
1374
- - check_id: The check identifier
1375
- - description: Full description of what the check validates
1376
- - default_severity: Default severity level
1377
- - category: Check category (security, aws, structure)
1378
- - example_violation: Example policy that would trigger this check
1379
- - example_fix: How to fix the violation
1380
- - configuration: Available configuration options
1381
- - related_checks: Related check IDs
1031
+ {check_id, description, default_severity, category, example_violation, example_fix, configuration, related_checks}
1382
1032
  """
1383
1033
  from iam_validator.core.check_registry import create_default_registry
1384
1034
 
@@ -1476,22 +1126,18 @@ async def get_check_details(check_id: str) -> dict[str, Any]:
1476
1126
 
1477
1127
 
1478
1128
  @mcp.tool()
1479
- async def explain_policy(policy: dict[str, Any]) -> dict[str, Any]:
1480
- """Generate a human-readable explanation of what a policy allows or denies.
1481
-
1482
- Analyzes the policy structure and produces a plain-language summary
1483
- of the effective permissions, including security concerns.
1129
+ async def explain_policy(
1130
+ policy: dict[str, Any],
1131
+ verbose: bool = False,
1132
+ ) -> dict[str, Any]:
1133
+ """Generate human-readable explanation of policy permissions and security concerns.
1484
1134
 
1485
1135
  Args:
1486
- policy: IAM policy as a dictionary
1136
+ policy: IAM policy dictionary
1137
+ verbose: Return all fields (True) or essential only (False)
1487
1138
 
1488
1139
  Returns:
1489
- Dictionary with:
1490
- - summary: Brief one-line summary
1491
- - statements: Detailed explanation of each statement
1492
- - services_accessed: List of AWS services with access types
1493
- - security_concerns: Identified security issues
1494
- - recommendations: Suggested improvements
1140
+ {summary, statements, services_accessed, security_concerns, recommendations}
1495
1141
  """
1496
1142
  from iam_validator.mcp.tools.query import get_policy_summary as _get_summary
1497
1143
 
@@ -1584,15 +1230,25 @@ async def explain_policy(policy: dict[str, Any]) -> dict[str, Any]:
1584
1230
  total_deny = len(statements) - total_allow
1585
1231
  brief_summary = f"Policy with {len(statements)} statement(s): {total_allow} Allow, {total_deny} Deny across {len(services_with_access)} service(s)"
1586
1232
 
1587
- return {
1588
- "summary": brief_summary,
1589
- "statements": statement_explanations,
1590
- "services_accessed": services_summary,
1591
- "security_concerns": security_concerns,
1592
- "recommendations": recommendations,
1593
- "has_wildcards": summary.has_wildcards,
1594
- "has_conditions": summary.has_conditions,
1595
- }
1233
+ if verbose:
1234
+ return {
1235
+ "summary": brief_summary,
1236
+ "statements": statement_explanations,
1237
+ "services_accessed": services_summary,
1238
+ "security_concerns": security_concerns,
1239
+ "recommendations": recommendations,
1240
+ "has_wildcards": summary.has_wildcards,
1241
+ "has_conditions": summary.has_conditions,
1242
+ }
1243
+ else:
1244
+ return {
1245
+ "summary": brief_summary,
1246
+ "security_concerns": security_concerns,
1247
+ "recommendations": recommendations,
1248
+ "has_wildcards": summary.has_wildcards,
1249
+ "statement_count": len(statement_explanations),
1250
+ "services_count": len(services_summary),
1251
+ }
1596
1252
 
1597
1253
 
1598
1254
  @mcp.tool()
@@ -1604,23 +1260,18 @@ async def build_arn(
1604
1260
  account_id: str = "",
1605
1261
  partition: str = "aws",
1606
1262
  ) -> dict[str, Any]:
1607
- """Build a valid ARN from components.
1608
-
1609
- Helps construct ARNs with proper format validation for the specified service.
1263
+ """Build a valid ARN from components with format validation.
1610
1264
 
1611
1265
  Args:
1612
1266
  service: AWS service (e.g., "s3", "lambda", "dynamodb")
1613
- resource_type: Type of resource (e.g., "bucket", "function", "table")
1267
+ resource_type: Resource type (e.g., "bucket", "function", "table")
1614
1268
  resource_name: Name of the resource
1615
- region: AWS region (required for regional resources, empty for global)
1616
- account_id: AWS account ID (12 digits, empty for some services like S3)
1617
- partition: AWS partition (default: "aws", or "aws-cn", "aws-us-gov")
1269
+ region: AWS region (empty for global resources)
1270
+ account_id: 12-digit AWS account ID (empty for S3)
1271
+ partition: "aws", "aws-cn", or "aws-us-gov"
1618
1272
 
1619
1273
  Returns:
1620
- Dictionary with:
1621
- - arn: The constructed ARN
1622
- - valid: Whether the ARN format is valid
1623
- - notes: Any notes about the ARN format
1274
+ {arn, valid, notes}
1624
1275
  """
1625
1276
  # ARN format patterns by service
1626
1277
  arn_patterns: dict[str, dict[str, Any]] = {
@@ -1753,24 +1404,17 @@ async def build_arn(
1753
1404
  async def compare_policies(
1754
1405
  policy_a: dict[str, Any],
1755
1406
  policy_b: dict[str, Any],
1407
+ verbose: bool = False,
1756
1408
  ) -> dict[str, Any]:
1757
1409
  """Compare two IAM policies and highlight differences.
1758
1410
 
1759
- Analyzes both policies and shows what permissions differ between them.
1760
-
1761
1411
  Args:
1762
- policy_a: First IAM policy (baseline)
1763
- policy_b: Second IAM policy (comparison)
1412
+ policy_a: First policy (baseline)
1413
+ policy_b: Second policy (comparison)
1414
+ verbose: Return all fields (True) or essential only (False)
1764
1415
 
1765
1416
  Returns:
1766
- Dictionary with:
1767
- - summary: Brief comparison summary
1768
- - added_actions: Actions in policy_b but not in policy_a
1769
- - removed_actions: Actions in policy_a but not in policy_b
1770
- - added_resources: Resources in policy_b but not in policy_a
1771
- - removed_resources: Resources in policy_a but not in policy_b
1772
- - condition_changes: Differences in conditions
1773
- - effect_changes: Statements with different effects
1417
+ {summary, added_actions, removed_actions, added_resources, removed_resources, condition_changes, effect_changes}
1774
1418
  """
1775
1419
 
1776
1420
  def extract_policy_elements(policy: dict[str, Any]) -> dict[str, Any]:
@@ -1844,18 +1488,28 @@ async def compare_policies(
1844
1488
 
1845
1489
  summary = ", ".join(changes) if changes else "No significant differences found"
1846
1490
 
1847
- return {
1848
- "summary": summary,
1849
- "added_actions": added_actions,
1850
- "removed_actions": removed_actions,
1851
- "added_resources": added_resources,
1852
- "removed_resources": removed_resources,
1853
- "condition_changes": {
1854
- "policy_a_conditions": len(elements_a["conditions"]),
1855
- "policy_b_conditions": len(elements_b["conditions"]),
1856
- },
1857
- "effect_changes": effect_changes,
1858
- }
1491
+ if verbose:
1492
+ return {
1493
+ "summary": summary,
1494
+ "added_actions": added_actions,
1495
+ "removed_actions": removed_actions,
1496
+ "added_resources": added_resources,
1497
+ "removed_resources": removed_resources,
1498
+ "condition_changes": {
1499
+ "policy_a_conditions": len(elements_a["conditions"]),
1500
+ "policy_b_conditions": len(elements_b["conditions"]),
1501
+ },
1502
+ "effect_changes": effect_changes,
1503
+ }
1504
+ else:
1505
+ return {
1506
+ "summary": summary,
1507
+ "added_actions_count": len(added_actions),
1508
+ "removed_actions_count": len(removed_actions),
1509
+ "added_resources_count": len(added_resources),
1510
+ "removed_resources_count": len(removed_resources),
1511
+ "effect_changes_count": len(effect_changes),
1512
+ }
1859
1513
 
1860
1514
 
1861
1515
  # =============================================================================
@@ -1870,28 +1524,15 @@ async def validate_policies_batch(
1870
1524
  policy_type: str | None = None,
1871
1525
  verbose: bool = False,
1872
1526
  ) -> list[dict[str, Any]]:
1873
- """Validate multiple IAM policies in a single call.
1874
-
1875
- More efficient than calling validate_policy multiple times when you need
1876
- to validate several policies at once. Validations run in parallel.
1877
-
1878
- Policy Type Auto-Detection:
1879
- If policy_type is None (default), each policy's type is automatically detected
1880
- from its structure (see validate_policy for detection rules).
1527
+ """Validate multiple IAM policies in parallel (more efficient than multiple validate_policy calls).
1881
1528
 
1882
1529
  Args:
1883
- ctx: FastMCP context (automatically passed by framework)
1884
- policies: List of IAM policy dictionaries to validate
1885
- policy_type: Type of policies. If None (default), auto-detects each policy.
1886
- Options: "identity", "resource", "trust"
1887
- verbose: If True, return all issue fields. If False (default), return
1888
- only essential fields to reduce tokens.
1530
+ policies: List of IAM policy dictionaries
1531
+ policy_type: "identity", "resource", or "trust" (auto-detected if None)
1532
+ verbose: Return all fields (True) or essential only (False)
1889
1533
 
1890
1534
  Returns:
1891
- List of validation results, each containing:
1892
- - policy_index: Index of the policy in the input list
1893
- - is_valid: Whether the policy passed validation
1894
- - issues: List of validation issues
1535
+ List of {policy_index, is_valid, issues}
1895
1536
  """
1896
1537
  import asyncio
1897
1538
 
@@ -1941,18 +1582,13 @@ async def validate_policies_batch(
1941
1582
 
1942
1583
  @mcp.tool()
1943
1584
  async def query_actions_batch(actions: list[str], ctx: Context) -> dict[str, dict[str, Any] | None]:
1944
- """Get details for multiple actions in a single call.
1945
-
1946
- More efficient than calling query_action_details multiple times when you
1947
- need information about several actions at once.
1585
+ """Get details for multiple actions in parallel (more efficient than multiple query_action_details calls).
1948
1586
 
1949
1587
  Args:
1950
- ctx: FastMCP context (automatically passed by framework)
1951
- actions: List of full action names (e.g., ["s3:GetObject", "iam:CreateUser"])
1588
+ actions: Action names (e.g., ["s3:GetObject", "iam:CreateUser"])
1952
1589
 
1953
1590
  Returns:
1954
- Dictionary mapping action names to their details (or None if not found).
1955
- Each action detail contains: service, access_level, resource_types, condition_keys
1591
+ Dict mapping action names to {service, access_level, resource_types, condition_keys} or None
1956
1592
  """
1957
1593
  import asyncio
1958
1594
 
@@ -1986,21 +1622,19 @@ async def query_actions_batch(actions: list[str], ctx: Context) -> dict[str, dic
1986
1622
 
1987
1623
 
1988
1624
  @mcp.tool()
1989
- async def check_actions_batch(actions: list[str], ctx: Context) -> dict[str, Any]:
1990
- """Validate and check sensitivity for multiple actions in one call.
1991
-
1992
- Combines action validation and sensitivity checking into a single tool
1993
- for efficient batch processing.
1625
+ async def check_actions_batch(
1626
+ actions: list[str],
1627
+ ctx: Context,
1628
+ verbose: bool = False,
1629
+ ) -> dict[str, Any]:
1630
+ """Validate existence and check sensitivity for multiple actions in parallel.
1994
1631
 
1995
1632
  Args:
1996
- ctx: FastMCP context (automatically passed by framework)
1997
- actions: List of AWS actions to check (e.g., ["s3:GetObject", "iam:PassRole"])
1633
+ actions: AWS actions to check (e.g., ["s3:GetObject", "iam:PassRole"])
1634
+ verbose: Return all fields (True) or essential only (False)
1998
1635
 
1999
1636
  Returns:
2000
- Dictionary with:
2001
- - valid_actions: List of actions that exist in AWS
2002
- - invalid_actions: List of actions that don't exist (with error messages)
2003
- - sensitive_actions: List of sensitive actions with their categories
1637
+ {valid_actions, invalid_actions, sensitive_actions}
2004
1638
  """
2005
1639
  import asyncio
2006
1640
 
@@ -2078,11 +1712,20 @@ async def check_actions_batch(actions: list[str], ctx: Context) -> dict[str, Any
2078
1712
  if result["sensitive"]:
2079
1713
  sensitive_actions.append({"action": action, **result["sensitive"]})
2080
1714
 
2081
- return {
2082
- "valid_actions": valid_actions,
2083
- "invalid_actions": invalid_actions,
2084
- "sensitive_actions": sensitive_actions,
2085
- }
1715
+ if verbose:
1716
+ return {
1717
+ "valid_actions": valid_actions,
1718
+ "invalid_actions": invalid_actions,
1719
+ "sensitive_actions": sensitive_actions,
1720
+ }
1721
+ else:
1722
+ return {
1723
+ "valid_actions": valid_actions,
1724
+ "invalid_count": len(invalid_actions),
1725
+ "sensitive_count": len(sensitive_actions),
1726
+ "invalid_actions": [ia["action"] for ia in invalid_actions],
1727
+ "sensitive_actions": [sa["action"] for sa in sensitive_actions],
1728
+ }
2086
1729
 
2087
1730
 
2088
1731
  # =============================================================================
@@ -2096,34 +1739,12 @@ async def set_organization_config(
2096
1739
  ) -> dict[str, Any]:
2097
1740
  """Set validator configuration for this MCP session.
2098
1741
 
2099
- The configuration uses the same format as the iam-validator YAML config files.
2100
- It applies to all subsequent validation operations until cleared or updated.
2101
-
2102
1742
  Args:
2103
- config: Validator configuration dictionary with:
2104
- - settings: Global settings
2105
- - fail_on_severity: List of severities that cause failure
2106
- (e.g., ["error", "critical", "high"])
2107
- - parallel_execution: Enable parallel check execution (default: true)
2108
- - fail_fast: Stop on first error (default: false)
2109
- - Check IDs as keys with configuration:
2110
- - enabled: Enable/disable the check (default: true)
2111
- - severity: Override check severity (error|critical|high|medium|low|warning)
2112
- - ignore_patterns: Patterns to skip (see docs for pattern syntax)
1743
+ config: Config with "settings" (fail_on_severity, parallel_execution) and
1744
+ check IDs as keys (enabled, severity, ignore_patterns)
2113
1745
 
2114
1746
  Returns:
2115
- Dictionary with:
2116
- - success: Whether config was set successfully
2117
- - applied_config: The effective configuration (settings + checks)
2118
- - warnings: Any configuration warnings
2119
-
2120
- Example:
2121
- >>> result = await set_organization_config({
2122
- ... "settings": {"fail_on_severity": ["error", "critical"]},
2123
- ... "wildcard_action": {"enabled": True, "severity": "critical"},
2124
- ... "sensitive_action": {"enabled": True, "severity": "high"},
2125
- ... "policy_size": {"enabled": False} # Disable a check
2126
- ... })
1747
+ {success, applied_config, warnings}
2127
1748
  """
2128
1749
  from iam_validator.mcp.tools.org_config_tools import set_organization_config_impl
2129
1750
 
@@ -2132,13 +1753,10 @@ async def set_organization_config(
2132
1753
 
2133
1754
  @mcp.tool()
2134
1755
  async def get_organization_config() -> dict[str, Any]:
2135
- """Get the current organization configuration for this session.
1756
+ """Get the current session organization configuration.
2136
1757
 
2137
1758
  Returns:
2138
- Dictionary with:
2139
- - has_config: Whether an organization config is currently set
2140
- - config: The current configuration (or null if not set)
2141
- - source: Where the config came from ("session", "yaml", or "none")
1759
+ {has_config, config, source}
2142
1760
  """
2143
1761
  from iam_validator.mcp.tools.org_config_tools import get_organization_config_impl
2144
1762
 
@@ -2147,14 +1765,10 @@ async def get_organization_config() -> dict[str, Any]:
2147
1765
 
2148
1766
  @mcp.tool()
2149
1767
  async def clear_organization_config() -> dict[str, str]:
2150
- """Clear the organization configuration for this session.
2151
-
2152
- After clearing, validation and generation will use default settings
2153
- without any organization-specific restrictions.
1768
+ """Clear session organization config, reverting to defaults.
2154
1769
 
2155
1770
  Returns:
2156
- Dictionary with:
2157
- - status: "cleared" if config was removed, "no_config_set" if none existed
1771
+ {status: "cleared" or "no_config_set"}
2158
1772
  """
2159
1773
  from iam_validator.mcp.tools.org_config_tools import clear_organization_config_impl
2160
1774
 
@@ -2165,39 +1779,13 @@ async def clear_organization_config() -> dict[str, str]:
2165
1779
  async def load_organization_config_from_yaml(
2166
1780
  yaml_content: str,
2167
1781
  ) -> dict[str, Any]:
2168
- """Load validator configuration from YAML content.
2169
-
2170
- Parses YAML configuration and sets it as the session config.
2171
- Uses the same format as iam-validator.yaml configuration files.
1782
+ """Load validator configuration from YAML content and set as session config.
2172
1783
 
2173
1784
  Args:
2174
- yaml_content: YAML configuration string. Example:
2175
- settings:
2176
- fail_on_severity:
2177
- - error
2178
- - critical
2179
- - high
2180
-
2181
- # Enable/disable/configure specific checks
2182
- wildcard_action:
2183
- enabled: true
2184
- severity: critical
2185
-
2186
- sensitive_action:
2187
- enabled: true
2188
- severity: high
2189
- ignore_patterns:
2190
- - action: "^s3:Get.*" # Ignore S3 read actions
2191
-
2192
- policy_size:
2193
- enabled: false # Disable this check
1785
+ yaml_content: YAML string with settings and check configurations
2194
1786
 
2195
1787
  Returns:
2196
- Dictionary with:
2197
- - success: Whether config was loaded successfully
2198
- - applied_config: The effective configuration (settings + checks)
2199
- - warnings: Any warnings (unknown keys, etc.)
2200
- - error: Error message if loading failed
1788
+ {success, applied_config, warnings, error}
2201
1789
  """
2202
1790
  from iam_validator.mcp.tools.org_config_tools import (
2203
1791
  load_organization_config_from_yaml_impl,
@@ -2209,29 +1797,30 @@ async def load_organization_config_from_yaml(
2209
1797
  @mcp.tool()
2210
1798
  async def check_org_compliance(
2211
1799
  policy: dict[str, Any],
1800
+ verbose: bool = False,
2212
1801
  ) -> dict[str, Any]:
2213
- """Validate a policy using the current session configuration.
2214
-
2215
- Runs the full IAM validator with the session configuration applied.
2216
- This includes all enabled checks with their configured severity levels
2217
- and ignore patterns.
2218
-
2219
- If no session config is set, uses default validator settings.
1802
+ """Validate a policy using session org config (or defaults if none set).
2220
1803
 
2221
1804
  Args:
2222
- policy: IAM policy as a dictionary
1805
+ policy: IAM policy dictionary
1806
+ verbose: Return all fields (True) or essential only (False)
2223
1807
 
2224
1808
  Returns:
2225
- Dictionary with:
2226
- - compliant: True if no issues exceed fail_on_severity threshold
2227
- - has_org_config: Whether a session config is set
2228
- - violations: List of validation issues found (type, message, severity)
2229
- - warnings: List of warnings
2230
- - suggestions: How to fix issues
1809
+ {compliant, has_org_config, violations, warnings, suggestions}
2231
1810
  """
2232
1811
  from iam_validator.mcp.tools.org_config_tools import check_org_compliance_impl
2233
1812
 
2234
- return await check_org_compliance_impl(policy)
1813
+ result = await check_org_compliance_impl(policy)
1814
+
1815
+ if not verbose:
1816
+ # Lean response: counts instead of full lists
1817
+ result["violation_count"] = len(result.get("violations", []))
1818
+ result["warning_count"] = len(result.get("warnings", []))
1819
+ if "suggestions" in result and isinstance(result["suggestions"], list):
1820
+ result["suggestion_count"] = len(result["suggestions"])
1821
+ del result["suggestions"]
1822
+
1823
+ return result
2235
1824
 
2236
1825
 
2237
1826
  @mcp.tool()
@@ -2240,36 +1829,15 @@ async def validate_with_config(
2240
1829
  config: dict[str, Any],
2241
1830
  policy_type: str | None = None,
2242
1831
  ) -> dict[str, Any]:
2243
- """Validate a policy with explicit inline configuration.
2244
-
2245
- Useful for one-off validation with specific settings without modifying
2246
- the session config. The provided config is used only for this call.
2247
-
2248
- Policy Type Auto-Detection:
2249
- If policy_type is None (default), the policy type is automatically detected
2250
- from the policy structure (see validate_policy for detection rules).
1832
+ """Validate a policy with inline configuration (one-off, doesn't modify session).
2251
1833
 
2252
1834
  Args:
2253
1835
  policy: IAM policy to validate
2254
- config: Inline configuration (same format as set_organization_config):
2255
- - settings: Global settings (fail_on_severity, parallel_execution, etc.)
2256
- - Check IDs as keys: {enabled, severity, ignore_patterns}
2257
- policy_type: Type of policy ("identity", "resource", "trust")
1836
+ config: Same format as set_organization_config
1837
+ policy_type: "identity", "resource", or "trust" (auto-detected if None)
2258
1838
 
2259
1839
  Returns:
2260
- Dictionary with:
2261
- - is_valid: Whether the policy passed all checks
2262
- - issues: List of validation issues (severity, message, suggestion, check_id)
2263
- - config_applied: The configuration that was used
2264
-
2265
- Example:
2266
- >>> result = await validate_with_config(
2267
- ... policy=my_policy,
2268
- ... config={
2269
- ... "settings": {"fail_on_severity": ["error"]},
2270
- ... "wildcard_action": {"severity": "warning"} # Downgrade to warning
2271
- ... }
2272
- ... )
1840
+ {is_valid, issues, config_applied}
2273
1841
  """
2274
1842
  from iam_validator.mcp.tools.org_config_tools import validate_with_config_impl
2275
1843
 
@@ -2285,33 +1853,15 @@ async def validate_with_config(
2285
1853
  async def set_custom_instructions(
2286
1854
  instructions: str,
2287
1855
  ) -> dict[str, Any]:
2288
- """Set custom instructions for policy generation.
2289
-
2290
- Custom instructions are appended to the default MCP server instructions,
2291
- allowing organizations to add their own policy generation guidelines.
1856
+ """Set custom policy generation guidelines for this session.
2292
1857
 
2293
- These instructions will influence how the AI assistant generates and
2294
- validates IAM policies during this session.
1858
+ Instructions are appended to default server instructions.
2295
1859
 
2296
1860
  Args:
2297
- instructions: Custom instructions text (markdown supported). Examples:
2298
- - "All policies must include aws:PrincipalOrgID condition"
2299
- - "Use resource tags for access control where possible"
2300
- - "S3 buckets must have encryption conditions"
1861
+ instructions: Custom instructions text (markdown supported)
2301
1862
 
2302
1863
  Returns:
2303
- Dictionary with:
2304
- - success: True if instructions were set
2305
- - instructions_preview: First 200 chars of the instructions
2306
- - previous_source: Source of any replaced instructions
2307
-
2308
- Example:
2309
- >>> await set_custom_instructions('''
2310
- ... ## Organization Security Requirements
2311
- ... - All policies must restrict to our AWS Organization
2312
- ... - MFA is required for any IAM modifications
2313
- ... - S3 access must include secure transport conditions
2314
- ... ''')
1864
+ {success, instructions_preview, previous_source}
2315
1865
  """
2316
1866
  from iam_validator.mcp.session_config import CustomInstructionsManager
2317
1867
 
@@ -2333,13 +1883,10 @@ async def set_custom_instructions(
2333
1883
 
2334
1884
  @mcp.tool()
2335
1885
  async def get_custom_instructions() -> dict[str, Any]:
2336
- """Get the current custom instructions.
1886
+ """Get current custom instructions.
2337
1887
 
2338
1888
  Returns:
2339
- Dictionary with:
2340
- - has_instructions: Whether custom instructions are set
2341
- - instructions: The custom instructions (or None)
2342
- - source: Where the instructions came from (api, env, file, config, none)
1889
+ {has_instructions, instructions, source}
2343
1890
  """
2344
1891
  from iam_validator.mcp.session_config import CustomInstructionsManager
2345
1892
 
@@ -2354,11 +1901,10 @@ async def get_custom_instructions() -> dict[str, Any]:
2354
1901
 
2355
1902
  @mcp.tool()
2356
1903
  async def clear_custom_instructions() -> dict[str, str]:
2357
- """Clear custom instructions, reverting to default server instructions.
1904
+ """Clear custom instructions, reverting to defaults.
2358
1905
 
2359
1906
  Returns:
2360
- Dictionary with:
2361
- - status: "cleared" if instructions were removed, "no_instructions_set" if none existed
1907
+ {status: "cleared" or "no_instructions_set"}
2362
1908
  """
2363
1909
  from iam_validator.mcp.session_config import CustomInstructionsManager
2364
1910