Skip to content

Commit be6c517

Browse files
authored
feat!: Add mTLS to SecuritySchemes, add oauth2 metadata url field, allow Skills to specify Security (#362)
# Description This PR makes changes to our security representations in AgentCards: 1. Adds a new MutualTLSSecurityScheme type for indicating mTLS connection requirements. This matches the same field in the OpenAPI specification v3.1. This type is pretty lacking: there is no way to indicate any requirements on the certificate that is presented by the client. I expect we'll want to extend this in the future (for example, we may want to allow an agent to indicate that a SPIFFE certificate must be presented). For now, users are advised to put requirements in the description field. 2. Adds a field for specifying a URL for OAuth2 provider metadata. This is planned for OpenAPI Specification v3.2, which has not yet been released. This allows clients to fetch additional information about the OAuth provider, such as to discover if they support Dynamic Client Registration. 3. Allows AgentSkills to specify a `security` field. This allows agents to indicate security requirements for leveraging a particular skill. I also clarified the usage and expectations for the `security` field in the base AgentCard. This change is marked backwards incompatible due to the addition of the MutualTLSSecurityScheme, which adds a new type to an exhaustive enum (the security scheme `anyOf`). Release-As: 0.3.0 Protocol Update: a2aproject/A2A@e162c0c
1 parent b567e80 commit be6c517

File tree

4 files changed

+129
-77
lines changed

4 files changed

+129
-77
lines changed

.github/actions/spelling/allow.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ genai
3939
getkwargs
4040
gle
4141
GVsb
42+
ietf
4243
initdb
4344
inmemory
4445
INR

src/a2a/grpc/a2a_pb2.py

Lines changed: 72 additions & 70 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/a2a/grpc/a2a_pb2.pyi

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -277,22 +277,24 @@ class AgentExtension(_message.Message):
277277
def __init__(self, uri: _Optional[str] = ..., description: _Optional[str] = ..., required: bool = ..., params: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ...) -> None: ...
278278

279279
class AgentSkill(_message.Message):
280-
__slots__ = ("id", "name", "description", "tags", "examples", "input_modes", "output_modes")
280+
__slots__ = ("id", "name", "description", "tags", "examples", "input_modes", "output_modes", "security")
281281
ID_FIELD_NUMBER: _ClassVar[int]
282282
NAME_FIELD_NUMBER: _ClassVar[int]
283283
DESCRIPTION_FIELD_NUMBER: _ClassVar[int]
284284
TAGS_FIELD_NUMBER: _ClassVar[int]
285285
EXAMPLES_FIELD_NUMBER: _ClassVar[int]
286286
INPUT_MODES_FIELD_NUMBER: _ClassVar[int]
287287
OUTPUT_MODES_FIELD_NUMBER: _ClassVar[int]
288+
SECURITY_FIELD_NUMBER: _ClassVar[int]
288289
id: str
289290
name: str
290291
description: str
291292
tags: _containers.RepeatedScalarFieldContainer[str]
292293
examples: _containers.RepeatedScalarFieldContainer[str]
293294
input_modes: _containers.RepeatedScalarFieldContainer[str]
294295
output_modes: _containers.RepeatedScalarFieldContainer[str]
295-
def __init__(self, id: _Optional[str] = ..., name: _Optional[str] = ..., description: _Optional[str] = ..., tags: _Optional[_Iterable[str]] = ..., examples: _Optional[_Iterable[str]] = ..., input_modes: _Optional[_Iterable[str]] = ..., output_modes: _Optional[_Iterable[str]] = ...) -> None: ...
296+
security: _containers.RepeatedCompositeFieldContainer[Security]
297+
def __init__(self, id: _Optional[str] = ..., name: _Optional[str] = ..., description: _Optional[str] = ..., tags: _Optional[_Iterable[str]] = ..., examples: _Optional[_Iterable[str]] = ..., input_modes: _Optional[_Iterable[str]] = ..., output_modes: _Optional[_Iterable[str]] = ..., security: _Optional[_Iterable[_Union[Security, _Mapping]]] = ...) -> None: ...
296298

297299
class AgentCardSignature(_message.Message):
298300
__slots__ = ("protected", "signature", "header")
@@ -332,16 +334,18 @@ class Security(_message.Message):
332334
def __init__(self, schemes: _Optional[_Mapping[str, StringList]] = ...) -> None: ...
333335

334336
class SecurityScheme(_message.Message):
335-
__slots__ = ("api_key_security_scheme", "http_auth_security_scheme", "oauth2_security_scheme", "open_id_connect_security_scheme")
337+
__slots__ = ("api_key_security_scheme", "http_auth_security_scheme", "oauth2_security_scheme", "open_id_connect_security_scheme", "mtls_security_scheme")
336338
API_KEY_SECURITY_SCHEME_FIELD_NUMBER: _ClassVar[int]
337339
HTTP_AUTH_SECURITY_SCHEME_FIELD_NUMBER: _ClassVar[int]
338340
OAUTH2_SECURITY_SCHEME_FIELD_NUMBER: _ClassVar[int]
339341
OPEN_ID_CONNECT_SECURITY_SCHEME_FIELD_NUMBER: _ClassVar[int]
342+
MTLS_SECURITY_SCHEME_FIELD_NUMBER: _ClassVar[int]
340343
api_key_security_scheme: APIKeySecurityScheme
341344
http_auth_security_scheme: HTTPAuthSecurityScheme
342345
oauth2_security_scheme: OAuth2SecurityScheme
343346
open_id_connect_security_scheme: OpenIdConnectSecurityScheme
344-
def __init__(self, api_key_security_scheme: _Optional[_Union[APIKeySecurityScheme, _Mapping]] = ..., http_auth_security_scheme: _Optional[_Union[HTTPAuthSecurityScheme, _Mapping]] = ..., oauth2_security_scheme: _Optional[_Union[OAuth2SecurityScheme, _Mapping]] = ..., open_id_connect_security_scheme: _Optional[_Union[OpenIdConnectSecurityScheme, _Mapping]] = ...) -> None: ...
347+
mtls_security_scheme: MutualTlsSecurityScheme
348+
def __init__(self, api_key_security_scheme: _Optional[_Union[APIKeySecurityScheme, _Mapping]] = ..., http_auth_security_scheme: _Optional[_Union[HTTPAuthSecurityScheme, _Mapping]] = ..., oauth2_security_scheme: _Optional[_Union[OAuth2SecurityScheme, _Mapping]] = ..., open_id_connect_security_scheme: _Optional[_Union[OpenIdConnectSecurityScheme, _Mapping]] = ..., mtls_security_scheme: _Optional[_Union[MutualTlsSecurityScheme, _Mapping]] = ...) -> None: ...
345349

346350
class APIKeySecurityScheme(_message.Message):
347351
__slots__ = ("description", "location", "name")
@@ -364,12 +368,14 @@ class HTTPAuthSecurityScheme(_message.Message):
364368
def __init__(self, description: _Optional[str] = ..., scheme: _Optional[str] = ..., bearer_format: _Optional[str] = ...) -> None: ...
365369

366370
class OAuth2SecurityScheme(_message.Message):
367-
__slots__ = ("description", "flows")
371+
__slots__ = ("description", "flows", "oauth2_metadata_url")
368372
DESCRIPTION_FIELD_NUMBER: _ClassVar[int]
369373
FLOWS_FIELD_NUMBER: _ClassVar[int]
374+
OAUTH2_METADATA_URL_FIELD_NUMBER: _ClassVar[int]
370375
description: str
371376
flows: OAuthFlows
372-
def __init__(self, description: _Optional[str] = ..., flows: _Optional[_Union[OAuthFlows, _Mapping]] = ...) -> None: ...
377+
oauth2_metadata_url: str
378+
def __init__(self, description: _Optional[str] = ..., flows: _Optional[_Union[OAuthFlows, _Mapping]] = ..., oauth2_metadata_url: _Optional[str] = ...) -> None: ...
373379

374380
class OpenIdConnectSecurityScheme(_message.Message):
375381
__slots__ = ("description", "open_id_connect_url")
@@ -379,6 +385,12 @@ class OpenIdConnectSecurityScheme(_message.Message):
379385
open_id_connect_url: str
380386
def __init__(self, description: _Optional[str] = ..., open_id_connect_url: _Optional[str] = ...) -> None: ...
381387

388+
class MutualTlsSecurityScheme(_message.Message):
389+
__slots__ = ("description",)
390+
DESCRIPTION_FIELD_NUMBER: _ClassVar[int]
391+
description: str
392+
def __init__(self, description: _Optional[str] = ...) -> None: ...
393+
382394
class OAuthFlows(_message.Message):
383395
__slots__ = ("authorization_code", "client_credentials", "implicit", "password")
384396
AUTHORIZATION_CODE_FIELD_NUMBER: _ClassVar[int]

src/a2a/types.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,15 @@ class AgentSkill(A2ABaseModel):
164164
"""
165165
The set of supported output MIME types for this skill, overriding the agent's defaults.
166166
"""
167+
security: list[dict[str, list[str]]] | None = Field(
168+
default=None, examples=[[{'google': ['oidc']}]]
169+
)
170+
"""
171+
Security schemes necessary for the agent to leverage this skill.
172+
As in the overall AgentCard.security, this list represents a logical OR of security
173+
requirement objects. Each object is a set of security schemes that must be used together
174+
(a logical AND).
175+
"""
167176
tags: list[str] = Field(
168177
..., examples=[['cooking', 'customer support', 'billing']]
169178
)
@@ -730,6 +739,21 @@ class MethodNotFoundError(A2ABaseModel):
730739
"""
731740

732741

742+
class MutualTLSSecurityScheme(A2ABaseModel):
743+
"""
744+
Defines a security scheme using mTLS authentication.
745+
"""
746+
747+
description: str | None = None
748+
"""
749+
An optional description for the security scheme.
750+
"""
751+
type: Literal['mutualTLS'] = 'mutualTLS'
752+
"""
753+
The type of the security scheme. Must be 'mutualTLS'.
754+
"""
755+
756+
733757
class OpenIdConnectSecurityScheme(A2ABaseModel):
734758
"""
735759
Defines a security scheme using OpenID Connect.
@@ -1486,6 +1510,11 @@ class OAuth2SecurityScheme(A2ABaseModel):
14861510
"""
14871511
An object containing configuration information for the supported OAuth 2.0 flows.
14881512
"""
1513+
oauth2_metadata_url: str | None = None
1514+
"""
1515+
URL to the oauth2 authorization server metadata
1516+
[RFC8414](https://datatracker.ietf.org/doc/html/rfc8414). TLS is required.
1517+
"""
14891518
type: Literal['oauth2'] = 'oauth2'
14901519
"""
14911520
The type of the security scheme. Must be 'oauth2'.
@@ -1498,13 +1527,15 @@ class SecurityScheme(
14981527
| HTTPAuthSecurityScheme
14991528
| OAuth2SecurityScheme
15001529
| OpenIdConnectSecurityScheme
1530+
| MutualTLSSecurityScheme
15011531
]
15021532
):
15031533
root: (
15041534
APIKeySecurityScheme
15051535
| HTTPAuthSecurityScheme
15061536
| OAuth2SecurityScheme
15071537
| OpenIdConnectSecurityScheme
1538+
| MutualTLSSecurityScheme
15081539
)
15091540
"""
15101541
Defines a security scheme that can be used to secure an agent's endpoints.
@@ -1762,10 +1793,16 @@ class AgentCard(A2ABaseModel):
17621793
"""
17631794
Information about the agent's service provider.
17641795
"""
1765-
security: list[dict[str, list[str]]] | None = None
1796+
security: list[dict[str, list[str]]] | None = Field(
1797+
default=None,
1798+
examples=[[{'oauth': ['read']}, {'api-key': [], 'mtls': []}]],
1799+
)
17661800
"""
17671801
A list of security requirement objects that apply to all agent interactions. Each object
17681802
lists security schemes that can be used. Follows the OpenAPI 3.0 Security Requirement Object.
1803+
This list can be seen as an OR of ANDs. Each object in the list describes one possible
1804+
set of security requirements that must be present on a request. This allows specifying,
1805+
for example, "callers must either use OAuth OR an API Key AND mTLS."
17691806
"""
17701807
security_schemes: dict[str, SecurityScheme] | None = None
17711808
"""

0 commit comments

Comments
 (0)