Skip to content

Conversation

rholinshead
Copy link
Member

@rholinshead rholinshead commented Sep 12, 2025

Description

Update the deployment secrets processing UX to respect mcp_agent.secrets.yaml as a "readonly" artifact, dropping the manual custom secrets tag / environment variable handling in favour of raw secrets transformation into the mcp_agent.deployed.secrets.yaml. This also replaces the 'developer secret' naming with 'deployment secret' to be clearer in its use.

More specifically, this means:

  • users no longer need to manually modify their secrets file to add !developer_secret or !user_secret tags, or specify env var names for such secrets; they use their secrets file as prescribed by mcp-agent without special handling for cloud
  • during deployment, we first check if mcp_agent.deployed.secrets.yaml exists; if so, prompt if it should be reprocessed (if not, it will be used)
  • during processing, prompt the user to update any deployed secrets that don't match (if both files exist)
  • during processing, prompt the user if a secret is a deployment secret or user secret, e.g.
Select secret type for 'openai.api_key'
1: Deployment Secret: The secret value will be stored securely and accessible to the deployed application runtime.
2: User Secret: No secret value will be stored. The 'configure' command must be used to create a configured 
application with this secret.

Select secret type: (1): 
  • the user can skip prompting via the --non-interactive flag and the command will either just use the existing deployed secrets file as-is (if it exists) or create new deployment secrets for all secrets in the mcp_agent.deployed.secrets.yaml file

Removed some redundant tests and added some new ones for the transformation logic update. Removed --dry-run flag for now since it was broken. We should consider adding a new --dry-run implementation which spins up a local mcp app server and does a test list_tools call over sse.

Testing

make lint
make format
make tests

Ran through the various flows and ensured secrets handling works as expected

Summary by CodeRabbit

  • New Features

    • Interactive secret classification (Deployment vs User) with reuse of existing deployed secrets.
    • Non-interactive mode defaults all secrets to deployment; clearer console summaries (created, reused, skipped).
    • Prompts when updating existing apps; prints API target info; deployment header shows App, ID, and deployed secrets file.
  • Refactor

    • Simplified deploy flow: removed --dry-run and --no-secrets; API URL and key now required.
    • Shift from developer to deployment secrets; stricter error handling.
    • Config discovery now includes deployed secrets file to drive readiness and processing.

Copy link

coderabbitai bot commented Sep 12, 2025

Walkthrough

Refactors deploy command to remove dry-run/no-secrets, add non-interactive semantics, and enforce real API usage. Introduces deployed-secrets workflow, revamped secret processing (deployment vs user secrets, reuse/skip), expanded UX headers/summaries, and tighter error handling. Updates tests to mock deployments and align with new secrets pipeline; removes legacy secrets tests.

Changes

Cohort / File(s) Summary
Deploy command refactor
src/mcp_agent/cli/cloud/commands/deploy/main.py
Reworked deploy flow; removed dry-run/no-secrets; added non-interactive behavior; introduced deployed-secrets file handling; enforced API URL/key; updated file discovery to return config, secrets, deployed-secrets; integrated new secrets processor and UX headers; tightened errors; returns app ID string.
Secrets processor overhaul
src/mcp_agent/cli/secrets/processor.py
Major redesign: raw-secret processing with interactive classification (deployment vs user); reuse/skip logic; new get_validated_config_secrets; recursive transform updates; explicit errors (CLIError); new process_secrets_in_config_str; removed prior tag-driven validation path.
UX updates for deployment secrets
src/mcp_agent/cli/utils/ux.py
Removed print_secret_prompt; updated print_secrets_summary signature and logic for deployment/reused/skipped secrets; adjusted counters and labels; expanded print_deployment_header (app_name/app_id, deployed_secrets_file), removed Mode line from configuration header.
Wrangler wrapper formatting
src/mcp_agent/cli/cloud/commands/deploy/wrangler_wrapper.py
Import formatting change only; no logic changes.
Core utils formatting
src/mcp_agent/cli/core/utils.py
Signature formatting/whitespace only for load_user_app; no behavior change.
Tests: deploy command
tests/cli/commands/test_deploy_command.py
Refactored to async mocks; updated to new CLI options; mocks MCPAppClient and wrangler_deploy; aligns with new secrets processor outputs and deployed-secrets file handling.
Tests removed (legacy secrets)
tests/cli/commands/test_cli_secrets.py, tests/cli/secrets/test_developer_secret_validation.py
Deleted legacy tests for tag-based secrets, dry-run, and developer-secret validation.
Tests: secrets transform
tests/cli/secrets/test_secrets_transform.py
Updated to raw-secret workflow with interactive/non-interactive paths, reuse/skip handling, and new API signatures/expectations.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant CLI as deploy_config
  participant FS as get_config_files
  participant SP as Secrets Processor
  participant UX as UX headers
  participant API as MCPAppClient
  participant WR as wrangler_deploy

  User->>CLI: Invoke deploy (opts: --non-interactive, --api-url, --api-key)
  CLI->>FS: Discover config, secrets, deployed_secrets
  FS-->>CLI: Paths (config_file, secrets_file, deployed_secrets_file?)
  CLI->>UX: print_configuration_header / print_deployment_header
  alt deployed_secrets_file present
    CLI->>SP: process using existing deployed secrets (reuse/validate)
  else
    CLI->>SP: process secrets.yaml -> deployed.secrets.yaml
  end
  SP-->>CLI: {deployment_secrets, user_secrets, reused, skipped}
  CLI->>API: get_app_id_by_name(app_name)
  alt App exists
    CLI->>User: Confirm update? (skipped if non-interactive)
    opt Update approved
      CLI->>API: update app metadata
    end
  else
    CLI->>API: create_app(app_name, description)
  end
  API-->>CLI: appId
  CLI->>WR: wrangler_deploy(bundle, appId, api creds)
  WR-->>CLI: appId
  CLI-->>User: Return appId
Loading
sequenceDiagram
  autonumber
  participant SP as Secrets Processor
  participant SC as SecretsClient
  participant UX as UX summary

  note over SP: Input: raw secrets YAML (+ optional existing deployed.secrets)
  loop Traverse config (dicts/lists)
    alt Non-interactive
      SP->>SC: create_secret(value)
      SC-->>SP: handle (UUID)
      SP-->>SP: record deployment_secret
    else Interactive
      SP->>User: Prompt: Deployment vs User Secret
      alt Deployment
        SP->>SC: create_secret(value)
        SC-->>SP: handle (UUID)
        SP-->>SP: record deployment_secret
      else User Secret
        SP-->>SP: tag as !user_secret (value omitted)
      end
    end
    opt Existing transformed present
      SP-->>SP: Reuse handle if consistent
    end
    opt Empty/invalid
      SP-->>SP: mark skipped, record error
    end
  end
  SP->>UX: print_secrets_summary(deployment, user, reused, skipped)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Fix: Port CLI UX updates #402 — Touches the same secrets processor path, replacing/augmenting validation flow; likely predecessor to current get_validated_config_secrets refactor.

Suggested reviewers

  • saqadri
  • petersonbill64

Poem

I hop through YAML fields at night,
Turning whispers into handles bright.
User secrets, softly kept—
Deployment keys, securely swept.
No dry runs—just forward leap!
Apps awake from configuration sleep.
Thump-thump: shipped, with secrets deep. 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Fix: Update Deployment Secrets Processing UX" is concise and directly reflects the primary change described in the PR (the redeveloped deployment-secrets workflow and UX), so it accurately summarizes the main intent and is clear for reviewers scanning history.
Docstring Coverage ✅ Passed Docstring coverage is 90.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/deploy-secrets-ux

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rholinshead rholinshead changed the title WIP starting point Fix: Update Deployment Secrets Processing UX Sep 14, 2025
@rholinshead rholinshead marked this pull request as ready for review September 14, 2025 03:28
@rholinshead rholinshead requested a review from saqadri September 14, 2025 03:28
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (17)
src/mcp_agent/cli/secrets/processor.py (4)

52-63: Doc terminology drift: “developer secret” → “deployment secret”

Align docs with the new UX to avoid confusion.

-        - If non-interactive is True, automatically transforms all secrets to
-            developer secrets without prompting, reusing existing secrets where applicable
+        - If non-interactive is True, automatically transforms all secrets to
+            deployment secrets without prompting, reusing existing secrets where applicable
-            - Prompts to determine whether a secret is a developer secret to transform
+            - Prompts to determine whether a secret is a deployment secret to transform

Also applies to: 70-71


180-185: Type hint: use concrete mutable types for context lists

These collections are mutated (append). Sequence is misleading.

-    secrets_context: Dict[str, Sequence] = {
+    secrets_context: Dict[str, List] = {

304-318: Existing-secrets reuse misses list values (arrays)

Validation skips list-typed nodes, so array-based secrets can’t be reused. Add list handling to preserve reuse in arrays.

         elif isinstance(existing_value, dict):
           # Always recursively process nested dictionaries
           input_dict = (
               input_config.get(key, {})
               if isinstance(input_config.get(key), dict)
               else {}
           )
           nested_validated = await get_validated_config_secrets(
               input_dict, existing_value, client, non_interactive, current_path
           )

           if nested_validated:
               validated_config[key] = nested_validated
+        elif isinstance(existing_value, list):
+            # Validate lists element-by-element to support paths like "foo[0].bar"
+            input_list = input_config.get(key, []) if isinstance(input_config.get(key), list) else []
+            validated_list: List[Any] = []
+            for idx, ev in enumerate(existing_value):
+                # Recurse only for dict/list elements; primitives can’t be validated here
+                if isinstance(ev, (dict, list)):
+                    nested = await get_validated_config_secrets(
+                        input_list[idx] if idx < len(input_list) and isinstance(input_list[idx], type(ev)) else {},
+                        ev,
+                        client,
+                        non_interactive,
+                        f"{current_path}[{idx}]",
+                    )
+                    validated_list.append(nested if nested else ({} if isinstance(ev, dict) else []))
+                else:
+                    validated_list.append(None)
+            if any(item not in (None, {}, []) for item in validated_list):
+                validated_config[key] = validated_list

509-513: Treat whitespace-only values as empty

Prevents creating secrets with “blank” values.

-            if config_value is None or config_value == "":
+            if config_value is None or (isinstance(config_value, str) and config_value.strip() == ""):
                 raise ValueError(
                     f"\nSecret at {path} has no value. Deployment secrets must have values."
                 )
src/mcp_agent/cli/utils/ux.py (1)

111-115: Avoid mutable default arguments

Use None defaults and initialize locally.

-def print_secrets_summary(
-    deployment_secrets: List[Dict[str, str]],
-    user_secrets: List[str],
-    reused_secrets: Optional[List[Dict[str, str]]] = [],
-    skipped_secrets: Optional[List[str]] = [],
-) -> None:
+def print_secrets_summary(
+    deployment_secrets: List[Dict[str, str]],
+    user_secrets: List[str],
+    reused_secrets: Optional[List[Dict[str, str]]] = None,
+    skipped_secrets: Optional[List[str]] = None,
+) -> None:
@@
-    reused_paths = (
-        {secret["path"] for secret in reused_secrets} if reused_secrets else set()
-    )
-    skipped_paths = set(skipped_secrets) if skipped_secrets else set()
+    reused_secrets = reused_secrets or []
+    skipped_secrets = skipped_secrets or []
+    reused_paths = {secret["path"] for secret in reused_secrets}
+    skipped_paths = set(skipped_secrets)
@@
-    reused_count = len(reused_secrets)
-    new_deployment_count = len(deployment_secrets)
+    reused_count = len(reused_secrets)
+    new_deployment_count = len(deployment_secrets)
tests/cli/commands/test_deploy_command.py (6)

72-77: Also assert removed flags are absent

Guard against regressions by asserting deprecated flags (e.g., --dry-run, --no-secrets) are not shown.

     assert "--non-interactive" in clean_text
+    assert "--dry-run" not in clean_text
+    assert "--no-secrets" not in clean_text

120-133: Pass str(config_dir) to the CLI runner

Click/Typer expects strings; passing Path can be flaky across environments.

-                temp_config_dir,
+                str(temp_config_dir),

135-143: Consider verifying transformed file contents too

You already check the status line. Also assert the file actually contains the mocked payload to catch write path issues.

-    assert "Transformed secrets file written to" in result.stdout
+    assert "Transformed secrets file written to" in result.stdout
+    out_path = temp_config_dir / MCP_DEPLOYED_SECRETS_FILENAME
+    with open(out_path, "r", encoding="utf-8") as f:
+        assert "test: value" in f.read()

171-189: Make this test fully hermetic: also patch process_config_secrets

Without patching, process_config_secrets could make network calls (api_url=http://test.api/). Patch it as in the first test.

-        with (
-            patch(
-                "mcp_agent.cli.cloud.commands.deploy.main.wrangler_deploy",
-                return_value=MOCK_APP_ID,
-            ),
-            patch(
-                "mcp_agent.cli.cloud.commands.deploy.main.MCPAppClient",
-                return_value=mock_client,
-            ),
-        ):
+        with (
+            patch(
+                "mcp_agent.cli.cloud.commands.deploy.main.wrangler_deploy",
+                return_value=MOCK_APP_ID,
+            ),
+            patch(
+                "mcp_agent.cli.cloud.commands.deploy.main.MCPAppClient",
+                return_value=mock_client,
+            ),
+            patch(
+                "mcp_agent.cli.secrets.processor.process_config_secrets",
+                side_effect=lambda **kw: (Path(kw["output_path"]).write_text("# transformed\n"), {"deployment_secrets": [], "user_secrets": [], "reused_secrets": [], "skipped_secrets": []})[1],  # noqa: E501
+            ),
+        ):

201-214: Also assert deployed file content and branch coverage for both-files-present

  • Add simple content assertion on mcp_agent.deployed.secrets.yaml.
  • Consider another test covering: both secrets files present + non_interactive=True → no reprocessing message.

19-19: Import deploy_config from deploy.main to avoid re-export coupling

deploy_config is defined in src/mcp_agent/cli/cloud/commands/deploy/main.py and is re-exported from src/mcp_agent/cli/cloud/commands/init.py; change the test import at tests/cli/commands/test_deploy_command.py:19 to:
from mcp_agent.cli.cloud.commands.deploy.main import deploy_config

src/mcp_agent/cli/cloud/commands/deploy/main.py (6)

69-74: Track the dry‑run comeback with an issue ID

You’ve left a TODO. Link an issue or add a clear follow‑up to avoid bit‑rot.

-    # TODO(@rholinshead): Re-add dry-run and perform pre-validation of the app
+    # TODO(@rholinshead): Re-add dry-run and perform pre-validation of the app (see ISSUE-XXXX)

152-166: Fix grammar and cancellation semantics

  • Prompt grammar: “Do you want to deploy an update…”
  • Returning app_id on cancel is surprising for a function documented to return the newly deployed app ID. Consider raising typer.Exit or returning None.
-                    use_existing = typer.confirm(
-                        f"Do you want deploy an update to the existing app ID: {app_id}?"
-                    )
+                    use_existing = typer.confirm(
+                        f"Do you want to deploy an update to the existing app ID: {app_id}?"
+                    )
@@
-                        print_error(
-                            "Cancelling deployment. Please choose a different app name."
-                        )
-                        return app_id
+                        print_error("Cancelling deployment. Please choose a different app name.")
+                        # Consider: raise typer.Exit() or return None
+                        return app_id

203-205: Header before processing is fine, but consider refreshing after re‑processing

If you reprocess secrets, printing a second header with the concrete deployed file path would improve UX.


210-213: Prefer Path composition over f‑string for paths

Cleaner and OS‑agnostic.

-            secrets_transformed_path = Path(
-                f"{config_dir}/{MCP_DEPLOYED_SECRETS_FILENAME}"
-            )
+            secrets_transformed_path = config_dir / MCP_DEPLOYED_SECRETS_FILENAME

214-222: Pass client optionally to aid testing (dependency injection)

Allowing an optional SecretsClient would simplify tests and reduce patching surface.

-                process_config_secrets(
+                process_config_secrets(
                     input_path=secrets_file,
                     output_path=secrets_transformed_path,
                     api_url=effective_api_url,
                     api_key=effective_api_key,
                     non_interactive=non_interactive,
                 )

Follow‑up: add a client: Optional[SecretsClient] param to deploy_config and thread it through when provided.


283-291: Use typing.Tuple for broader Python compatibility or confirm min version

tuple[...] requires Python 3.9+. If you support 3.8, switch to typing.Tuple; otherwise, note the min Python in packaging.

-from typing import Optional
+from typing import Optional, Tuple
@@
-def get_config_files(config_dir: Path) -> tuple[Path, Optional[Path], Optional[Path]]:
+def get_config_files(config_dir: Path) -> Tuple[Path, Optional[Path], Optional[Path]]:
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 83cd876 and a445d20.

📒 Files selected for processing (9)
  • src/mcp_agent/cli/cloud/commands/deploy/main.py (8 hunks)
  • src/mcp_agent/cli/cloud/commands/deploy/wrangler_wrapper.py (1 hunks)
  • src/mcp_agent/cli/core/utils.py (1 hunks)
  • src/mcp_agent/cli/secrets/processor.py (12 hunks)
  • src/mcp_agent/cli/utils/ux.py (3 hunks)
  • tests/cli/commands/test_cli_secrets.py (0 hunks)
  • tests/cli/commands/test_deploy_command.py (5 hunks)
  • tests/cli/secrets/test_developer_secret_validation.py (0 hunks)
  • tests/cli/secrets/test_secrets_transform.py (12 hunks)
💤 Files with no reviewable changes (2)
  • tests/cli/commands/test_cli_secrets.py
  • tests/cli/secrets/test_developer_secret_validation.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-22T18:59:49.368Z
Learnt from: CR
PR: lastmile-ai/mcp-agent#0
File: examples/usecases/reliable_conversation/CLAUDE.md:0-0
Timestamp: 2025-07-22T18:59:49.368Z
Learning: Applies to examples/usecases/reliable_conversation/examples/reliable_conversation/src/utils/config.py : Configuration values such as quality_threshold, max_refinement_attempts, consolidation_interval, and evaluator_model_provider must be loaded from mcp_agent.config.yaml.

Applied to files:

  • src/mcp_agent/cli/cloud/commands/deploy/main.py
🧬 Code graph analysis (5)
tests/cli/commands/test_deploy_command.py (3)
src/mcp_agent/cli/cloud/commands/deploy/main.py (1)
  • deploy_config (41-280)
src/mcp_agent/cli/mcp_app/api_client.py (2)
  • get_app_id_by_name (285-307)
  • create_app (133-163)
src/mcp_agent/cli/mcp_app/mock_client.py (2)
  • get_app_id_by_name (36-45)
  • create_app (86-124)
tests/cli/secrets/test_secrets_transform.py (6)
src/mcp_agent/cli/secrets/mock_client.py (2)
  • create_secret (30-68)
  • get_secret_value (70-85)
src/mcp_agent/cli/secrets/api_client.py (2)
  • create_secret (15-70)
  • get_secret_value (72-98)
tests/cli/fixtures/mock_secrets_client.py (2)
  • create_secret (26-63)
  • get_secret_value (65-84)
src/mcp_agent/cli/core/constants.py (1)
  • SecretType (35-39)
src/mcp_agent/cli/secrets/processor.py (1)
  • transform_config_recursive (321-534)
src/mcp_agent/cli/secrets/yaml_tags.py (3)
  • UserSecret (24-27)
  • DeveloperSecret (30-33)
  • load_yaml_with_secrets (97-107)
src/mcp_agent/cli/cloud/commands/deploy/main.py (4)
src/mcp_agent/cli/utils/ux.py (3)
  • print_error (80-91)
  • print_info (32-49)
  • print_deployment_header (185-208)
src/mcp_agent/cli/core/api_client.py (1)
  • UnauthenticatedError (9-12)
src/mcp_agent/cli/core/utils.py (1)
  • run_async (12-27)
src/mcp_agent/cli/secrets/processor.py (1)
  • process_config_secrets (41-152)
src/mcp_agent/cli/secrets/processor.py (5)
src/mcp_agent/cli/exceptions.py (1)
  • CLIError (4-9)
src/mcp_agent/cli/utils/ux.py (3)
  • print_info (32-49)
  • print_warning (66-77)
  • print_error (80-91)
src/mcp_agent/cli/secrets/api_client.py (2)
  • get_secret_value (72-98)
  • create_secret (15-70)
src/mcp_agent/cli/secrets/yaml_tags.py (2)
  • DeveloperSecret (30-33)
  • UserSecret (24-27)
src/mcp_agent/cli/core/constants.py (1)
  • SecretType (35-39)
src/mcp_agent/cli/utils/ux.py (2)
src/mcp_agent/app.py (1)
  • logger (193-210)
src/mcp_agent/logging/logger.py (1)
  • info (271-279)
🔇 Additional comments (13)
src/mcp_agent/cli/cloud/commands/deploy/wrangler_wrapper.py (1)

12-14: Import formatting only — LGTM

No behavior change. Safe to merge.

src/mcp_agent/cli/core/utils.py (1)

30-33: Signature reflow — LGTM

Purely stylistic; no functional impact.

src/mcp_agent/cli/utils/ux.py (2)

100-107: Context-to-UI glue — LGTM

Mapping to the new keys (deployment_secrets, reused, skipped) is correct.


185-208: Header updates — LGTM

New app_name/app_id context is clear; logs show both secrets files.

tests/cli/secrets/test_secrets_transform.py (3)

56-83: Interactive deployment secret path — LGTM

Covers handle creation and value propagation correctly.


196-211: Empty value behavior test — LGTM

Matches skip-on-empty semantics.


334-451: Reuse path coverage — LGTM

Validates reuse, exclusion, and mixed user/deployment outputs.

tests/cli/commands/test_deploy_command.py (2)

84-95: Good: hermetic secrets processing via patched process_config_secrets


105-118: Patch targets look right

Patching process_config_secrets, MCPAppClient, and wrangler_deploy at their use sites keeps the test hermetic.

src/mcp_agent/cli/cloud/commands/deploy/main.py (4)

65-68: Non‑interactive help text is clear

Reads well and matches behavior. No change needed.


91-96: Docstring aligns with the deployed‑secrets flow

Accurately describes mcp_agent.deployed.secrets.yaml creation and use.


120-129: Good explicit validation for API URL and key

Clear error messages; no secret leakage in logs.


299-311: get_config_files is straightforward and robust

Clear errors and explicit returns. LGTM.

Comment on lines +388 to +404
elif isinstance(config_value, list):
# Process each item in the list
result_list = []
for i, value in enumerate(config_value):
new_path = f"{path}[{i}]" if path else f"[{i}]"
result_list.append(
await transform_config_recursive(
value,
client,
new_path,
non_interactive,
secrets_context,
existing_config,
)
)
return result_list

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Uncaught exceptions in list branch abort processing

A bad element in a list will bubble up and cancel the whole transform. Mirror the dict-branch try/except and track skipped paths.

-    elif isinstance(config_value, list):
-        # Process each item in the list
-        result_list = []
-        for i, value in enumerate(config_value):
-            new_path = f"{path}[{i}]" if path else f"[{i}]"
-            result_list.append(
-                await transform_config_recursive(
-                    value,
-                    client,
-                    new_path,
-                    non_interactive,
-                    secrets_context,
-                    existing_config,
-                )
-            )
-        return result_list
+    elif isinstance(config_value, list):
+        # Process each item in the list with per-item fault isolation
+        result_list = []
+        for i, value in enumerate(config_value):
+            new_path = f"{path}[{i}]" if path else f"[{i}]"
+            try:
+                item = await transform_config_recursive(
+                    value,
+                    client,
+                    new_path,
+                    non_interactive,
+                    secrets_context,
+                    existing_config,
+                )
+                result_list.append(item)
+            except Exception as e:
+                print_error(f"\nError processing secret at '{new_path}': {str(e)}\n Skipping this secret.")
+                if "skipped_secrets" not in secrets_context:
+                    secrets_context["skipped_secrets"] = []
+                secrets_context["skipped_secrets"].append(new_path)
+                # Skip this element entirely
+                continue
+        return result_list
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
elif isinstance(config_value, list):
# Process each item in the list
result_list = []
for i, value in enumerate(config_value):
new_path = f"{path}[{i}]" if path else f"[{i}]"
result_list.append(
await transform_config_recursive(
value,
client,
new_path,
non_interactive,
secrets_context,
existing_config,
)
)
return result_list
elif isinstance(config_value, list):
# Process each item in the list with per-item fault isolation
result_list = []
for i, value in enumerate(config_value):
new_path = f"{path}[{i}]" if path else f"[{i}]"
try:
item = await transform_config_recursive(
value,
client,
new_path,
non_interactive,
secrets_context,
existing_config,
)
result_list.append(item)
except Exception as e:
print_error(f"\nError processing secret at '{new_path}': {str(e)}\n Skipping this secret.")
if "skipped_secrets" not in secrets_context:
secrets_context["skipped_secrets"] = []
secrets_context["skipped_secrets"].append(new_path)
# Skip this element entirely
continue
return result_list
🤖 Prompt for AI Agents
In src/mcp_agent/cli/secrets/processor.py around lines 388-404, the list branch
currently lets exceptions from a single element abort the whole transform;
mirror the dict-branch behavior by wrapping each per-item transform call in a
try/except, on exception record the failing path (e.g. ensure secrets_context
has a skipped_paths list and append new_path) and append a placeholder (None or
the original value) to result_list so processing continues, and only re-raise
when the same non-interactive semantics require it (follow the dict-branch logic
for logging and control flow).

Copy link
Collaborator

@saqadri saqadri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks awesome!! Great work!!

@saqadri
Copy link
Collaborator

saqadri commented Sep 14, 2025

Going to merge this in so I can test it!

@saqadri saqadri merged commit ebf23c1 into main Sep 14, 2025
8 checks passed
saqadri pushed a commit that referenced this pull request Sep 14, 2025
* WIP starting point

* Reorder app creation and prompt to redeploy

* Update secrets processing

* Track skipped secrets

* Improve messaging with path

* Fix validation test

* Transform tests

* Clean up tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants