Skip to content

Conversation

@wzxxing
Copy link
Contributor

@wzxxing wzxxing commented Nov 25, 2025

Summary

Changes

Please provide a summary of what's being changed

When client fails, we want to the see the failure reason in the MCP client. This was possible with v1.0 where every request is independent, but with v1.1.1, to reuse the MCP session for every request, proxy takes an already connected mcp client, so the client initialization happens outside of the MCP client life cycle.

User experience

Please share what the user experience looks like before and after this change

The user should see a more detailed error message such as below, immediately after the connection is closed:

Failed to connect to MCP server "aws-local": MCP error -32001: Your AWS session token has expired. Please refresh your credentials and try again. If you're using temporary credentials, you may need to re-authenticate to obtain new credentials.

Previously v1.1.1 make the error message very unclear:

Failed to connect to MCP server "AWSMCPFromLatestPyPi": MCP error -32000: Connection closed

Testing

update MCP config as

"aws": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/aws/mcp-proxy-for-aws@wzxxing/write-last-error",
        "mcp-proxy-for-aws",
        "--service",
        "aws-mcp",
        "--profile",
        "mcp-tester",
        "--region",
        "us-east-1",
        "--log-level",
        "DEBUG",
        "<remote-mcp-server-url>"
      ]
    },

Expired credentials (Authn failure):

Screenshot 2025-11-26 at 10 24 34

Credentials without permission (Authz faillure)

image

Tool call failure (Authz), normal without terminating the transport

image

Checklist

If your change doesn't seem to apply, please leave them unchecked.

  • I have reviewed the contributing guidelines
  • I have performed a self-review of this change
  • Changes have been tested
  • Changes are documented

Is this a breaking change? (Y/N)

  • Yes
  • No

Please add details about how this change was tested.

  • Did integration tests succeed?
  • If the feature is a new use case, is it necessary to add a new integration test case?

Acknowledgment

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@wzxxing wzxxing force-pushed the wzxxing/write-last-error branch 2 times, most recently from 312c93d to 9b58283 Compare November 25, 2025 16:47
logger.error('Failed to start server: %s', e)
cause = e.__cause__
if isinstance(cause, McpProxy) and cause.payload:
print(cause.payload, file=sys.stdout)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is to print the initialization error to the process stdout so that the client can receive it and show the error message properly.

Copy link
Contributor

Choose a reason for hiding this comment

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

nit/qq: Maybe we want to do this also via the logger but point it to sys.stdout only in this case.

@wzxxing wzxxing force-pushed the wzxxing/write-last-error branch from 9b58283 to 10b44b3 Compare November 25, 2025 17:04
@wzxxing wzxxing changed the title write last error to stdout write initialize error to stdout Nov 25, 2025
@wzxxing wzxxing force-pushed the wzxxing/write-last-error branch from 10b44b3 to 8d26bf9 Compare November 25, 2025 17:05
"""Error which will be handled by the proxy writing the payload to stdout."""

def __init__(self, payload: str | None = None, *args: object) -> None:
"""Consturct a MCP proxy error with payload."""

Choose a reason for hiding this comment

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

Typo here.

if response.status_code == 401 or response.status_code == 403:
request = await response.request.aread()
request_body = json.loads(request.decode())
if isinstance(request_body, dict) and request_body.get('method') == 'initialize':

Choose a reason for hiding this comment

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

Why is this thrown only on initialize? I presume because other MCP operations would normally return 200 if they are implemented according to the spec. But we can't make that assumption especially as the proxy can be leveraged by any non-AWS service specific MCP server (for example an AgentCore Runtime MCP server would be feasible to use with the proxy).

Copy link
Contributor

Choose a reason for hiding this comment

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

would normally return 200 if they are implemented according to the spec.

Note, this is wrong. If done by spec, e.g. when a SessionId is expired the server must return 404 if its passed.
However I do agree this is not really respected properly 😓

name='MCP Proxy',
instructions=(
'MCP Proxy for AWS Server that provides access to backend servers through a single interface. '
'This proxy handles authentication and request routing to the appropriate backend services.'
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: SigV4 authentication and we don't really do any routing.

logger.error('Failed to start server: %s', e)
cause = e.__cause__
if isinstance(cause, McpProxy) and cause.payload:
print(cause.payload, file=sys.stdout)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit/qq: Maybe we want to do this also via the logger but point it to sys.stdout only in this case.

cause = e.__cause__
if isinstance(cause, McpProxyError) and cause.payload:
print(cause.payload, file=sys.stdout)
raise e
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: raise

Suggested change
raise e
raise

if response.status_code == 401 or response.status_code == 403:
request = await response.request.aread()
request_body = json.loads(request.decode())
if isinstance(request_body, dict) and request_body.get('method') == 'initialize':
Copy link
Contributor

Choose a reason for hiding this comment

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

would normally return 200 if they are implemented according to the spec.

Note, this is wrong. If done by spec, e.g. when a SessionId is expired the server must return 404 if its passed.
However I do agree this is not really respected properly 😓

error_details = response.json()
logger.log(log_level, 'HTTP %d Error Details: %s', response.status_code, error_details)
except Exception:
if response.status_code == 401 or response.status_code == 403:
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we do this on any 4xx / 5xx errors per default and ignore failures for maybe GET requests?

e.g. allowlist for know "ok" failures like GET requests instad of this surgical way?
wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Move the error handling outside of sigv4 helper

@wzxxing wzxxing force-pushed the wzxxing/write-last-error branch from 8d26bf9 to db7e54e Compare November 26, 2025 08:31
@wzxxing wzxxing marked this pull request as ready for review November 26, 2025 08:42
@wzxxing wzxxing requested a review from a team as a code owner November 26, 2025 08:42
@wzxxing wzxxing force-pushed the wzxxing/write-last-error branch from b540a25 to d777d6d Compare November 26, 2025 08:42
@wzxxing wzxxing enabled auto-merge (squash) November 26, 2025 09:33
@wzxxing wzxxing force-pushed the wzxxing/write-last-error branch from d777d6d to c6a9177 Compare November 26, 2025 13:06
arangatang
arangatang previously approved these changes Nov 26, 2025
Comment on lines +59 to +60
# line = sys.stdin.readline()
# logger.debug('First line from kiro %s', line)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
# line = sys.stdin.readline()
# logger.debug('First line from kiro %s', line)

Co-authored-by: Leonardo Araneda Freccero <arangatang@users.noreply.github.com>
Signed-off-by: wzxxing <169175349+wzxxing@users.noreply.github.com>
@wzxxing wzxxing merged commit 9632822 into main Nov 26, 2025
7 checks passed
@wzxxing wzxxing deleted the wzxxing/write-last-error branch November 26, 2025 15:00
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.

4 participants