Skip to content

Commit a903b68

Browse files
committed
Bedrock auth: don't crash MCP at import when AWS profile is ambiguous
0.3.2 introduced a regression: BedrockClient is constructed at module import time in eval_mcp/server.py, and _autodetect_aws_profile() raised RuntimeError when it found multiple AWS profiles with no AWS_PROFILE chosen. The exception propagated through the click entry point and killed the MCP process with exit code 1 before it could handshake with the IDE — Claude Code / Cursor / etc. just showed "Failed to connect" with no actionable message. Affects every user with two or more SSO profiles configured in ~/.aws/config who didn't explicitly set AWS_PROFILE in their MCP env block. That's the exact population the autodetect was supposed to help. Change the autodetect to record the ambiguity error in a module-level slot instead of raising. Bedrock tool entry points (list_bedrock_models, ...) now call get_autodetect_error() before issuing AWS calls and return the structured "Multiple AWS profiles configured" message in the tool response, where it actually reaches the user. The MCP starts cleanly in all cases. Also harden _is_profile_logged_in's caller: when an SSO token registration itself has expired (not just the credentials), the probe can raise rather than just return False. Wrap the call so autodetect never escapes.
1 parent d41e2f7 commit a903b68

2 files changed

Lines changed: 36 additions & 16 deletions

File tree

eval_mcp/core/bedrock_client.py

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -80,22 +80,22 @@ def _autodetect_aws_profile() -> None:
8080
AWS_BEARER_TOKEN_BEDROCK) → leave them alone.
8181
2. Saved choice in ~/.config/eval-mcp/profile → use it.
8282
3. Exactly one profile in ~/.aws/config → use it and persist the name.
83-
4. Multiple profiles → raise with the list (and login-state hint) so the
84-
caller / Claude can ask the user. The chosen name is then written to
85-
PROFILE_CONFIG_PATH (or AWS_PROFILE set in the MCP env block).
83+
4. Multiple profiles → record an "ambiguous" error message that Bedrock
84+
tool entry points retrieve via get_autodetect_error() and surface to
85+
the user. **Never raises from this function**, so importing the MCP
86+
server (which constructs a BedrockClient at module load) cannot crash
87+
the whole process before Claude Code can talk to it.
8688
8789
Token validity is never used to route — only the saved name. If the chosen
8890
profile's SSO token has expired, boto3 will surface a clear auth error and
8991
`aws sso login --profile <name>` fixes it. The MCP will not silently
9092
reroute to a different account.
9193
92-
Runs at most once per process; result (or error) is cached.
94+
Runs at most once per process; result is cached.
9395
"""
9496
global _autodetect_done, _autodetect_error
9597
with _autodetect_lock:
9698
if _autodetect_done:
97-
if _autodetect_error is not None:
98-
raise _autodetect_error
9999
return
100100
_autodetect_done = True
101101

@@ -127,12 +127,22 @@ def _autodetect_aws_profile() -> None:
127127
logger.info("Auto-selected sole AWS profile: %s", configured[0])
128128
return
129129

130-
# (4) ambiguous — ask the user (via the MCP error → Claude relay)
130+
# (4) ambiguous — record the error for tool entry points to surface.
131+
# Do NOT raise here: this runs at module import time via
132+
# BedrockClient.__init__, and raising would kill the MCP before it
133+
# can serve any tools — Claude Code just sees "Failed to connect."
131134
lines = []
132135
for name in configured:
133-
state = "logged in" if _is_profile_logged_in(name) else "not logged in"
136+
try:
137+
logged_in = _is_profile_logged_in(name)
138+
except Exception:
139+
# Token probe itself can throw (e.g. SSO token registration
140+
# expired). Treat as "not logged in" rather than letting the
141+
# error escape autodetect.
142+
logged_in = False
143+
state = "logged in" if logged_in else "not logged in"
134144
lines.append(f" - {name} ({state})")
135-
err = RuntimeError(
145+
_autodetect_error = RuntimeError(
136146
"Multiple AWS profiles configured — the eval MCP needs you to pick one:\n"
137147
+ "\n".join(lines)
138148
+ "\n\nTo save your choice, either:\n"
@@ -142,8 +152,15 @@ def _autodetect_aws_profile() -> None:
142152
+ "if its SSO token expires, run `aws sso login --profile <name>` — "
143153
+ "the MCP will not silently switch to a different profile."
144154
)
145-
_autodetect_error = err
146-
raise err
155+
logger.warning("AWS profile autodetect: %s", _autodetect_error)
156+
157+
158+
def get_autodetect_error() -> Optional[Exception]:
159+
"""Return the autodetect ambiguity error, if any. Tool entry points call
160+
this to convert the error into a structured response instead of letting
161+
AWS calls fail with cryptic 'no credentials' messages."""
162+
_autodetect_aws_profile()
163+
return _autodetect_error
147164

148165

149166
def create_boto3_bedrock_client(service: str = "bedrock-runtime", region: str = "us-west-2", **extra_config):

eval_mcp/tools/bedrock_models.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
"""
1313

1414
import os
15-
from eval_mcp.core.bedrock_client import create_boto3_bedrock_client
15+
from eval_mcp.core.bedrock_client import (
16+
create_boto3_bedrock_client,
17+
get_autodetect_error,
18+
)
1619
from eval_mcp.tools.external_providers import (
1720
detect_available_providers,
1821
get_external_models,
@@ -125,10 +128,10 @@ def list_bedrock_models(
125128
Returns a dict (callers JSON-encode). Filtered against SUPPORTED_MODELS
126129
so only entries a human has validated for the eval pipeline are surfaced.
127130
"""
128-
try:
129-
bedrock_client = create_boto3_bedrock_client('bedrock', region)
130-
except RuntimeError as e:
131-
return {"models": [], "count": 0, "error": str(e)}
131+
err = get_autodetect_error()
132+
if err is not None:
133+
return {"models": [], "count": 0, "error": str(err)}
134+
bedrock_client = create_boto3_bedrock_client('bedrock', region)
132135

133136
# Patterns to exclude for text_only mode
134137
exclude_patterns = ['stability.', 'embed', 'upscale', 'inpaint', 'outpaint', 'image', 'pegasus']

0 commit comments

Comments
 (0)