Skip to content

[Bug]: APIKeySecurityScheme implementation parsing in the client #165

Closed

Description

@alberto-atk

What happened?

Hello everyone,

I am trying to do an example of an A2A client-server communication with APIKeySecurityScheme authentication.

My pydantic version is 2.11.3 and the a2a library version is the 0.2.5.

This is my agent card specification in the server:

agent_card = AgentCard(
        name='Carpenter Agent',
        description='This agent handles carpentry tasks using tools like hammer and screwdriver.',
        url=f'http://{host}:{port}/',
        version='1.0.0',
        defaultInputModes=CarpenterAgent.SUPPORTED_CONTENT_TYPES,
        defaultOutputModes=CarpenterAgent.SUPPORTED_CONTENT_TYPES,
        capabilities=capabilities,
        skills=skills,
        securitySchemes={
           "api-key": {
               "description":"the description of the security schema",
               "in":In.header,
               "name":"passed_api_key",
               "type":"apiKey"}
       }
    ) 

The problem is in the A2ACardResolver when it retrieves the card with the get_agent_card() method, as the server sends the 'in' parameter in the securityScheme like 'in_' (using the 'model_dump' from pydantic) so the model_validate() in the client cannot parse it and throws the error showed in the log.

The solution I've managed is to remove the Field in the 'in_' types definition for APIKeySecurityScheme:

class APIKeySecurityScheme(BaseModel):
    """
    API Key security scheme.
    """

    description: str | None = None
    """
    Description of this security scheme.
    """
    in_: In
    """
    The location of the API key. Valid values are "query", "header", or "cookie".
    """
    name: str
    """
    The name of the header, query or cookie parameter to be used.
    """
    type: Literal['apiKey'] = 'apiKey

And having my new card like this:

agent_card = AgentCard(
        name='Carpenter Agent',
        description='This agent handles carpentry tasks using tools like hammer and screwdriver.',
        url=f'http://{host}:{port}/',
        version='1.0.0',
        defaultInputModes=CarpenterAgent.SUPPORTED_CONTENT_TYPES,
        defaultOutputModes=CarpenterAgent.SUPPORTED_CONTENT_TYPES,
        capabilities=capabilities,
        skills=skills,
        securitySchemes={
           "api-key": {
               "description":"the description of the security schema",
               "in_":In.header,
               "name":"passed_api_key",
               "type":"apiKey"}
       }
    )

Should I add it as a new PR or there is an existant solution?

Thank you so much!

Relevant log output

a2a.client.errors.A2AClientJSONError: JSON Error: Failed to validate agent card structure from http://localhost:8080/.well-known/agent.json: [{"type":"missing","loc":["securitySchemes","api-key","APIKeySecurityScheme","in"],"msg":"Field required","input":{"description":"the description of the security schema","in_":"header","name":"passed_api_key","type":"apiKey"},"url":"https://errors.pydantic.dev/2.11/v/missing"},{"type":"missing","loc":["securitySchemes","api-key","HTTPAuthSecurityScheme","scheme"],"msg":"Field required","input":{"description":"the description of the security schema","in_":"header","name":"passed_api_key","type":"apiKey"},"url":"https://errors.pydantic.dev/2.11/v/missing"},{"type":"literal_error","loc":["securitySchemes","api-key","HTTPAuthSecurityScheme","type"],"msg":"Input should be 'http'","input":"apiKey","ctx":{"expected":"'http'"},"url":"https://errors.pydantic.dev/2.11/v/literal_error"},{"type":"missing","loc":["securitySchemes","api-key","OAuth2SecurityScheme","flows"],"msg":"Field required","input":{"description":"the description of the security schema","in_":"header","name":"passed_api_key","type":"apiKey"},"url":"https://errors.pydantic.dev/2.11/v/missing"},{"type":"literal_error","loc":["securitySchemes","api-key","OAuth2SecurityScheme","type"],"msg":"Input should be 'oauth2'","input":"apiKey","ctx":{"expected":"'oauth2'"},"url":"https://errors.pydantic.dev/2.11/v/literal_error"},{"type":"missing","loc":["securitySchemes","api-key","OpenIdConnectSecurityScheme","openIdConnectUrl"],"msg":"Field required","input":{"description":"the description of the security schema","in_":"header","name":"passed_api_key","type":"apiKey"},"url":"https://errors.pydantic.dev/2.11/v/missing"},{"type":"literal_error","loc":["securitySchemes","api-key","OpenIdConnectSecurityScheme","type"],"msg":"Input should be 'openIdConnect'","input":"apiKey","ctx":{"expected":"'openIdConnect'"},"url":"https://errors.pydantic.dev/2.11/v/literal_error"}]

Code of Conduct

  • I agree to follow this project's Code of Conduct

Activity

  1. changed the title [-][Bug]:[/-] [+][Bug]: APIKeySecurityScheme implementation parsing in the client[/+] on Jun 6, 2025
  2. idgu-dev commented on Jun 11, 2025

    @idgu-dev

    Hi, i am also facing the same issue. Looking forward to this bug to be fixed

  3. holtskinner commented on Jun 13, 2025

    @holtskinner
    Member

    Hello! Thank you for the excellent and detailed bug report. This is a fantastic catch and highlights a subtle but important aspect of working with Pydantic models that have field aliases for reserved keywords.

    The good news is that the SDK's type definitions are correct, and this can be resolved with a small adjustment in how the AgentCard is created on your server, without needing any changes to the SDK itself.

    The Explanation

    The core of the issue lies in the difference between a Python object's attribute name and its JSON alias, especially for reserved keywords like in.

    1. Why the SDK uses in_: Because in is a reserved keyword in Python, it cannot be used directly as a variable or attribute name. The standard practice (as recommended by PEP 8) is to append a trailing underscore, making it in_. This is what we've done in the APIKeySecurityScheme model.

    2. The Role of alias='in': The Field(..., alias='in') part in the model is the bridge between the Python world and the JSON world. It tells Pydantic:

      • When creating from JSON: "When you see a JSON key named in, use its value to populate the Python attribute named in_." (This is what happens on the client side).
      • When dumping to JSON: "When you serialize this model to JSON, take the value from the in_ attribute and put it under a key named in." (This is what happens on the server side when it serves the agent card).

    The Fix

    The problem is in how the APIKeySecurityScheme dictionary is defined in your server-side Python code. When you are creating a Pydantic model instance from a Python dictionary, you must use the Python attribute names, not the JSON aliases.

    Your current code uses the JSON alias "in" as a dictionary key:

    # Your current server-side code
    agent_card = AgentCard(
        ...
        securitySchemes={
           "api-key": {
               "description": "...",
               "in": In.header,  # <--- This should be the Python attribute name
               "name": "passed_api_key",
               "type": "apiKey"
           }
       }
    )

    When Pydantic validates this, it doesn't recognize the in key for instantiation and therefore raises a validation error because the required in_ field is missing. When model_dump is later called on the server, it correctly uses the alias and sends "in" over the wire, which is what the client expects.

    The fix is to use the Python attribute name in_ when defining the dictionary on your server:

    # Corrected server-side code
    agent_card = AgentCard(
        name='Carpenter Agent',
        description='This agent handles carpentry tasks using tools like hammer and screwdriver.',
        url=f'http://{host}:{port}/',
        version='1.0.0',
        defaultInputModes=CarpenterAgent.SUPPORTED_CONTENT_TYPES,
        defaultOutputModes=CarpenterAgent.SUPPORTED_CONTENT_TYPES,
        capabilities=capabilities,
        skills=skills,
        securitySchemes={
           "api-key": {
               "description": "the description of the security schema",
               "in_": In.header,  # <-- Use the Python field name 'in_'
               "name": "passed_api_key",
               "type": "apiKey"
           }
       }
    )

    Or you can just use the APIKeySecurityScheme class instead of a dict, which should map to the correct field.

    With this change, your server will correctly instantiate the AgentCard model. Then, when the A2ACardResolver on the client side requests the card, your server will serialize the model to JSON, correctly converting the in_ attribute back to an "in" key, which the client will then parse successfully.

    Your proposed solution of removing the alias works, but it would make the SDK non-compliant with the A2A specification, which requires the on-the-wire JSON key to be "in".

    Thank you again for flagging this! It's a very common point of confusion with Pydantic, and your report helps clarify it for everyone. No PR is needed for the SDK itself.

  4. alberto-atk commented on Jun 16, 2025

    @alberto-atk
    Author

    Hello again.

    I've tried several things, including the two options that you mentioned in the examples, but it is still not working:

    Test 1: Object + keyword argument

        agent_card = AgentCard(
            name='Carpenter Agent',
            description='This agent handles carpentry tasks using tools like hammer and screwdriver.',
            url=f'http://{host}:{port}/',
            version='1.0.0',
            defaultInputModes=CarpenterAgent.SUPPORTED_CONTENT_TYPES,
            defaultOutputModes=CarpenterAgent.SUPPORTED_CONTENT_TYPES,
            capabilities=capabilities,
            skills=skills,
            securitySchemes={
               "api-key": APIKeySecurityScheme(
                   description="the description of the security schema",
                   in_=In.header,
                   name="passed_api_key",
                   type="apiKey"),
            },
            security=[{
               "api-key": ["a valid api key"]
           }]
        )

    Error in server:

    validated_self = self.pydantic_validator.validate_python(data, self_instance=self)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    pydantic_core._pydantic_core.ValidationError: 1 validation error for APIKeySecurityScheme
    in
    Field required [type=missing, input_value={'description': 'the desc..._key', 'type': 'apiKey'}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.11/v/missing

    Test 2: Object + without keyword argument

        agent_card = AgentCard(
            name='Carpenter Agent',
            description='This agent handles carpentry tasks using tools like hammer and screwdriver.',
            url=f'http://{host}:{port}/',
            version='1.0.0',
            defaultInputModes=CarpenterAgent.SUPPORTED_CONTENT_TYPES,
            defaultOutputModes=CarpenterAgent.SUPPORTED_CONTENT_TYPES,
            capabilities=capabilities,
            skills=skills,
            securitySchemes={
               "api-key": APIKeySecurityScheme(
                   "the description of the security schema", 
                   In.header,
                   "passed_api_key", 
                   "apiKey"),
            },
            security=[{
               "api-key": ["a valid api key"]
           }]
        )

    Error in server:

    "api-key": APIKeySecurityScheme(
    ^^^^^^^^^^^^^^^^^^^^^
    TypeError: BaseModel.init() takes 1 positional argument but 5 were given

    Test 3: Object + dict init with in_

        agent_card = AgentCard(
            name='Carpenter Agent',
            description='This agent handles carpentry tasks using tools like hammer and screwdriver.',
            url=f'http://{host}:{port}/',
            version='1.0.0',
            defaultInputModes=CarpenterAgent.SUPPORTED_CONTENT_TYPES,
            defaultOutputModes=CarpenterAgent.SUPPORTED_CONTENT_TYPES,
            capabilities=capabilities,
            skills=skills,
            securitySchemes={
               "api-key": APIKeySecurityScheme(**{
                   "description": "the description of the security schema",
                   "in_": In.header,
                   "name": "passed_api_key",
                   "type": "apiKey"}),
            },
            security=[{
               "api-key": ["a valid api key"]
           }]
        )

    Error in server:

    pydantic_core._pydantic_core.ValidationError: 1 validation error for APIKeySecurityScheme
    in
    Field required [type=missing, input_value={'description': 'the desc..._key', 'type': 'apiKey'}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.11/v/missing

    Test 4: Object + dict init with in

        agent_card = AgentCard(
            name='Carpenter Agent',
            description='This agent handles carpentry tasks using tools like hammer and screwdriver.',
            url=f'http://{host}:{port}/',
            version='1.0.0',
            defaultInputModes=CarpenterAgent.SUPPORTED_CONTENT_TYPES,
            defaultOutputModes=CarpenterAgent.SUPPORTED_CONTENT_TYPES,
            capabilities=capabilities,
            skills=skills,
            securitySchemes={
               "api-key": APIKeySecurityScheme(**{
                   "description": "the description of the security schema",
                   "in": In.header,
                   "name": "passed_api_key",
                   "type": "apiKey"}),
            },
            security=[{
               "api-key": ["a valid api key"]
           }]
        )

    Error in client:

    a2a.client.errors.A2AClientJSONError: JSON Error: Failed to validate agent card structure from http://localhost:8080/.well-known/agent.json: [{"type":"missing","loc":["securitySchemes","api-key","APIKeySecurityScheme","in"],"msg":"Field required","input":{"description":"the description of the security schema","in_":"header","name":"passed_api_key","type":"apiKey"},"url":"https://errors.pydantic.dev/2.11/v/missing"},{"type":"missing","loc":["securitySchemes","api-key","HTTPAuthSecurityScheme","scheme"],"msg":"Field required","input":{"description":"the description of the security schema","in_":"header","name":"passed_api_key","type":"apiKey"},"url":"https://errors.pydantic.dev/2.11/v/missing"},{"type":"literal_error","loc":["securitySchemes","api-key","HTTPAuthSecurityScheme","type"],"msg":"Input should be 'http'","input":"apiKey","ctx":{"expected":"'http'"},"url":"https://errors.pydantic.dev/2.11/v/literal_error"},{"type":"missing","loc":["securitySchemes","api-key","OAuth2SecurityScheme","flows"],"msg":"Field required","input":{"description":"the description of the security schema","in_":"header","name":"passed_api_key","type":"apiKey"},"url":"https://errors.pydantic.dev/2.11/v/missing"},{"type":"literal_error","loc":["securitySchemes","api-key","OAuth2SecurityScheme","type"],"msg":"Input should be 'oauth2'","input":"apiKey","ctx":{"expected":"'oauth2'"},"url":"https://errors.pydantic.dev/2.11/v/literal_error"},{"type":"missing","loc":["securitySchemes","api-key","OpenIdConnectSecurityScheme","openIdConnectUrl"],"msg":"Field required","input":{"description":"the description of the security schema","in_":"header","name":"passed_api_key","type":"apiKey"},"url":"https://errors.pydantic.dev/2.11/v/missing"},{"type":"literal_error","loc":["securitySchemes","api-key","OpenIdConnectSecurityScheme","type"],"msg":"Input should be 'openIdConnect'","input":"apiKey","ctx":{"expected":"'openIdConnect'"},"url":"https://errors.pydantic.dev/2.11/v/literal_error"}]

    Test 5: dict init with in

        agent_card = AgentCard(
            name='Carpenter Agent',
            description='This agent handles carpentry tasks using tools like hammer and screwdriver.',
            url=f'http://{host}:{port}/',
            version='1.0.0',
            defaultInputModes=CarpenterAgent.SUPPORTED_CONTENT_TYPES,
            defaultOutputModes=CarpenterAgent.SUPPORTED_CONTENT_TYPES,
            capabilities=capabilities,
            skills=skills,
            securitySchemes={
               "api-key": {
                   "description": "the description of the security schema",
                   "in_": In.header,
                   "name": "passed_api_key",
                   "type": "apiKey"
                },
            },
            security=[{
               "api-key": ["a valid api key"]
           }]
        )

    Error in server:

    pydantic_core._pydantic_core.ValidationError: 7 validation errors for AgentCard
    securitySchemes.api-key.APIKeySecurityScheme.in
    Field required [type=missing, input_value={'description': 'the desc..._key', 'type': 'apiKey'}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.11/v/missing
    securitySchemes.api-key.HTTPAuthSecurityScheme.scheme
    Field required [type=missing, input_value={'description': 'the desc..._key', 'type': 'apiKey'}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.11/v/missing
    securitySchemes.api-key.HTTPAuthSecurityScheme.type
    Input should be 'http' [type=literal_error, input_value='apiKey', input_type=str]
    For further information visit https://errors.pydantic.dev/2.11/v/literal_error
    securitySchemes.api-key.OAuth2SecurityScheme.flows
    Field required [type=missing, input_value={'description': 'the desc..._key', 'type': 'apiKey'}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.11/v/missing
    securitySchemes.api-key.OAuth2SecurityScheme.type
    Input should be 'oauth2' [type=literal_error, input_value='apiKey', input_type=str]
    For further information visit https://errors.pydantic.dev/2.11/v/literal_error
    securitySchemes.api-key.OpenIdConnectSecurityScheme.openIdConnectUrl
    Field required [type=missing, input_value={'description': 'the desc..._key', 'type': 'apiKey'}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.11/v/missing
    securitySchemes.api-key.OpenIdConnectSecurityScheme.type
    Input should be 'openIdConnect' [type=literal_error, input_value='apiKey', input_type=str]
    For further information visit https://errors.pydantic.dev/2.11/v/literal_error

  5. fernando-torres-blip-ai commented on Jun 16, 2025

    @fernando-torres-blip-ai
    Contributor

    Also facing same issue, the field "_in" alias must be python compliant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions