From cd9163c1aaec0e3c82fa34466d89daf37c15ad2a Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Thu, 20 Nov 2025 11:07:06 +0000 Subject: [PATCH 01/13] first commint implementation of authentication on server side --- src/server/authentication/user.ts | 30 ++++++++++++++++++ src/server/context.ts | 10 +++++- src/server/express/auth_handler.ts | 44 ++++++++++++++++++++++++++ src/server/express/json_rpc_handler.ts | 3 ++ 4 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 src/server/authentication/user.ts create mode 100644 src/server/express/auth_handler.ts diff --git a/src/server/authentication/user.ts b/src/server/authentication/user.ts new file mode 100644 index 00000000..a9d79350 --- /dev/null +++ b/src/server/authentication/user.ts @@ -0,0 +1,30 @@ +export interface User { + getName(): string; + isAuthenticated(): boolean; +} + +export class AuthenticatedUser implements User { + private readonly name: string; + + constructor(name: string) { + this.name = name; + } + + public getName(): string { + return this.name; + } + + public isAuthenticated(): boolean { + return true; + } +} + +export class unAuthenticatedUser implements User { + public getName(): string { + return 'unAuthenticatedUser'; + } + + public isAuthenticated(): boolean { + return false; + } +} diff --git a/src/server/context.ts b/src/server/context.ts index 38678e62..1015eb99 100644 --- a/src/server/context.ts +++ b/src/server/context.ts @@ -1,9 +1,17 @@ +import { User } from './authentication/user.js'; + export class ServerCallContext { private readonly _requestedExtensions?: Set; + private readonly _user?: User; private _activatedExtensions?: Set; - constructor(requestedExtensions?: Set) { + constructor(requestedExtensions?: Set, user?: User) { this._requestedExtensions = requestedExtensions; + this._user = user; + } + + get user(): User | undefined { + return this._user; } get activatedExtensions(): ReadonlySet | undefined { diff --git a/src/server/express/auth_handler.ts b/src/server/express/auth_handler.ts new file mode 100644 index 00000000..024f8ddc --- /dev/null +++ b/src/server/express/auth_handler.ts @@ -0,0 +1,44 @@ +import { NextFunction, Request, Response } from "express"; +import { IncomingHttpHeaders } from "http"; + +export const authHandler = ( + req: Request, + res: Response, + next: NextFunction +) => { + + let validAuthentications : string[]; + const headers : IncomingHttpHeaders = req.headers; + + const authHeader = headers['authorization']; + if (authHeader) { + const [scheme, token] = authHeader.split(' '); + + if (!scheme || !token) { + return res.status(401).json({ message: 'Invalid authorization format' }); + } + + // 3. Switch based on the scheme + switch (scheme) { + case 'Basic': + return validateBasicToken(token); + case 'Bearer': + return validateBearerToken(token) + } + } + +}; + +// Helper: Handle Basic Auth (Base64 encoded username:password) +const validateBasicToken = ( + token: string +): boolean => { + return true; +}; + +// Helper: Handle Bearer Token (JWT) +const validateBearerToken = ( + token: string +): boolean => { + return true; +}; \ No newline at end of file diff --git a/src/server/express/json_rpc_handler.ts b/src/server/express/json_rpc_handler.ts index 43536d79..d9d86859 100644 --- a/src/server/express/json_rpc_handler.ts +++ b/src/server/express/json_rpc_handler.ts @@ -12,6 +12,7 @@ import { JsonRpcTransportHandler } from '../transports/jsonrpc_transport_handler import { ServerCallContext } from '../context.js'; import { getRequestedExtensions } from '../utils.js'; import { HTTP_EXTENSION_HEADER } from '../../constants.js'; +import { checkAuthentication } from '../authentication/auth.js'; export interface JsonRpcHandlerOptions { requestHandler: A2ARequestHandler; @@ -34,6 +35,8 @@ export function jsonRpcHandler(options: JsonRpcHandlerOptions): RequestHandler { router.post('/', async (req: Request, res: Response) => { try { + const agentCard = await options.requestHandler.getAgentCard(); + checkAuthentication(req.headers, agentCard); const context = new ServerCallContext( getRequestedExtensions(req.header(HTTP_EXTENSION_HEADER)) ); From 343ba3e0f5e063a89362076b26f8ae85d5b2f28b Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Mon, 24 Nov 2025 09:56:40 +0000 Subject: [PATCH 02/13] implementstion of extended agent card WIP --- src/server/authentication/user.ts | 30 ------------- src/server/express/auth_handler.ts | 44 ------------------- src/server/express/json_rpc_handler.ts | 3 -- .../request_handler/a2a_request_handler.ts | 2 +- .../default_request_handler.ts | 7 ++- .../transports/jsonrpc_transport_handler.ts | 14 ++---- 6 files changed, 11 insertions(+), 89 deletions(-) delete mode 100644 src/server/authentication/user.ts delete mode 100644 src/server/express/auth_handler.ts diff --git a/src/server/authentication/user.ts b/src/server/authentication/user.ts deleted file mode 100644 index a9d79350..00000000 --- a/src/server/authentication/user.ts +++ /dev/null @@ -1,30 +0,0 @@ -export interface User { - getName(): string; - isAuthenticated(): boolean; -} - -export class AuthenticatedUser implements User { - private readonly name: string; - - constructor(name: string) { - this.name = name; - } - - public getName(): string { - return this.name; - } - - public isAuthenticated(): boolean { - return true; - } -} - -export class unAuthenticatedUser implements User { - public getName(): string { - return 'unAuthenticatedUser'; - } - - public isAuthenticated(): boolean { - return false; - } -} diff --git a/src/server/express/auth_handler.ts b/src/server/express/auth_handler.ts deleted file mode 100644 index 024f8ddc..00000000 --- a/src/server/express/auth_handler.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { NextFunction, Request, Response } from "express"; -import { IncomingHttpHeaders } from "http"; - -export const authHandler = ( - req: Request, - res: Response, - next: NextFunction -) => { - - let validAuthentications : string[]; - const headers : IncomingHttpHeaders = req.headers; - - const authHeader = headers['authorization']; - if (authHeader) { - const [scheme, token] = authHeader.split(' '); - - if (!scheme || !token) { - return res.status(401).json({ message: 'Invalid authorization format' }); - } - - // 3. Switch based on the scheme - switch (scheme) { - case 'Basic': - return validateBasicToken(token); - case 'Bearer': - return validateBearerToken(token) - } - } - -}; - -// Helper: Handle Basic Auth (Base64 encoded username:password) -const validateBasicToken = ( - token: string -): boolean => { - return true; -}; - -// Helper: Handle Bearer Token (JWT) -const validateBearerToken = ( - token: string -): boolean => { - return true; -}; \ No newline at end of file diff --git a/src/server/express/json_rpc_handler.ts b/src/server/express/json_rpc_handler.ts index 7faca5c9..a152ac56 100644 --- a/src/server/express/json_rpc_handler.ts +++ b/src/server/express/json_rpc_handler.ts @@ -12,7 +12,6 @@ import { JsonRpcTransportHandler } from '../transports/jsonrpc_transport_handler import { ServerCallContext } from '../context.js'; import { getRequestedExtensions } from '../utils.js'; import { HTTP_EXTENSION_HEADER } from '../../constants.js'; -import { checkAuthentication } from '../authentication/auth.js'; export interface JsonRpcHandlerOptions { requestHandler: A2ARequestHandler; @@ -35,8 +34,6 @@ export function jsonRpcHandler(options: JsonRpcHandlerOptions): RequestHandler { router.post('/', async (req: Request, res: Response) => { try { - const agentCard = await options.requestHandler.getAgentCard(); - checkAuthentication(req.headers, agentCard); const context = new ServerCallContext( getRequestedExtensions(req.header(HTTP_EXTENSION_HEADER)) ); diff --git a/src/server/request_handler/a2a_request_handler.ts b/src/server/request_handler/a2a_request_handler.ts index c31aefbc..ec4d386a 100644 --- a/src/server/request_handler/a2a_request_handler.ts +++ b/src/server/request_handler/a2a_request_handler.ts @@ -17,7 +17,7 @@ import { ServerCallContext } from '../context.js'; export interface A2ARequestHandler { getAgentCard(): Promise; - getAuthenticatedExtendedAgentCard(): Promise; + getAuthenticatedExtendedAgentCard(context?: ServerCallContext): Promise; sendMessage(params: MessageSendParams, context?: ServerCallContext): Promise; diff --git a/src/server/request_handler/default_request_handler.ts b/src/server/request_handler/default_request_handler.ts index ac8afebb..1b366c4a 100644 --- a/src/server/request_handler/default_request_handler.ts +++ b/src/server/request_handler/default_request_handler.ts @@ -74,11 +74,16 @@ export class DefaultRequestHandler implements A2ARequestHandler { return this.agentCard; } - async getAuthenticatedExtendedAgentCard(): Promise { + async getAuthenticatedExtendedAgentCard(context?: ServerCallContext): Promise { + if(!this.agentCard.supportsAuthenticatedExtendedCard) { + throw A2AError.unsupportedOperation('Agent does not support authenticated extended card.'); + } if (!this.extendedAgentCard) { throw A2AError.authenticatedExtendedCardNotConfigured(); } + + return this.extendedAgentCard; } diff --git a/src/server/transports/jsonrpc_transport_handler.ts b/src/server/transports/jsonrpc_transport_handler.ts index 423ba98e..deb4ffbb 100644 --- a/src/server/transports/jsonrpc_transport_handler.ts +++ b/src/server/transports/jsonrpc_transport_handler.ts @@ -60,16 +60,7 @@ export class JsonRpcTransportHandler { const { method, id: requestId = null } = rpcRequest; try { - if (method === 'agent/getAuthenticatedExtendedCard') { - const result = await this.requestHandler.getAuthenticatedExtendedAgentCard(); - return { - jsonrpc: '2.0', - id: requestId, - result: result, - } as JSONRPCResponse; - } - - if (!this.paramsAreValid(rpcRequest.params)) { + if (method !== 'agent/getAuthenticatedExtendedCard' && !this.paramsAreValid(rpcRequest.params)) { throw A2AError.invalidParams(`Invalid method parameters.`); } @@ -148,6 +139,9 @@ export class JsonRpcTransportHandler { context ); break; + case 'agent/getAuthenticatedExtendedCard': + result = await this.requestHandler.getAuthenticatedExtendedAgentCard(); + break; default: throw A2AError.methodNotFound(method); } From 4b949542767148750e1ce5e6cfd9bed09c64677b Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Mon, 24 Nov 2025 14:04:48 +0000 Subject: [PATCH 03/13] update with user callback --- src/server/request_handler/default_request_handler.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/server/request_handler/default_request_handler.ts b/src/server/request_handler/default_request_handler.ts index 1b366c4a..fb3ff65a 100644 --- a/src/server/request_handler/default_request_handler.ts +++ b/src/server/request_handler/default_request_handler.ts @@ -45,6 +45,7 @@ export class DefaultRequestHandler implements A2ARequestHandler { private readonly eventBusManager: ExecutionEventBusManager; private readonly pushNotificationStore?: PushNotificationStore; private readonly pushNotificationSender?: PushNotificationSender; + private readonly extendedCardModifier?: (agentCard: AgentCard, context?: ServerCallContext) => Promise; constructor( agentCard: AgentCard, @@ -53,13 +54,15 @@ export class DefaultRequestHandler implements A2ARequestHandler { eventBusManager: ExecutionEventBusManager = new DefaultExecutionEventBusManager(), pushNotificationStore?: PushNotificationStore, pushNotificationSender?: PushNotificationSender, - extendedAgentCard?: AgentCard + extendedAgentCard?: AgentCard, + extendedCardModifier?: (agentCard: AgentCard, context?: ServerCallContext) => Promise ) { this.agentCard = agentCard; this.taskStore = taskStore; this.agentExecutor = agentExecutor; this.eventBusManager = eventBusManager; this.extendedAgentCard = extendedAgentCard; + this.extendedCardModifier = extendedCardModifier; // If push notifications are supported, use the provided store and sender. // Otherwise, use the default in-memory store and sender. @@ -81,9 +84,9 @@ export class DefaultRequestHandler implements A2ARequestHandler { if (!this.extendedAgentCard) { throw A2AError.authenticatedExtendedCardNotConfigured(); } - - - + if (this.extendedCardModifier) { + return this.extendedCardModifier(this.extendedAgentCard, context); + } return this.extendedAgentCard; } From 3136ed9f90f0b7db46bbbeae855d48d82d1b37c2 Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Tue, 25 Nov 2025 12:54:14 +0000 Subject: [PATCH 04/13] add tests for getAuthenticatedExtendedAgentCard --- src/server/index.ts | 1 + src/server/request_handler/common.ts | 7 ++ .../default_request_handler.ts | 11 +- .../transports/jsonrpc_transport_handler.ts | 5 +- test/server/default_request_handler.spec.ts | 116 ++++++++++++++++++ 5 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 src/server/request_handler/common.ts diff --git a/src/server/index.ts b/src/server/index.ts index d845707e..c694c7f0 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -14,6 +14,7 @@ export { ExecutionEventQueue } from './events/execution_event_queue.js'; export type { A2ARequestHandler } from './request_handler/a2a_request_handler.js'; export { DefaultRequestHandler } from './request_handler/default_request_handler.js'; +export type { ExtendedCardModifier } from './request_handler/common.js'; export { ResultManager } from './result_manager.js'; export type { TaskStore } from './store.js'; export { InMemoryTaskStore } from './store.js'; diff --git a/src/server/request_handler/common.ts b/src/server/request_handler/common.ts new file mode 100644 index 00000000..a069e9b4 --- /dev/null +++ b/src/server/request_handler/common.ts @@ -0,0 +1,7 @@ +import { AgentCard } from '../../types.js'; +import { ServerCallContext } from '../context.js'; + +export type ExtendedCardModifier = ( + extendedAgentCard: AgentCard, + context?: ServerCallContext +) => Promise; diff --git a/src/server/request_handler/default_request_handler.ts b/src/server/request_handler/default_request_handler.ts index fb3ff65a..0f8c1949 100644 --- a/src/server/request_handler/default_request_handler.ts +++ b/src/server/request_handler/default_request_handler.ts @@ -34,6 +34,7 @@ import { import { PushNotificationSender } from '../push_notification/push_notification_sender.js'; import { DefaultPushNotificationSender } from '../push_notification/default_push_notification_sender.js'; import { ServerCallContext } from '../context.js'; +import { ExtendedCardModifier } from './common.js'; const terminalStates: TaskState[] = ['completed', 'failed', 'canceled', 'rejected']; @@ -45,7 +46,7 @@ export class DefaultRequestHandler implements A2ARequestHandler { private readonly eventBusManager: ExecutionEventBusManager; private readonly pushNotificationStore?: PushNotificationStore; private readonly pushNotificationSender?: PushNotificationSender; - private readonly extendedCardModifier?: (agentCard: AgentCard, context?: ServerCallContext) => Promise; + private readonly extendedCardModifier?: ExtendedCardModifier; constructor( agentCard: AgentCard, @@ -55,7 +56,7 @@ export class DefaultRequestHandler implements A2ARequestHandler { pushNotificationStore?: PushNotificationStore, pushNotificationSender?: PushNotificationSender, extendedAgentCard?: AgentCard, - extendedCardModifier?: (agentCard: AgentCard, context?: ServerCallContext) => Promise + extendedCardModifier?: ExtendedCardModifier ) { this.agentCard = agentCard; this.taskStore = taskStore; @@ -78,16 +79,16 @@ export class DefaultRequestHandler implements A2ARequestHandler { } async getAuthenticatedExtendedAgentCard(context?: ServerCallContext): Promise { - if(!this.agentCard.supportsAuthenticatedExtendedCard) { + if (!this.agentCard.supportsAuthenticatedExtendedCard) { throw A2AError.unsupportedOperation('Agent does not support authenticated extended card.'); } if (!this.extendedAgentCard) { throw A2AError.authenticatedExtendedCardNotConfigured(); } if (this.extendedCardModifier) { - return this.extendedCardModifier(this.extendedAgentCard, context); + return this.extendedCardModifier(this.extendedAgentCard, context) ?? this.agentCard; } - return this.extendedAgentCard; + return this.agentCard; } private async _createRequestContext( diff --git a/src/server/transports/jsonrpc_transport_handler.ts b/src/server/transports/jsonrpc_transport_handler.ts index deb4ffbb..71cf366b 100644 --- a/src/server/transports/jsonrpc_transport_handler.ts +++ b/src/server/transports/jsonrpc_transport_handler.ts @@ -60,7 +60,10 @@ export class JsonRpcTransportHandler { const { method, id: requestId = null } = rpcRequest; try { - if (method !== 'agent/getAuthenticatedExtendedCard' && !this.paramsAreValid(rpcRequest.params)) { + if ( + method !== 'agent/getAuthenticatedExtendedCard' && + !this.paramsAreValid(rpcRequest.params) + ) { throw A2AError.invalidParams(`Invalid method parameters.`); } diff --git a/test/server/default_request_handler.spec.ts b/test/server/default_request_handler.spec.ts index bd1f8499..fa942c3a 100644 --- a/test/server/default_request_handler.spec.ts +++ b/test/server/default_request_handler.spec.ts @@ -12,6 +12,8 @@ import { InMemoryPushNotificationStore, RequestContext, ExecutionEventBus, + ExtendedCardModifier, + User, } from '../../src/server/index.js'; import { AgentCard, @@ -1729,4 +1731,118 @@ describe('DefaultRequestHandler as A2ARequestHandler', () => { 'requestedExtensions should contain the expected extension' ); }); + + describe('getAuthenticatedExtendedAgentCard', async () => { + const agentCardWithExtendedSupport: AgentCard = { + name: 'Test Agent', + description: 'An agent for testing purposes', + url: 'http://localhost:8080', + version: '1.0.0', + protocolVersion: '0.3.0', + capabilities: { + extensions: [{ uri: 'requested-extension-uri' }], + streaming: true, + pushNotifications: true, + }, + defaultInputModes: ['text/plain'], + defaultOutputModes: ['text/plain'], + skills: [ + { + id: 'test-skill', + name: 'Test Skill', + description: 'A skill for testing', + tags: ['test'], + }, + ], + supportsAuthenticatedExtendedCard: true, + }; + + it('getAuthenticatedExtendedAgentCard should fail if the agent card does not support extended agent card', async () => { + let caughtError; + try { + await handler.getAuthenticatedExtendedAgentCard(); + } catch (error: any) { + caughtError = error; + } finally { + expect(caughtError).to.be.instanceOf(A2AError); + expect(caughtError.code).to.equal(-32004); + expect(caughtError.message).to.contain('Unsupported operation'); + } + }); + + it('getAuthenticatedExtendedAgentCard should fail if the extended card is not provided', async () => { + handler = new DefaultRequestHandler( + agentCardWithExtendedSupport, + mockTaskStore, + mockAgentExecutor, + executionEventBusManager + ); + let caughtError; + try { + await handler.getAuthenticatedExtendedAgentCard(); + } catch (error: any) { + caughtError = error; + } finally { + expect(caughtError).to.be.instanceOf(A2AError); + expect(caughtError.code).to.equal(-32007); + expect(caughtError.message).to.contain('Extended card not configured'); + } + }); + + it('getAuthenticatedExtendedAgentCard should return extended card if user is authenticated', async () => { + const extendedCardModifier: ExtendedCardModifier = async (extendedAgentCard, context?) => { + if (context?.user?.isAuthenticated) { + return extendedAgentCard; + } + return undefined; + }; + const extendedAgentCard: AgentCard = { + name: 'Test ExtendedAgentCard Agent', + description: 'An agent for testing the extended agent card functionality', + url: 'http://localhost:8080', + version: '1.0.0', + protocolVersion: '0.3.0', + capabilities: { + extensions: [ + { uri: 'requested-extension-uri' }, + { uri: 'extension-uri-for-authenticated-clients' }, + ], + streaming: true, + pushNotifications: true, + }, + defaultInputModes: ['text/plain'], + defaultOutputModes: ['text/plain'], + skills: [ + { + id: 'test-skill', + name: 'Test Skill', + description: 'A skill for testing', + tags: ['test'], + }, + ], + }; + handler = new DefaultRequestHandler( + agentCardWithExtendedSupport, + mockTaskStore, + mockAgentExecutor, + executionEventBusManager, + undefined, + undefined, + extendedAgentCard, + extendedCardModifier + ); + + class A2AUser implements User { + isAuthenticated(): boolean { + return true; + } + userName(): string { + return 'test-user'; + } + } + const context = new ServerCallContext(undefined, new A2AUser()); + const agentCard = await handler.getAuthenticatedExtendedAgentCard(context); + assert.deepEqual(agentCard, extendedAgentCard); + }); + }); }); From 81e7ce7033d8e505f64bee894670afe1d3912b76 Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Tue, 25 Nov 2025 13:10:37 +0000 Subject: [PATCH 05/13] check isauthenticated value if no callback is provided --- src/server/request_handler/default_request_handler.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/server/request_handler/default_request_handler.ts b/src/server/request_handler/default_request_handler.ts index 0f8c1949..0fd6daaf 100644 --- a/src/server/request_handler/default_request_handler.ts +++ b/src/server/request_handler/default_request_handler.ts @@ -86,7 +86,10 @@ export class DefaultRequestHandler implements A2ARequestHandler { throw A2AError.authenticatedExtendedCardNotConfigured(); } if (this.extendedCardModifier) { - return this.extendedCardModifier(this.extendedAgentCard, context) ?? this.agentCard; + return this.extendedCardModifier(this.extendedAgentCard, context); + } + if (context?.user?.isAuthenticated()) { + return this.extendedAgentCard; } return this.agentCard; } From 60046a87044dfa97bd3a2b48c4e1ad7454abfb1e Mon Sep 17 00:00:00 2001 From: Guglielmo Colombo Date: Tue, 25 Nov 2025 14:15:59 +0100 Subject: [PATCH 06/13] Update src/server/transports/jsonrpc_transport_handler.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/server/transports/jsonrpc_transport_handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/transports/jsonrpc_transport_handler.ts b/src/server/transports/jsonrpc_transport_handler.ts index 71cf366b..072e1c55 100644 --- a/src/server/transports/jsonrpc_transport_handler.ts +++ b/src/server/transports/jsonrpc_transport_handler.ts @@ -143,7 +143,7 @@ export class JsonRpcTransportHandler { ); break; case 'agent/getAuthenticatedExtendedCard': - result = await this.requestHandler.getAuthenticatedExtendedAgentCard(); + result = await this.requestHandler.getAuthenticatedExtendedAgentCard(context); break; default: throw A2AError.methodNotFound(method); From 74ed83594e0bae8e7189d836d586ec36fe28241d Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Tue, 25 Nov 2025 13:33:20 +0000 Subject: [PATCH 07/13] improve tests --- test/server/default_request_handler.spec.ts | 109 ++++++++++++-------- 1 file changed, 68 insertions(+), 41 deletions(-) diff --git a/test/server/default_request_handler.spec.ts b/test/server/default_request_handler.spec.ts index fa942c3a..5457821e 100644 --- a/test/server/default_request_handler.spec.ts +++ b/test/server/default_request_handler.spec.ts @@ -1732,7 +1732,29 @@ describe('DefaultRequestHandler as A2ARequestHandler', () => { ); }); - describe('getAuthenticatedExtendedAgentCard', async () => { + describe('getAuthenticatedExtendedAgentCard tests', async () => { + class A2AUser implements User { + constructor(private _isAuthenticated: boolean){} + + isAuthenticated(): boolean { + return this._isAuthenticated; + } + userName(): string { + return 'test-user'; + } + } + + const extendedCardModifier: ExtendedCardModifier = async (extendedAgentCard, context?) => { + if (context?.user?.isAuthenticated()) { + return extendedAgentCard; + } + // Remove the extensions that are not allowed for unauthenticated clients + extendedAgentCard.capabilities.extensions = [ + { uri: 'requested-extension-uri' }, + ]; + return extendedAgentCard; + }; + const agentCardWithExtendedSupport: AgentCard = { name: 'Test Agent', description: 'An agent for testing purposes', @@ -1757,6 +1779,32 @@ describe('DefaultRequestHandler as A2ARequestHandler', () => { supportsAuthenticatedExtendedCard: true, }; + const extendedAgentCard: AgentCard = { + name: 'Test ExtendedAgentCard Agent', + description: 'An agent for testing the extended agent card functionality', + url: 'http://localhost:8080', + version: '1.0.0', + protocolVersion: '0.3.0', + capabilities: { + extensions: [ + { uri: 'requested-extension-uri' }, + { uri: 'extension-uri-for-authenticated-clients' }, + ], + streaming: true, + pushNotifications: true, + }, + defaultInputModes: ['text/plain'], + defaultOutputModes: ['text/plain'], + skills: [ + { + id: 'test-skill', + name: 'Test Skill', + description: 'A skill for testing', + tags: ['test'], + }, + ], + }; + it('getAuthenticatedExtendedAgentCard should fail if the agent card does not support extended agent card', async () => { let caughtError; try { @@ -1790,37 +1838,6 @@ describe('DefaultRequestHandler as A2ARequestHandler', () => { }); it('getAuthenticatedExtendedAgentCard should return extended card if user is authenticated', async () => { - const extendedCardModifier: ExtendedCardModifier = async (extendedAgentCard, context?) => { - if (context?.user?.isAuthenticated) { - return extendedAgentCard; - } - return undefined; - }; - const extendedAgentCard: AgentCard = { - name: 'Test ExtendedAgentCard Agent', - description: 'An agent for testing the extended agent card functionality', - url: 'http://localhost:8080', - version: '1.0.0', - protocolVersion: '0.3.0', - capabilities: { - extensions: [ - { uri: 'requested-extension-uri' }, - { uri: 'extension-uri-for-authenticated-clients' }, - ], - streaming: true, - pushNotifications: true, - }, - defaultInputModes: ['text/plain'], - defaultOutputModes: ['text/plain'], - skills: [ - { - id: 'test-skill', - name: 'Test Skill', - description: 'A skill for testing', - tags: ['test'], - }, - ], - }; handler = new DefaultRequestHandler( agentCardWithExtendedSupport, mockTaskStore, @@ -1832,17 +1849,27 @@ describe('DefaultRequestHandler as A2ARequestHandler', () => { extendedCardModifier ); - class A2AUser implements User { - isAuthenticated(): boolean { - return true; - } - userName(): string { - return 'test-user'; - } - } - const context = new ServerCallContext(undefined, new A2AUser()); + const context = new ServerCallContext(undefined, new A2AUser(true)); const agentCard = await handler.getAuthenticatedExtendedAgentCard(context); assert.deepEqual(agentCard, extendedAgentCard); }); + + it('getAuthenticatedExtendedAgentCard should return capped extended card if user is not authenticated', async () => { + handler = new DefaultRequestHandler( + agentCardWithExtendedSupport, + mockTaskStore, + mockAgentExecutor, + executionEventBusManager, + undefined, + undefined, + extendedAgentCard, + extendedCardModifier + ); + + const context = new ServerCallContext(undefined, new A2AUser(false)); + const agentCard = await handler.getAuthenticatedExtendedAgentCard(context); + assert(agentCard.capabilities.extensions.length === 1); + assert.deepEqual(agentCard.capabilities.extensions[0], { uri: 'requested-extension-uri' }); + }); }); }); From 00f731346a7e1df810169d4c760bebcb6b140fe7 Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Tue, 25 Nov 2025 13:48:32 +0000 Subject: [PATCH 08/13] improve extended agent card support --- src/server/request_handler/common.ts | 1 + .../request_handler/default_request_handler.ts | 2 +- test/server/default_request_handler.spec.ts | 18 +++++++++++++++++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/server/request_handler/common.ts b/src/server/request_handler/common.ts index a069e9b4..460e32bc 100644 --- a/src/server/request_handler/common.ts +++ b/src/server/request_handler/common.ts @@ -3,5 +3,6 @@ import { ServerCallContext } from '../context.js'; export type ExtendedCardModifier = ( extendedAgentCard: AgentCard, + agentCard: AgentCard, context?: ServerCallContext ) => Promise; diff --git a/src/server/request_handler/default_request_handler.ts b/src/server/request_handler/default_request_handler.ts index 0fd6daaf..1cba7ecd 100644 --- a/src/server/request_handler/default_request_handler.ts +++ b/src/server/request_handler/default_request_handler.ts @@ -86,7 +86,7 @@ export class DefaultRequestHandler implements A2ARequestHandler { throw A2AError.authenticatedExtendedCardNotConfigured(); } if (this.extendedCardModifier) { - return this.extendedCardModifier(this.extendedAgentCard, context); + return this.extendedCardModifier(this.extendedAgentCard, this.agentCard, context); } if (context?.user?.isAuthenticated()) { return this.extendedAgentCard; diff --git a/test/server/default_request_handler.spec.ts b/test/server/default_request_handler.spec.ts index 5457821e..f43e071b 100644 --- a/test/server/default_request_handler.spec.ts +++ b/test/server/default_request_handler.spec.ts @@ -1744,7 +1744,7 @@ describe('DefaultRequestHandler as A2ARequestHandler', () => { } } - const extendedCardModifier: ExtendedCardModifier = async (extendedAgentCard, context?) => { + const extendedCardModifier: ExtendedCardModifier = async (extendedAgentCard, _agentCard, context?) => { if (context?.user?.isAuthenticated()) { return extendedAgentCard; } @@ -1870,6 +1870,22 @@ describe('DefaultRequestHandler as A2ARequestHandler', () => { const agentCard = await handler.getAuthenticatedExtendedAgentCard(context); assert(agentCard.capabilities.extensions.length === 1); assert.deepEqual(agentCard.capabilities.extensions[0], { uri: 'requested-extension-uri' }); + }); + + it('getAuthenticatedExtendedAgentCard should return capped extended card if user is authenticated and no card modifier is provided', async () => { + handler = new DefaultRequestHandler( + agentCardWithExtendedSupport, + mockTaskStore, + mockAgentExecutor, + executionEventBusManager, + undefined, + undefined, + extendedAgentCard, + ); + + const context = new ServerCallContext(undefined, new A2AUser(true)); + const agentCard = await handler.getAuthenticatedExtendedAgentCard(context); + assert.deepEqual(agentCard, extendedAgentCard); }); }); }); From 7d2b84983ec5cf967c7530da5e71827cd925477d Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Tue, 25 Nov 2025 13:49:04 +0000 Subject: [PATCH 09/13] run linter --- test/server/default_request_handler.spec.ts | 84 +++++++++++---------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/test/server/default_request_handler.spec.ts b/test/server/default_request_handler.spec.ts index f43e071b..d7d67929 100644 --- a/test/server/default_request_handler.spec.ts +++ b/test/server/default_request_handler.spec.ts @@ -1734,25 +1734,27 @@ describe('DefaultRequestHandler as A2ARequestHandler', () => { describe('getAuthenticatedExtendedAgentCard tests', async () => { class A2AUser implements User { - constructor(private _isAuthenticated: boolean){} - - isAuthenticated(): boolean { - return this._isAuthenticated; - } - userName(): string { - return 'test-user'; - } + constructor(private _isAuthenticated: boolean) {} + + isAuthenticated(): boolean { + return this._isAuthenticated; + } + userName(): string { + return 'test-user'; + } } - const extendedCardModifier: ExtendedCardModifier = async (extendedAgentCard, _agentCard, context?) => { - if (context?.user?.isAuthenticated()) { - return extendedAgentCard; - } - // Remove the extensions that are not allowed for unauthenticated clients - extendedAgentCard.capabilities.extensions = [ - { uri: 'requested-extension-uri' }, - ]; + const extendedCardModifier: ExtendedCardModifier = async ( + extendedAgentCard, + _agentCard, + context? + ) => { + if (context?.user?.isAuthenticated()) { return extendedAgentCard; + } + // Remove the extensions that are not allowed for unauthenticated clients + extendedAgentCard.capabilities.extensions = [{ uri: 'requested-extension-uri' }]; + return extendedAgentCard; }; const agentCardWithExtendedSupport: AgentCard = { @@ -1780,30 +1782,30 @@ describe('DefaultRequestHandler as A2ARequestHandler', () => { }; const extendedAgentCard: AgentCard = { - name: 'Test ExtendedAgentCard Agent', - description: 'An agent for testing the extended agent card functionality', - url: 'http://localhost:8080', - version: '1.0.0', - protocolVersion: '0.3.0', - capabilities: { - extensions: [ - { uri: 'requested-extension-uri' }, - { uri: 'extension-uri-for-authenticated-clients' }, - ], - streaming: true, - pushNotifications: true, - }, - defaultInputModes: ['text/plain'], - defaultOutputModes: ['text/plain'], - skills: [ - { - id: 'test-skill', - name: 'Test Skill', - description: 'A skill for testing', - tags: ['test'], - }, + name: 'Test ExtendedAgentCard Agent', + description: 'An agent for testing the extended agent card functionality', + url: 'http://localhost:8080', + version: '1.0.0', + protocolVersion: '0.3.0', + capabilities: { + extensions: [ + { uri: 'requested-extension-uri' }, + { uri: 'extension-uri-for-authenticated-clients' }, ], - }; + streaming: true, + pushNotifications: true, + }, + defaultInputModes: ['text/plain'], + defaultOutputModes: ['text/plain'], + skills: [ + { + id: 'test-skill', + name: 'Test Skill', + description: 'A skill for testing', + tags: ['test'], + }, + ], + }; it('getAuthenticatedExtendedAgentCard should fail if the agent card does not support extended agent card', async () => { let caughtError; @@ -1872,7 +1874,7 @@ describe('DefaultRequestHandler as A2ARequestHandler', () => { assert.deepEqual(agentCard.capabilities.extensions[0], { uri: 'requested-extension-uri' }); }); - it('getAuthenticatedExtendedAgentCard should return capped extended card if user is authenticated and no card modifier is provided', async () => { + it('getAuthenticatedExtendedAgentCard should return capped extended card if user is authenticated and no card modifier is provided', async () => { handler = new DefaultRequestHandler( agentCardWithExtendedSupport, mockTaskStore, @@ -1880,7 +1882,7 @@ describe('DefaultRequestHandler as A2ARequestHandler', () => { executionEventBusManager, undefined, undefined, - extendedAgentCard, + extendedAgentCard ); const context = new ServerCallContext(undefined, new A2AUser(true)); From 98393cfc856a7060a17cc16ad0b188a4ecc71f0a Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Tue, 25 Nov 2025 13:55:08 +0000 Subject: [PATCH 10/13] change test names --- test/server/default_request_handler.spec.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/server/default_request_handler.spec.ts b/test/server/default_request_handler.spec.ts index d7d67929..0f81eef6 100644 --- a/test/server/default_request_handler.spec.ts +++ b/test/server/default_request_handler.spec.ts @@ -1839,7 +1839,7 @@ describe('DefaultRequestHandler as A2ARequestHandler', () => { } }); - it('getAuthenticatedExtendedAgentCard should return extended card if user is authenticated', async () => { + it('getAuthenticatedExtendedAgentCard should return extended card if user is authenticated with provided card modifier', async () => { handler = new DefaultRequestHandler( agentCardWithExtendedSupport, mockTaskStore, @@ -1856,7 +1856,7 @@ describe('DefaultRequestHandler as A2ARequestHandler', () => { assert.deepEqual(agentCard, extendedAgentCard); }); - it('getAuthenticatedExtendedAgentCard should return capped extended card if user is not authenticated', async () => { + it('getAuthenticatedExtendedAgentCard should return capped extended card if user is not authenticated with provided card modifier', async () => { handler = new DefaultRequestHandler( agentCardWithExtendedSupport, mockTaskStore, @@ -1872,9 +1872,10 @@ describe('DefaultRequestHandler as A2ARequestHandler', () => { const agentCard = await handler.getAuthenticatedExtendedAgentCard(context); assert(agentCard.capabilities.extensions.length === 1); assert.deepEqual(agentCard.capabilities.extensions[0], { uri: 'requested-extension-uri' }); + assert.deepEqual(agentCard.name, extendedAgentCard.name); }); - it('getAuthenticatedExtendedAgentCard should return capped extended card if user is authenticated and no card modifier is provided', async () => { + it('getAuthenticatedExtendedAgentCard should return extended card if user is authenticated and no card modifier is provided', async () => { handler = new DefaultRequestHandler( agentCardWithExtendedSupport, mockTaskStore, From 247a34e5c5b55e2822c2ac9810a758559c431245 Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Tue, 25 Nov 2025 18:36:10 +0000 Subject: [PATCH 11/13] fix --- src/server/index.ts | 2 +- src/server/request_handler/common.ts | 8 ----- .../default_request_handler.ts | 22 ++++++------ test/server/default_request_handler.spec.ts | 34 ++++--------------- 4 files changed, 18 insertions(+), 48 deletions(-) delete mode 100644 src/server/request_handler/common.ts diff --git a/src/server/index.ts b/src/server/index.ts index c694c7f0..84b3393e 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -14,7 +14,7 @@ export { ExecutionEventQueue } from './events/execution_event_queue.js'; export type { A2ARequestHandler } from './request_handler/a2a_request_handler.js'; export { DefaultRequestHandler } from './request_handler/default_request_handler.js'; -export type { ExtendedCardModifier } from './request_handler/common.js'; +export type { ExtendedAgentCardProvider } from './request_handler/default_request_handler.js'; export { ResultManager } from './result_manager.js'; export type { TaskStore } from './store.js'; export { InMemoryTaskStore } from './store.js'; diff --git a/src/server/request_handler/common.ts b/src/server/request_handler/common.ts deleted file mode 100644 index 460e32bc..00000000 --- a/src/server/request_handler/common.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { AgentCard } from '../../types.js'; -import { ServerCallContext } from '../context.js'; - -export type ExtendedCardModifier = ( - extendedAgentCard: AgentCard, - agentCard: AgentCard, - context?: ServerCallContext -) => Promise; diff --git a/src/server/request_handler/default_request_handler.ts b/src/server/request_handler/default_request_handler.ts index 1cba7ecd..379a5474 100644 --- a/src/server/request_handler/default_request_handler.ts +++ b/src/server/request_handler/default_request_handler.ts @@ -34,19 +34,17 @@ import { import { PushNotificationSender } from '../push_notification/push_notification_sender.js'; import { DefaultPushNotificationSender } from '../push_notification/default_push_notification_sender.js'; import { ServerCallContext } from '../context.js'; -import { ExtendedCardModifier } from './common.js'; const terminalStates: TaskState[] = ['completed', 'failed', 'canceled', 'rejected']; export class DefaultRequestHandler implements A2ARequestHandler { private readonly agentCard: AgentCard; - private readonly extendedAgentCard?: AgentCard; private readonly taskStore: TaskStore; private readonly agentExecutor: AgentExecutor; private readonly eventBusManager: ExecutionEventBusManager; private readonly pushNotificationStore?: PushNotificationStore; private readonly pushNotificationSender?: PushNotificationSender; - private readonly extendedCardModifier?: ExtendedCardModifier; + private readonly extendedAgentCardProvider?: AgentCard | ExtendedAgentCardProvider; constructor( agentCard: AgentCard, @@ -55,15 +53,13 @@ export class DefaultRequestHandler implements A2ARequestHandler { eventBusManager: ExecutionEventBusManager = new DefaultExecutionEventBusManager(), pushNotificationStore?: PushNotificationStore, pushNotificationSender?: PushNotificationSender, - extendedAgentCard?: AgentCard, - extendedCardModifier?: ExtendedCardModifier + extendedAgentCardProvider?: AgentCard | ExtendedAgentCardProvider ) { this.agentCard = agentCard; this.taskStore = taskStore; this.agentExecutor = agentExecutor; this.eventBusManager = eventBusManager; - this.extendedAgentCard = extendedAgentCard; - this.extendedCardModifier = extendedCardModifier; + this.extendedAgentCardProvider = extendedAgentCardProvider; // If push notifications are supported, use the provided store and sender. // Otherwise, use the default in-memory store and sender. @@ -82,14 +78,14 @@ export class DefaultRequestHandler implements A2ARequestHandler { if (!this.agentCard.supportsAuthenticatedExtendedCard) { throw A2AError.unsupportedOperation('Agent does not support authenticated extended card.'); } - if (!this.extendedAgentCard) { + if (!this.extendedAgentCardProvider) { throw A2AError.authenticatedExtendedCardNotConfigured(); } - if (this.extendedCardModifier) { - return this.extendedCardModifier(this.extendedAgentCard, this.agentCard, context); + if (typeof this.extendedAgentCardProvider === 'function') { + return this.extendedAgentCardProvider(context); } - if (context?.user?.isAuthenticated()) { - return this.extendedAgentCard; + if (this.extendedAgentCardProvider && context?.user?.isAuthenticated()) { + return this.extendedAgentCardProvider; } return this.agentCard; } @@ -693,3 +689,5 @@ export class DefaultRequestHandler implements A2ARequestHandler { } } } + +export type ExtendedAgentCardProvider = (context?: ServerCallContext) => Promise; diff --git a/test/server/default_request_handler.spec.ts b/test/server/default_request_handler.spec.ts index 0f81eef6..d8a52af9 100644 --- a/test/server/default_request_handler.spec.ts +++ b/test/server/default_request_handler.spec.ts @@ -12,7 +12,7 @@ import { InMemoryPushNotificationStore, RequestContext, ExecutionEventBus, - ExtendedCardModifier, + ExtendedAgentCardProvider, User, } from '../../src/server/index.js'; import { @@ -1744,9 +1744,7 @@ describe('DefaultRequestHandler as A2ARequestHandler', () => { } } - const extendedCardModifier: ExtendedCardModifier = async ( - extendedAgentCard, - _agentCard, + const extendedAgentcardProvider: ExtendedAgentCardProvider = async ( context? ) => { if (context?.user?.isAuthenticated()) { @@ -1820,7 +1818,7 @@ describe('DefaultRequestHandler as A2ARequestHandler', () => { } }); - it('getAuthenticatedExtendedAgentCard should fail if the extended card is not provided', async () => { + it('getAuthenticatedExtendedAgentCard should fail if ExtendedAgentCardProvider is not provided', async () => { handler = new DefaultRequestHandler( agentCardWithExtendedSupport, mockTaskStore, @@ -1839,7 +1837,7 @@ describe('DefaultRequestHandler as A2ARequestHandler', () => { } }); - it('getAuthenticatedExtendedAgentCard should return extended card if user is authenticated with provided card modifier', async () => { + it('getAuthenticatedExtendedAgentCard should return extended card if user is authenticated with ExtendedAgentCardProvider as AgentCard', async () => { handler = new DefaultRequestHandler( agentCardWithExtendedSupport, mockTaskStore, @@ -1847,8 +1845,7 @@ describe('DefaultRequestHandler as A2ARequestHandler', () => { executionEventBusManager, undefined, undefined, - extendedAgentCard, - extendedCardModifier + extendedAgentCard ); const context = new ServerCallContext(undefined, new A2AUser(true)); @@ -1856,7 +1853,7 @@ describe('DefaultRequestHandler as A2ARequestHandler', () => { assert.deepEqual(agentCard, extendedAgentCard); }); - it('getAuthenticatedExtendedAgentCard should return capped extended card if user is not authenticated with provided card modifier', async () => { + it('getAuthenticatedExtendedAgentCard should return capped extended card if user is not authenticated with ExtendedAgentCardProvider as callback', async () => { handler = new DefaultRequestHandler( agentCardWithExtendedSupport, mockTaskStore, @@ -1864,8 +1861,7 @@ describe('DefaultRequestHandler as A2ARequestHandler', () => { executionEventBusManager, undefined, undefined, - extendedAgentCard, - extendedCardModifier + extendedAgentcardProvider ); const context = new ServerCallContext(undefined, new A2AUser(false)); @@ -1874,21 +1870,5 @@ describe('DefaultRequestHandler as A2ARequestHandler', () => { assert.deepEqual(agentCard.capabilities.extensions[0], { uri: 'requested-extension-uri' }); assert.deepEqual(agentCard.name, extendedAgentCard.name); }); - - it('getAuthenticatedExtendedAgentCard should return extended card if user is authenticated and no card modifier is provided', async () => { - handler = new DefaultRequestHandler( - agentCardWithExtendedSupport, - mockTaskStore, - mockAgentExecutor, - executionEventBusManager, - undefined, - undefined, - extendedAgentCard - ); - - const context = new ServerCallContext(undefined, new A2AUser(true)); - const agentCard = await handler.getAuthenticatedExtendedAgentCard(context); - assert.deepEqual(agentCard, extendedAgentCard); - }); }); }); From b71ad6aa9692ba0ac7b1751e2661a207c6823d85 Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Tue, 25 Nov 2025 18:36:52 +0000 Subject: [PATCH 12/13] run linter --- test/server/default_request_handler.spec.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/server/default_request_handler.spec.ts b/test/server/default_request_handler.spec.ts index d8a52af9..111d9925 100644 --- a/test/server/default_request_handler.spec.ts +++ b/test/server/default_request_handler.spec.ts @@ -1744,9 +1744,7 @@ describe('DefaultRequestHandler as A2ARequestHandler', () => { } } - const extendedAgentcardProvider: ExtendedAgentCardProvider = async ( - context? - ) => { + const extendedAgentcardProvider: ExtendedAgentCardProvider = async (context?) => { if (context?.user?.isAuthenticated()) { return extendedAgentCard; } From 267f6cc9a94fd3e12c3d86af4de3e3b8e76144d0 Mon Sep 17 00:00:00 2001 From: guglielmoc Date: Tue, 25 Nov 2025 18:39:40 +0000 Subject: [PATCH 13/13] remove unnecessary check --- src/server/request_handler/default_request_handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/request_handler/default_request_handler.ts b/src/server/request_handler/default_request_handler.ts index 379a5474..3b52be37 100644 --- a/src/server/request_handler/default_request_handler.ts +++ b/src/server/request_handler/default_request_handler.ts @@ -84,7 +84,7 @@ export class DefaultRequestHandler implements A2ARequestHandler { if (typeof this.extendedAgentCardProvider === 'function') { return this.extendedAgentCardProvider(context); } - if (this.extendedAgentCardProvider && context?.user?.isAuthenticated()) { + if (context?.user?.isAuthenticated()) { return this.extendedAgentCardProvider; } return this.agentCard;