a2a-ruby 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +137 -0
  4. data/.simplecov +46 -0
  5. data/.yardopts +10 -0
  6. data/CHANGELOG.md +33 -0
  7. data/CODE_OF_CONDUCT.md +128 -0
  8. data/CONTRIBUTING.md +165 -0
  9. data/Gemfile +43 -0
  10. data/Guardfile +34 -0
  11. data/LICENSE.txt +21 -0
  12. data/PUBLISHING_CHECKLIST.md +214 -0
  13. data/README.md +171 -0
  14. data/Rakefile +165 -0
  15. data/docs/agent_execution.md +309 -0
  16. data/docs/api_reference.md +792 -0
  17. data/docs/configuration.md +780 -0
  18. data/docs/events.md +475 -0
  19. data/docs/getting_started.md +668 -0
  20. data/docs/integration.md +262 -0
  21. data/docs/server_apps.md +621 -0
  22. data/docs/troubleshooting.md +765 -0
  23. data/lib/a2a/client/api_methods.rb +263 -0
  24. data/lib/a2a/client/auth/api_key.rb +161 -0
  25. data/lib/a2a/client/auth/interceptor.rb +288 -0
  26. data/lib/a2a/client/auth/jwt.rb +189 -0
  27. data/lib/a2a/client/auth/oauth2.rb +146 -0
  28. data/lib/a2a/client/auth.rb +137 -0
  29. data/lib/a2a/client/base.rb +316 -0
  30. data/lib/a2a/client/config.rb +210 -0
  31. data/lib/a2a/client/connection_pool.rb +233 -0
  32. data/lib/a2a/client/http_client.rb +524 -0
  33. data/lib/a2a/client/json_rpc_handler.rb +136 -0
  34. data/lib/a2a/client/middleware/circuit_breaker_interceptor.rb +245 -0
  35. data/lib/a2a/client/middleware/logging_interceptor.rb +371 -0
  36. data/lib/a2a/client/middleware/rate_limit_interceptor.rb +142 -0
  37. data/lib/a2a/client/middleware/retry_interceptor.rb +161 -0
  38. data/lib/a2a/client/middleware.rb +116 -0
  39. data/lib/a2a/client/performance_tracker.rb +60 -0
  40. data/lib/a2a/configuration/defaults.rb +34 -0
  41. data/lib/a2a/configuration/environment_loader.rb +76 -0
  42. data/lib/a2a/configuration/file_loader.rb +115 -0
  43. data/lib/a2a/configuration/inheritance.rb +101 -0
  44. data/lib/a2a/configuration/validator.rb +180 -0
  45. data/lib/a2a/configuration.rb +201 -0
  46. data/lib/a2a/errors.rb +291 -0
  47. data/lib/a2a/modules.rb +50 -0
  48. data/lib/a2a/monitoring/alerting.rb +490 -0
  49. data/lib/a2a/monitoring/distributed_tracing.rb +398 -0
  50. data/lib/a2a/monitoring/health_endpoints.rb +204 -0
  51. data/lib/a2a/monitoring/metrics_collector.rb +438 -0
  52. data/lib/a2a/monitoring.rb +463 -0
  53. data/lib/a2a/plugin.rb +358 -0
  54. data/lib/a2a/plugin_manager.rb +159 -0
  55. data/lib/a2a/plugins/example_auth.rb +81 -0
  56. data/lib/a2a/plugins/example_middleware.rb +118 -0
  57. data/lib/a2a/plugins/example_transport.rb +76 -0
  58. data/lib/a2a/protocol/agent_card.rb +8 -0
  59. data/lib/a2a/protocol/agent_card_server.rb +584 -0
  60. data/lib/a2a/protocol/capability.rb +496 -0
  61. data/lib/a2a/protocol/json_rpc.rb +254 -0
  62. data/lib/a2a/protocol/message.rb +8 -0
  63. data/lib/a2a/protocol/task.rb +8 -0
  64. data/lib/a2a/rails/a2a_controller.rb +258 -0
  65. data/lib/a2a/rails/controller_helpers.rb +499 -0
  66. data/lib/a2a/rails/engine.rb +167 -0
  67. data/lib/a2a/rails/generators/agent_generator.rb +311 -0
  68. data/lib/a2a/rails/generators/install_generator.rb +209 -0
  69. data/lib/a2a/rails/generators/migration_generator.rb +232 -0
  70. data/lib/a2a/rails/generators/templates/add_a2a_indexes.rb +57 -0
  71. data/lib/a2a/rails/generators/templates/agent_controller.rb +122 -0
  72. data/lib/a2a/rails/generators/templates/agent_controller_spec.rb +160 -0
  73. data/lib/a2a/rails/generators/templates/agent_readme.md +200 -0
  74. data/lib/a2a/rails/generators/templates/create_a2a_push_notification_configs.rb +68 -0
  75. data/lib/a2a/rails/generators/templates/create_a2a_tasks.rb +83 -0
  76. data/lib/a2a/rails/generators/templates/example_agent_controller.rb +228 -0
  77. data/lib/a2a/rails/generators/templates/initializer.rb +108 -0
  78. data/lib/a2a/rails/generators/templates/push_notification_config_model.rb +228 -0
  79. data/lib/a2a/rails/generators/templates/task_model.rb +200 -0
  80. data/lib/a2a/rails/tasks/a2a.rake +228 -0
  81. data/lib/a2a/server/a2a_methods.rb +520 -0
  82. data/lib/a2a/server/agent.rb +537 -0
  83. data/lib/a2a/server/agent_execution/agent_executor.rb +279 -0
  84. data/lib/a2a/server/agent_execution/request_context.rb +219 -0
  85. data/lib/a2a/server/apps/rack_app.rb +311 -0
  86. data/lib/a2a/server/apps/sinatra_app.rb +261 -0
  87. data/lib/a2a/server/default_request_handler.rb +350 -0
  88. data/lib/a2a/server/events/event_consumer.rb +116 -0
  89. data/lib/a2a/server/events/event_queue.rb +226 -0
  90. data/lib/a2a/server/example_agent.rb +248 -0
  91. data/lib/a2a/server/handler.rb +281 -0
  92. data/lib/a2a/server/middleware/authentication_middleware.rb +212 -0
  93. data/lib/a2a/server/middleware/cors_middleware.rb +171 -0
  94. data/lib/a2a/server/middleware/logging_middleware.rb +362 -0
  95. data/lib/a2a/server/middleware/rate_limit_middleware.rb +382 -0
  96. data/lib/a2a/server/middleware.rb +213 -0
  97. data/lib/a2a/server/push_notification_manager.rb +327 -0
  98. data/lib/a2a/server/request_handler.rb +136 -0
  99. data/lib/a2a/server/storage/base.rb +141 -0
  100. data/lib/a2a/server/storage/database.rb +266 -0
  101. data/lib/a2a/server/storage/memory.rb +274 -0
  102. data/lib/a2a/server/storage/redis.rb +320 -0
  103. data/lib/a2a/server/storage.rb +38 -0
  104. data/lib/a2a/server/task_manager.rb +534 -0
  105. data/lib/a2a/transport/grpc.rb +481 -0
  106. data/lib/a2a/transport/http.rb +415 -0
  107. data/lib/a2a/transport/sse.rb +499 -0
  108. data/lib/a2a/types/agent_card.rb +540 -0
  109. data/lib/a2a/types/artifact.rb +99 -0
  110. data/lib/a2a/types/base_model.rb +223 -0
  111. data/lib/a2a/types/events.rb +117 -0
  112. data/lib/a2a/types/message.rb +106 -0
  113. data/lib/a2a/types/part.rb +288 -0
  114. data/lib/a2a/types/push_notification.rb +139 -0
  115. data/lib/a2a/types/security.rb +167 -0
  116. data/lib/a2a/types/task.rb +154 -0
  117. data/lib/a2a/types.rb +88 -0
  118. data/lib/a2a/utils/helpers.rb +245 -0
  119. data/lib/a2a/utils/message_buffer.rb +278 -0
  120. data/lib/a2a/utils/performance.rb +247 -0
  121. data/lib/a2a/utils/rails_detection.rb +97 -0
  122. data/lib/a2a/utils/structured_logger.rb +306 -0
  123. data/lib/a2a/utils/time_helpers.rb +167 -0
  124. data/lib/a2a/utils/validation.rb +8 -0
  125. data/lib/a2a/version.rb +6 -0
  126. data/lib/a2a-rails.rb +58 -0
  127. data/lib/a2a.rb +198 -0
  128. metadata +437 -0
@@ -0,0 +1,621 @@
1
+ # Server Applications
2
+
3
+ The A2A Ruby SDK provides ready-to-use server applications that can be deployed with popular Ruby web frameworks. These applications handle all the A2A protocol details, allowing you to focus on your agent logic.
4
+
5
+ ## Overview
6
+
7
+ The SDK includes two main server application types:
8
+
9
+ - **RackApp**: A Rack-compatible application that works with any Rack server
10
+ - **SinatraApp**: A Sinatra-based application with Ruby-idiomatic routing
11
+
12
+ Both applications provide:
13
+ - Agent card serving at `/.well-known/a2a/agent-card`
14
+ - Extended agent card serving at `/a2a/agent-card/extended`
15
+ - JSON-RPC endpoint at `/a2a/rpc`
16
+ - Server-Sent Events for streaming responses
17
+ - Proper error handling and response formatting
18
+
19
+ ## Rack Application
20
+
21
+ ### Basic Setup
22
+
23
+ ```ruby
24
+ require 'a2a'
25
+
26
+ # Create agent card
27
+ agent_card = A2A::Types::AgentCard.new(
28
+ name: "My Agent",
29
+ description: "A helpful agent",
30
+ version: "1.0.0",
31
+ capabilities: A2A::Types::Capabilities.new(
32
+ streaming: true,
33
+ push_notifications: true,
34
+ task_management: true
35
+ )
36
+ )
37
+
38
+ # Create agent executor
39
+ class MyAgentExecutor < A2A::Server::AgentExecution::SimpleAgentExecutor
40
+ def process_message(message, task, context)
41
+ text = message.parts.first.text
42
+ { response: "You said: #{text}" }
43
+ end
44
+ end
45
+
46
+ # Create request handler
47
+ executor = MyAgentExecutor.new
48
+ handler = A2A::Server::DefaultRequestHandler.new(executor)
49
+
50
+ # Create Rack application
51
+ app = A2A::Server::Apps::RackApp.new(
52
+ agent_card: agent_card,
53
+ request_handler: handler
54
+ )
55
+
56
+ # Run with any Rack server
57
+ # rackup -p 9292
58
+ ```
59
+
60
+ ### Advanced Configuration
61
+
62
+ ```ruby
63
+ app = A2A::Server::Apps::RackApp.new(
64
+ agent_card: agent_card,
65
+ request_handler: handler,
66
+ extended_agent_card: extended_card, # Optional extended card
67
+ card_modifier: ->(card) {
68
+ # Dynamically modify the public card
69
+ card.metadata ||= {}
70
+ card.metadata[:server_time] = Time.now.utc.iso8601
71
+ card.metadata[:ruby_version] = RUBY_VERSION
72
+ card
73
+ },
74
+ extended_card_modifier: ->(card, context) {
75
+ # Modify extended card based on context
76
+ if context.authenticated?
77
+ card.metadata ||= {}
78
+ card.metadata[:user] = context.user.to_s
79
+ end
80
+ card
81
+ }
82
+ )
83
+ ```
84
+
85
+ ### Deployment with config.ru
86
+
87
+ ```ruby
88
+ # config.ru
89
+ require_relative 'my_agent'
90
+
91
+ # Add middleware
92
+ use Rack::Logger
93
+ use Rack::CommonLogger
94
+
95
+ # CORS for development
96
+ if ENV['RACK_ENV'] == 'development'
97
+ use Rack::Cors do
98
+ allow do
99
+ origins '*'
100
+ resource '*',
101
+ headers: :any,
102
+ methods: [:get, :post, :options]
103
+ end
104
+ end
105
+ end
106
+
107
+ # Authentication middleware (optional)
108
+ use MyAuthMiddleware
109
+
110
+ # Run the A2A app
111
+ run create_a2a_app
112
+ ```
113
+
114
+ ### Custom Middleware Integration
115
+
116
+ ```ruby
117
+ class AuthenticationMiddleware
118
+ def initialize(app)
119
+ @app = app
120
+ end
121
+
122
+ def call(env)
123
+ # Extract authentication from headers
124
+ auth_header = env['HTTP_AUTHORIZATION']
125
+
126
+ if auth_header&.start_with?('Bearer ')
127
+ token = auth_header[7..-1]
128
+ user = authenticate_token(token)
129
+ env['current_user'] = user if user
130
+ end
131
+
132
+ @app.call(env)
133
+ end
134
+
135
+ private
136
+
137
+ def authenticate_token(token)
138
+ # Your authentication logic
139
+ User.find_by_token(token)
140
+ end
141
+ end
142
+
143
+ # Use with Rack app
144
+ use AuthenticationMiddleware
145
+ run app
146
+ ```
147
+
148
+ ## Sinatra Application
149
+
150
+ ### Basic Setup
151
+
152
+ ```ruby
153
+ require 'sinatra'
154
+ require 'a2a'
155
+
156
+ class MyA2AApp < A2A::Server::Apps::SinatraApp
157
+ # Configure A2A components
158
+ configure_a2a(
159
+ agent_card: agent_card,
160
+ request_handler: handler
161
+ )
162
+
163
+ # Add custom routes if needed
164
+ get '/health' do
165
+ { status: 'ok', timestamp: Time.now.utc.iso8601 }.to_json
166
+ end
167
+
168
+ # Custom error handling
169
+ error A2A::Errors::AuthenticationRequired do
170
+ status 401
171
+ { error: 'Authentication required' }.to_json
172
+ end
173
+ end
174
+
175
+ # Run the app
176
+ MyA2AApp.run! port: 9292
177
+ ```
178
+
179
+ ### Advanced Sinatra Integration
180
+
181
+ ```ruby
182
+ class AdvancedA2AApp < A2A::Server::Apps::SinatraApp
183
+ # Enable sessions for authentication
184
+ enable :sessions
185
+
186
+ # Configure A2A with dynamic card modification
187
+ configure_a2a(
188
+ agent_card: agent_card,
189
+ request_handler: handler,
190
+ card_modifier: method(:modify_public_card),
191
+ extended_card_modifier: method(:modify_extended_card)
192
+ )
193
+
194
+ # Authentication helpers
195
+ helpers do
196
+ def current_user
197
+ @current_user ||= User.find(session[:user_id]) if session[:user_id]
198
+ end
199
+
200
+ def authenticated?
201
+ !current_user.nil?
202
+ end
203
+
204
+ def require_auth!
205
+ halt 401, { error: 'Authentication required' }.to_json unless authenticated?
206
+ end
207
+ end
208
+
209
+ # Authentication routes
210
+ post '/auth/login' do
211
+ user = User.authenticate(params[:username], params[:password])
212
+ if user
213
+ session[:user_id] = user.id
214
+ { success: true, user: user.to_h }.to_json
215
+ else
216
+ status 401
217
+ { error: 'Invalid credentials' }.to_json
218
+ end
219
+ end
220
+
221
+ post '/auth/logout' do
222
+ session.clear
223
+ { success: true }.to_json
224
+ end
225
+
226
+ # Protected routes
227
+ get '/admin/stats' do
228
+ require_auth!
229
+ content_type :json
230
+
231
+ {
232
+ tasks_processed: TaskStats.total_processed,
233
+ active_sessions: SessionManager.active_count,
234
+ uptime: Time.now - start_time
235
+ }.to_json
236
+ end
237
+
238
+ private
239
+
240
+ def modify_public_card(card)
241
+ card.metadata ||= {}
242
+ card.metadata[:server_info] = {
243
+ ruby_version: RUBY_VERSION,
244
+ sinatra_version: Sinatra::VERSION,
245
+ uptime: Time.now - start_time
246
+ }
247
+ card
248
+ end
249
+
250
+ def modify_extended_card(card, context)
251
+ if context.authenticated?
252
+ card.metadata ||= {}
253
+ card.metadata[:user_info] = {
254
+ id: context.user.id,
255
+ name: context.user.name,
256
+ permissions: context.user.permissions
257
+ }
258
+ end
259
+ card
260
+ end
261
+ end
262
+ ```
263
+
264
+ ## Rails Integration
265
+
266
+ ### Engine Mount
267
+
268
+ ```ruby
269
+ # config/routes.rb
270
+ Rails.application.routes.draw do
271
+ mount A2A::Engine => "/a2a"
272
+
273
+ # Or mount custom A2A app
274
+ mount MyA2AApp => "/custom-a2a"
275
+ end
276
+ ```
277
+
278
+ ### Custom Rails Controller
279
+
280
+ ```ruby
281
+ class A2AController < ApplicationController
282
+ before_action :authenticate_user!, only: [:extended_card]
283
+
284
+ def rpc
285
+ # Create request handler for this request
286
+ handler = create_request_handler
287
+
288
+ # Parse JSON-RPC request
289
+ rpc_request = A2A::Protocol::JsonRpc.parse_request(request.body.read)
290
+
291
+ # Route to handler
292
+ result = route_to_handler(rpc_request, handler)
293
+
294
+ # Return response
295
+ if result.is_a?(Enumerator)
296
+ # Handle streaming
297
+ render_streaming_response(result)
298
+ else
299
+ render json: A2A::Protocol::JsonRpc.build_response(
300
+ result: result,
301
+ id: rpc_request.id
302
+ )
303
+ end
304
+ rescue A2A::Errors::A2AError => e
305
+ render json: A2A::Protocol::JsonRpc.build_error_response(
306
+ code: e.code,
307
+ message: e.message,
308
+ id: rpc_request&.id
309
+ )
310
+ end
311
+
312
+ def agent_card
313
+ card = build_agent_card
314
+
315
+ # Apply modifications
316
+ card = modify_card_for_user(card, current_user) if respond_to?(:current_user)
317
+
318
+ render json: card.to_h
319
+ end
320
+
321
+ def extended_agent_card
322
+ card = build_extended_agent_card
323
+ card = modify_extended_card_for_user(card, current_user)
324
+
325
+ render json: card.to_h
326
+ end
327
+
328
+ private
329
+
330
+ def create_request_handler
331
+ executor = MyAgentExecutor.new
332
+ A2A::Server::DefaultRequestHandler.new(executor)
333
+ end
334
+
335
+ def render_streaming_response(enumerator)
336
+ response.headers['Content-Type'] = 'text/event-stream'
337
+ response.headers['Cache-Control'] = 'no-cache'
338
+ response.headers['Connection'] = 'keep-alive'
339
+
340
+ self.response_body = Enumerator.new do |yielder|
341
+ enumerator.each do |event|
342
+ data = event.respond_to?(:to_h) ? event.to_h : event
343
+ yielder << "data: #{data.to_json}\n\n"
344
+ end
345
+ rescue => e
346
+ yielder << "data: #{JSON.generate(error: e.message)}\n\n"
347
+ ensure
348
+ yielder << "data: [DONE]\n\n"
349
+ end
350
+ end
351
+ end
352
+ ```
353
+
354
+ ## Deployment Options
355
+
356
+ ### Puma (Recommended)
357
+
358
+ ```ruby
359
+ # config/puma.rb
360
+ workers ENV.fetch("WEB_CONCURRENCY") { 2 }
361
+ threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
362
+ threads threads_count, threads_count
363
+
364
+ preload_app!
365
+
366
+ rackup DefaultRackup
367
+ port ENV.fetch("PORT") { 3000 }
368
+ environment ENV.fetch("RACK_ENV") { "development" }
369
+
370
+ on_worker_boot do
371
+ # Worker-specific initialization
372
+ A2A.initialize_monitoring!
373
+ end
374
+ ```
375
+
376
+ ### Unicorn
377
+
378
+ ```ruby
379
+ # config/unicorn.rb
380
+ worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3)
381
+ timeout 30
382
+ preload_app true
383
+
384
+ before_fork do |server, worker|
385
+ # Close database connections
386
+ ActiveRecord::Base.connection.disconnect! if defined?(ActiveRecord::Base)
387
+ end
388
+
389
+ after_fork do |server, worker|
390
+ # Reconnect database
391
+ ActiveRecord::Base.establish_connection if defined?(ActiveRecord::Base)
392
+
393
+ # Initialize A2A monitoring
394
+ A2A.initialize_monitoring!
395
+ end
396
+ ```
397
+
398
+ ### Docker Deployment
399
+
400
+ ```dockerfile
401
+ FROM ruby:3.2-alpine
402
+
403
+ WORKDIR /app
404
+
405
+ # Install dependencies
406
+ COPY Gemfile Gemfile.lock ./
407
+ RUN bundle install --deployment --without development test
408
+
409
+ # Copy application
410
+ COPY . .
411
+
412
+ # Expose port
413
+ EXPOSE 9292
414
+
415
+ # Run with Puma
416
+ CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]
417
+ ```
418
+
419
+ ```yaml
420
+ # docker-compose.yml
421
+ version: '3.8'
422
+ services:
423
+ a2a-agent:
424
+ build: .
425
+ ports:
426
+ - "9292:9292"
427
+ environment:
428
+ - RACK_ENV=production
429
+ - A2A_LOG_LEVEL=info
430
+ volumes:
431
+ - ./logs:/app/logs
432
+ depends_on:
433
+ - redis
434
+ - postgres
435
+
436
+ redis:
437
+ image: redis:alpine
438
+
439
+ postgres:
440
+ image: postgres:13
441
+ environment:
442
+ POSTGRES_DB: a2a_production
443
+ POSTGRES_USER: a2a
444
+ POSTGRES_PASSWORD: secret
445
+ ```
446
+
447
+ ## Monitoring and Health Checks
448
+
449
+ ### Health Check Endpoint
450
+
451
+ ```ruby
452
+ class HealthCheckApp
453
+ def call(env)
454
+ if env['PATH_INFO'] == '/health'
455
+ status = check_health
456
+
457
+ if status[:healthy]
458
+ [200, {'Content-Type' => 'application/json'}, [status.to_json]]
459
+ else
460
+ [503, {'Content-Type' => 'application/json'}, [status.to_json]]
461
+ end
462
+ else
463
+ [404, {}, ['Not Found']]
464
+ end
465
+ end
466
+
467
+ private
468
+
469
+ def check_health
470
+ {
471
+ healthy: true,
472
+ timestamp: Time.now.utc.iso8601,
473
+ version: A2A::VERSION,
474
+ checks: {
475
+ database: check_database,
476
+ redis: check_redis,
477
+ memory: check_memory
478
+ }
479
+ }
480
+ rescue => e
481
+ {
482
+ healthy: false,
483
+ error: e.message,
484
+ timestamp: Time.now.utc.iso8601
485
+ }
486
+ end
487
+ end
488
+
489
+ # Mount health check
490
+ map '/health' do
491
+ run HealthCheckApp.new
492
+ end
493
+
494
+ map '/' do
495
+ run a2a_app
496
+ end
497
+ ```
498
+
499
+ ### Metrics Collection
500
+
501
+ ```ruby
502
+ class MetricsMiddleware
503
+ def initialize(app)
504
+ @app = app
505
+ end
506
+
507
+ def call(env)
508
+ start_time = Time.now
509
+
510
+ status, headers, body = @app.call(env)
511
+
512
+ duration = Time.now - start_time
513
+
514
+ # Record metrics
515
+ A2A.record_metric('http_requests_total', 1,
516
+ method: env['REQUEST_METHOD'],
517
+ path: env['PATH_INFO'],
518
+ status: status
519
+ )
520
+
521
+ A2A.record_metric('http_request_duration_seconds', duration,
522
+ method: env['REQUEST_METHOD'],
523
+ path: env['PATH_INFO']
524
+ )
525
+
526
+ [status, headers, body]
527
+ end
528
+ end
529
+
530
+ use MetricsMiddleware
531
+ ```
532
+
533
+ ## Testing Server Applications
534
+
535
+ ### Testing Rack App
536
+
537
+ ```ruby
538
+ RSpec.describe A2A::Server::Apps::RackApp do
539
+ include Rack::Test::Methods
540
+
541
+ let(:app) { create_test_app }
542
+
543
+ describe "GET /.well-known/a2a/agent-card" do
544
+ it "returns agent card" do
545
+ get "/.well-known/a2a/agent-card"
546
+
547
+ expect(last_response).to be_ok
548
+ expect(last_response.content_type).to include('application/json')
549
+
550
+ card = JSON.parse(last_response.body)
551
+ expect(card['name']).to eq('Test Agent')
552
+ end
553
+ end
554
+
555
+ describe "POST /a2a/rpc" do
556
+ it "handles JSON-RPC requests" do
557
+ request = {
558
+ jsonrpc: "2.0",
559
+ method: "message/send",
560
+ params: { message: test_message.to_h },
561
+ id: 1
562
+ }
563
+
564
+ post "/a2a/rpc", request.to_json,
565
+ 'CONTENT_TYPE' => 'application/json'
566
+
567
+ expect(last_response).to be_ok
568
+
569
+ response = JSON.parse(last_response.body)
570
+ expect(response['jsonrpc']).to eq('2.0')
571
+ expect(response['id']).to eq(1)
572
+ end
573
+ end
574
+ end
575
+ ```
576
+
577
+ ### Testing Sinatra App
578
+
579
+ ```ruby
580
+ RSpec.describe MyA2AApp do
581
+ include Rack::Test::Methods
582
+
583
+ let(:app) { MyA2AApp }
584
+
585
+ it "handles authentication" do
586
+ post "/auth/login", {
587
+ username: "test",
588
+ password: "password"
589
+ }
590
+
591
+ expect(last_response).to be_ok
592
+
593
+ # Test authenticated request
594
+ get "/admin/stats"
595
+ expect(last_response).to be_ok
596
+ end
597
+ end
598
+ ```
599
+
600
+ ## Best Practices
601
+
602
+ 1. **Security**: Always implement proper authentication and authorization
603
+ 2. **Error Handling**: Provide meaningful error responses
604
+ 3. **Logging**: Log important events and errors
605
+ 4. **Monitoring**: Monitor application health and performance
606
+ 5. **Testing**: Write comprehensive tests for your endpoints
607
+ 6. **Documentation**: Document your API endpoints and authentication requirements
608
+ 7. **Deployment**: Use proper deployment practices with process managers
609
+ 8. **Scaling**: Consider horizontal scaling for high-traffic scenarios
610
+
611
+ The server applications provide a solid foundation for deploying A2A agents in production environments while maintaining flexibility for customization and integration with existing systems.
612
+
613
+ ## Complete Examples
614
+
615
+ For complete working examples of server applications, see the [A2A Ruby Samples Repository](https://github.com/a2aproject/a2a-ruby-samples), which includes:
616
+
617
+ - **Rack Applications** - Production-ready Rack server examples
618
+ - **Sinatra Integration** - Lightweight web service examples
619
+ - **Rails Applications** - Full Rails integration with web UI
620
+ - **Docker Deployment** - Container-based deployment examples
621
+ - **Multi-Agent Systems** - Complex agent orchestration examples