a2a-handler 0.1.26__tar.gz → 0.2.0__tar.gz

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 (61) hide show
  1. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/PKG-INFO +3 -4
  2. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/README.md +1 -1
  3. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/pyproject.toml +7 -3
  4. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/pyproject.toml.orig +13 -3
  5. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/cli/__init__.py +1 -1
  6. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/cli/_helpers.py +23 -0
  7. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/cli/server.py +1 -43
  8. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/cli/task.py +34 -11
  9. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/mcp/server.py +6 -0
  10. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/servers.py +0 -35
  11. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/app.py +0 -2
  12. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/server/tab.py +0 -194
  13. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/server/views.py +0 -4
  14. a2a_handler-0.1.26/src/a2a_handler/server/__init__.py +0 -79
  15. a2a_handler-0.1.26/src/a2a_handler/server/agent.py +0 -133
  16. a2a_handler-0.1.26/src/a2a_handler/server/app.py +0 -221
  17. a2a_handler-0.1.26/src/a2a_handler/server/card.py +0 -164
  18. a2a_handler-0.1.26/src/a2a_handler/server/ollama.py +0 -75
  19. a2a_handler-0.1.26/src/a2a_handler/server/tools/__init__.py +0 -23
  20. a2a_handler-0.1.26/src/a2a_handler/server/tools/a2a_docs.py +0 -140
  21. a2a_handler-0.1.26/src/a2a_handler/server/tools/handler_docs.py +0 -44
  22. a2a_handler-0.1.26/src/a2a_handler/server/tools/source.py +0 -109
  23. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/LICENSE +0 -0
  24. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/__init__.py +0 -0
  25. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/auth.py +0 -0
  26. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/cli/card.py +0 -0
  27. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/cli/mcp.py +0 -0
  28. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/cli/message.py +0 -0
  29. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/cli/schema.py +0 -0
  30. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/cli/session.py +0 -0
  31. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/common/__init__.py +0 -0
  32. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/common/config.py +0 -0
  33. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/common/dotenv.py +0 -0
  34. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/common/input_validation.py +0 -0
  35. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/common/logging.py +0 -0
  36. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/common/output.py +0 -0
  37. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/mcp/__init__.py +0 -0
  38. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/service.py +0 -0
  39. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/session.py +0 -0
  40. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/__init__.py +0 -0
  41. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/app.tcss +0 -0
  42. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/commands/__init__.py +0 -0
  43. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/commands/palette.py +0 -0
  44. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/commands/screens.py +0 -0
  45. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/components/__init__.py +0 -0
  46. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/components/artifacts.py +0 -0
  47. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/components/auth.py +0 -0
  48. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/components/card.py +0 -0
  49. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/components/headers.py +0 -0
  50. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/components/input.py +0 -0
  51. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/components/logs.py +0 -0
  52. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/components/messages.py +0 -0
  53. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/components/tasks.py +0 -0
  54. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/server/__init__.py +0 -0
  55. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/server/save.py +0 -0
  56. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/server/session.py +0 -0
  57. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/server/tabs.py +0 -0
  58. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/server/types.py +0 -0
  59. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/server/workspace.py +0 -0
  60. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/validation.py +0 -0
  61. {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/webhook.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: a2a-handler
3
- Version: 0.1.26
3
+ Version: 0.2.0
4
4
  Summary: An A2A Protocol client TUI and CLI.
5
5
  Keywords: a2a,a2a-protocol,agent,agent-to-agent,ai,cli,tui
6
6
  Author: Al Duncanson
@@ -20,11 +20,10 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
20
  Classifier: Typing :: Typed
21
21
  Requires-Dist: a2a-sdk[http-server]>=1.0.0,<2.0.0
22
22
  Requires-Dist: click>=8.0.0
23
- Requires-Dist: google-adk[mcp]>=2.5.0
24
23
  Requires-Dist: httpx>=0.27.0
25
- Requires-Dist: litellm>=1.0.0
26
24
  Requires-Dist: python-dotenv>=1.0.0
27
25
  Requires-Dist: rich>=13.0.0
26
+ Requires-Dist: starlette>=0.37.0
28
27
  Requires-Dist: textual>=0.47.0
29
28
  Requires-Dist: uvicorn>=0.30.0
30
29
  Requires-Dist: textual-serve>=1.1.0
@@ -55,7 +54,7 @@ MCP server that lets other agents integrate with A2A services directly. Handler
55
54
  also supports global and repo-scoped A2A server configuration with bearer,
56
55
  API key, mTLS, and OAuth2 client credentials auth.
57
56
 
58
- ![Handler TUI connected to the built-in Handler Agent, showing the agent card and a completed assistant response](https://raw.githubusercontent.com/alDuncanson/Handler/73915875903b60dad6e4e404aa7ed91b6d94559f/assets/tui.png)
57
+ ![Handler TUI connected to an A2A agent, showing the agent card and a completed assistant response](https://raw.githubusercontent.com/alDuncanson/Handler/73915875903b60dad6e4e404aa7ed91b6d94559f/assets/tui.png)
59
58
 
60
59
  ## Install
61
60
 
@@ -14,7 +14,7 @@ MCP server that lets other agents integrate with A2A services directly. Handler
14
14
  also supports global and repo-scoped A2A server configuration with bearer,
15
15
  API key, mTLS, and OAuth2 client credentials auth.
16
16
 
17
- ![Handler TUI connected to the built-in Handler Agent, showing the agent card and a completed assistant response](https://raw.githubusercontent.com/alDuncanson/Handler/73915875903b60dad6e4e404aa7ed91b6d94559f/assets/tui.png)
17
+ ![Handler TUI connected to an A2A agent, showing the agent card and a completed assistant response](https://raw.githubusercontent.com/alDuncanson/Handler/73915875903b60dad6e4e404aa7ed91b6d94559f/assets/tui.png)
18
18
 
19
19
  ## Install
20
20
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "a2a-handler"
3
- version = "0.1.26"
3
+ version = "0.2.0"
4
4
  description = "An A2A Protocol client TUI and CLI."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -31,11 +31,10 @@ classifiers = [
31
31
  dependencies = [
32
32
  "a2a-sdk[http-server]>=1.0.0,<2.0.0",
33
33
  "click>=8.0.0",
34
- "google-adk[mcp]>=2.5.0",
35
34
  "httpx>=0.27.0",
36
- "litellm>=1.0.0",
37
35
  "python-dotenv>=1.0.0",
38
36
  "rich>=13.0.0",
37
+ "starlette>=0.37.0",
39
38
  "textual>=0.47.0",
40
39
  "uvicorn>=0.30.0",
41
40
  "textual-serve>=1.1.0",
@@ -76,6 +75,11 @@ build-backend = "uv_build"
76
75
  [tool.uv.build-backend]
77
76
  module-root = "src"
78
77
 
78
+ [[tool.uv.index]]
79
+ name = "pypi"
80
+ url = "https://pypi.org/simple"
81
+ default = true
82
+
79
83
  [tool.pytest.ini_options]
80
84
  asyncio_mode = "auto"
81
85
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "a2a-handler"
3
- version = "0.1.26"
3
+ version = "0.2.0"
4
4
  description = "An A2A Protocol client TUI and CLI."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -34,11 +34,12 @@ classifiers = [
34
34
  dependencies = [
35
35
  "a2a-sdk[http-server]>=1.0.0,<2.0.0",
36
36
  "click>=8.0.0",
37
- "google-adk[mcp]>=2.5.0",
38
37
  "httpx>=0.27.0",
39
- "litellm>=1.0.0",
40
38
  "python-dotenv>=1.0.0",
41
39
  "rich>=13.0.0",
40
+ # Imported directly by the push notification webhook server. Declared rather
41
+ # than relied upon transitively via a2a-sdk[http-server].
42
+ "starlette>=0.37.0",
42
43
  "textual>=0.47.0",
43
44
  "uvicorn>=0.30.0",
44
45
  "textual-serve>=1.1.0",
@@ -75,6 +76,15 @@ build-backend = "uv_build"
75
76
  [tool.uv.build-backend]
76
77
  module-root = "src"
77
78
 
79
+ # This is a public package published to PyPI, so its lockfile must resolve from
80
+ # pypi.org. Pinned explicitly because an ambient UV_INDEX_URL (e.g. a corporate
81
+ # mirror) would otherwise rewrite every source URL in uv.lock on the next
82
+ # `uv lock`, leaking internal infrastructure and breaking CI for contributors.
83
+ [[tool.uv.index]]
84
+ name = "pypi"
85
+ url = "https://pypi.org/simple"
86
+ default = true
87
+
78
88
  [tool.pytest.ini_options]
79
89
  asyncio_mode = "auto"
80
90
 
@@ -6,7 +6,7 @@ Provides commands for interacting with A2A agents:
6
6
  - task notification set: Configure push notifications
7
7
  - card get/validate: Agent card operations
8
8
  - server list/show/add/remove/validate: Manage configured servers
9
- - server run agent/push: Run local servers
9
+ - server run push: Run the local push notification webhook server
10
10
  - session list/show/clear: Manage saved conversation sessions
11
11
 
12
12
  Authentication is configured per-server via `handler server add` or
@@ -292,6 +292,29 @@ def resolve_agent_target(
292
292
  return selection.agent_url, credentials
293
293
 
294
294
 
295
+ def resolve_task_id(positional: str | None, option: str | None) -> str | None:
296
+ """Resolve a task ID given either positionally or via --task.
297
+
298
+ Returns None when neither is supplied, because `--params` may still carry a
299
+ task_id; callers check for a missing ID after merging that payload.
300
+ """
301
+ if positional is not None and option is not None and positional != option:
302
+ raise click.UsageError(
303
+ "Conflicting task IDs: "
304
+ f"{positional!r} was given positionally and {option!r} with --task."
305
+ )
306
+ return positional if positional is not None else option
307
+
308
+
309
+ def require_task_id(task_id: str | None) -> str:
310
+ """Return the resolved task ID, or fail with usage guidance when absent."""
311
+ if not task_id:
312
+ raise click.UsageError(
313
+ "Missing task ID. Pass it positionally (e.g. `task-123`) or with --task."
314
+ )
315
+ return task_id
316
+
317
+
295
318
  def handle_validation_error(error: InputValidationError, output: Output) -> None:
296
319
  """Render input validation errors in the standard envelope."""
297
320
  output.error(
@@ -7,7 +7,7 @@ import stat
7
7
  import tempfile
8
8
  import tomllib
9
9
  from pathlib import Path
10
- from typing import Optional, cast
10
+ from typing import cast
11
11
 
12
12
  import click
13
13
 
@@ -16,7 +16,6 @@ from a2a_handler.common.input_validation import (
16
16
  InputValidationError,
17
17
  validate_header_name,
18
18
  )
19
- from a2a_handler.server import run_server
20
19
  from a2a_handler.servers import (
21
20
  SERVERS_FILENAME,
22
21
  SERVER_SCHEMA_VERSION,
@@ -613,47 +612,6 @@ def server_run() -> None:
613
612
  pass
614
613
 
615
614
 
616
- @server_run.command("agent")
617
- @click.option("--host", default="0.0.0.0", help="Host to bind to", show_default=True)
618
- @click.option("--port", default=8000, help="Port to bind to", show_default=True)
619
- @click.option("--auth/--no-auth", default=False, help="Require API key authentication")
620
- @click.option(
621
- "--api-key",
622
- default=None,
623
- help="Specific API key to use (auto-generated if not set)",
624
- )
625
- @click.option(
626
- "--model",
627
- "-m",
628
- default=None,
629
- help="Model to use (e.g., 'gemma4:e2b', 'qwen3', 'gemini-2.0-flash')",
630
- )
631
- def server_agent(
632
- host: str,
633
- port: int,
634
- auth: bool,
635
- api_key: Optional[str],
636
- model: Optional[str],
637
- ) -> None:
638
- """Start a local A2A agent server.
639
-
640
- \b
641
- Examples:
642
- $ handler server run agent
643
- $ handler server run agent --port 9000
644
- $ handler server run agent --auth --api-key my-secret
645
- $ handler server run agent --model gemma4:e4b
646
- """
647
- log.info("Starting A2A server on %s:%d", host, port)
648
- run_server(
649
- host=host,
650
- port=port,
651
- require_auth=auth,
652
- api_key=api_key,
653
- model=model,
654
- )
655
-
656
-
657
615
  @server_run.command("push")
658
616
  @click.option("--host", default="127.0.0.1", help="Host to bind to", show_default=True)
659
617
  @click.option("--port", default=9000, help="Port to bind to", show_default=True)
@@ -28,8 +28,10 @@ from ._helpers import (
28
28
  build_streaming_http_client,
29
29
  handle_client_error,
30
30
  handle_validation_error,
31
+ require_task_id,
31
32
  resolve_agent_selection,
32
33
  resolve_selection_credentials,
34
+ resolve_task_id,
33
35
  )
34
36
 
35
37
  log = get_logger(__name__)
@@ -42,9 +44,10 @@ def task() -> None:
42
44
 
43
45
 
44
46
  @task.command("get")
47
+ @click.argument("task_id_arg", metavar="[TASK_ID]", required=False)
45
48
  @click.option("--url", "agent_url", help="Agent URL")
46
49
  @click.option("--server", "-s", "server_name", help="Named server from servers.toml")
47
- @click.option("--task", "task_id", required=True, help="Task ID to retrieve")
50
+ @click.option("--task", "task_id", help="Task ID to retrieve (alias for TASK_ID)")
48
51
  @click.option(
49
52
  "--history-length", "-n", type=int, help="Number of history messages to include"
50
53
  )
@@ -60,9 +63,10 @@ def task() -> None:
60
63
  "--api-key-env", "-k", help="Env var containing API key (overrides saved)"
61
64
  )
62
65
  def task_get(
66
+ task_id_arg: Optional[str],
63
67
  agent_url: Optional[str],
64
68
  server_name: Optional[str],
65
- task_id: str,
69
+ task_id: Optional[str],
66
70
  history_length: Optional[int],
67
71
  json_params: Optional[str],
68
72
  bearer_env: Optional[str],
@@ -72,12 +76,14 @@ def task_get(
72
76
 
73
77
  \b
74
78
  Examples:
79
+ $ handler task get task-123 --server my_agent
75
80
  $ handler task get --server my_agent --task task-123
76
81
  $ handler task get --url http://localhost:8000 --task task-123
77
- $ handler task get --server my_agent --task task-123 --history-length 10
82
+ $ handler task get task-123 --server my_agent --history-length 10
78
83
  """
79
84
  output = Output()
80
85
  payload: dict[str, Any] = {}
86
+ task_id = resolve_task_id(task_id_arg, task_id)
81
87
 
82
88
  selection = resolve_agent_selection(agent_url, server_name)
83
89
  resolved_url = selection.agent_url
@@ -99,6 +105,7 @@ def task_get(
99
105
  if history_length is None and isinstance(payload_history_length, int):
100
106
  history_length = payload_history_length
101
107
 
108
+ task_id = require_task_id(task_id)
102
109
  validate_resource_id(task_id, "task_id")
103
110
  except InputValidationError as error:
104
111
  handle_validation_error(error, output)
@@ -122,9 +129,10 @@ def task_get(
122
129
 
123
130
 
124
131
  @task.command("cancel")
132
+ @click.argument("task_id_arg", metavar="[TASK_ID]", required=False)
125
133
  @click.option("--url", "agent_url", help="Agent URL")
126
134
  @click.option("--server", "-s", "server_name", help="Named server from servers.toml")
127
- @click.option("--task", "task_id", required=True, help="Task ID to cancel")
135
+ @click.option("--task", "task_id", help="Task ID to cancel (alias for TASK_ID)")
128
136
  @click.option(
129
137
  "--bearer-env", "-b", help="Env var containing bearer token (overrides saved)"
130
138
  )
@@ -132,9 +140,10 @@ def task_get(
132
140
  "--api-key-env", "-k", help="Env var containing API key (overrides saved)"
133
141
  )
134
142
  def task_cancel(
143
+ task_id_arg: Optional[str],
135
144
  agent_url: Optional[str],
136
145
  server_name: Optional[str],
137
- task_id: str,
146
+ task_id: Optional[str],
138
147
  bearer_env: Optional[str],
139
148
  api_key_env: Optional[str],
140
149
  ) -> None:
@@ -142,10 +151,12 @@ def task_cancel(
142
151
 
143
152
  \b
144
153
  Examples:
154
+ $ handler task cancel task-123 --server my_agent
145
155
  $ handler task cancel --server my_agent --task task-123
146
156
  $ handler task cancel --url http://localhost:8000 --task task-123
147
157
  """
148
158
  output = Output()
159
+ task_id = require_task_id(resolve_task_id(task_id_arg, task_id))
149
160
 
150
161
  selection = resolve_agent_selection(agent_url, server_name)
151
162
  resolved_url = selection.agent_url
@@ -177,9 +188,10 @@ def task_cancel(
177
188
 
178
189
 
179
190
  @task.command("resubscribe")
191
+ @click.argument("task_id_arg", metavar="[TASK_ID]", required=False)
180
192
  @click.option("--url", "agent_url", help="Agent URL")
181
193
  @click.option("--server", "-s", "server_name", help="Named server from servers.toml")
182
- @click.option("--task", "task_id", required=True, help="Task ID to resubscribe to")
194
+ @click.option("--task", "task_id", help="Task ID to resubscribe to (alias for TASK_ID)")
183
195
  @click.option(
184
196
  "--bearer-env", "-b", help="Env var containing bearer token (overrides saved)"
185
197
  )
@@ -187,9 +199,10 @@ def task_cancel(
187
199
  "--api-key-env", "-k", help="Env var containing API key (overrides saved)"
188
200
  )
189
201
  def task_resubscribe(
202
+ task_id_arg: Optional[str],
190
203
  agent_url: Optional[str],
191
204
  server_name: Optional[str],
192
- task_id: str,
205
+ task_id: Optional[str],
193
206
  bearer_env: Optional[str],
194
207
  api_key_env: Optional[str],
195
208
  ) -> None:
@@ -197,10 +210,12 @@ def task_resubscribe(
197
210
 
198
211
  \b
199
212
  Examples:
213
+ $ handler task resubscribe task-123 --server my_agent
200
214
  $ handler task resubscribe --server my_agent --task task-123
201
215
  $ handler task resubscribe --url http://localhost:8000 --task task-123
202
216
  """
203
217
  output = Output()
218
+ task_id = require_task_id(resolve_task_id(task_id_arg, task_id))
204
219
 
205
220
  selection = resolve_agent_selection(agent_url, server_name)
206
221
  resolved_url = selection.agent_url
@@ -255,9 +270,10 @@ def task_notification() -> None:
255
270
 
256
271
 
257
272
  @task_notification.command("set")
273
+ @click.argument("task_id_arg", metavar="[TASK_ID]", required=False)
258
274
  @click.option("--url", "agent_url", help="Agent URL")
259
275
  @click.option("--server", "-s", "server_name", help="Named server from servers.toml")
260
- @click.option("--task", "task_id", required=True, help="Task ID")
276
+ @click.option("--task", "task_id", help="Task ID (alias for TASK_ID)")
261
277
  @click.option(
262
278
  "--webhook-url", required=True, help="Webhook URL to receive notifications"
263
279
  )
@@ -269,9 +285,10 @@ def task_notification() -> None:
269
285
  "--api-key-env", "-k", help="Env var containing API key (overrides saved)"
270
286
  )
271
287
  def notification_set(
288
+ task_id_arg: Optional[str],
272
289
  agent_url: Optional[str],
273
290
  server_name: Optional[str],
274
- task_id: str,
291
+ task_id: Optional[str],
275
292
  webhook_url: str,
276
293
  token: Optional[str],
277
294
  bearer_env: Optional[str],
@@ -281,10 +298,12 @@ def notification_set(
281
298
 
282
299
  \b
283
300
  Examples:
301
+ $ handler task notification set task-123 --server my_agent --webhook-url http://webhook.example.com
284
302
  $ handler task notification set --server my_agent --task task-123 --webhook-url http://webhook.example.com
285
303
  $ handler task notification set --url http://localhost:8000 --task task-123 --webhook-url http://webhook.example.com --token SECRET
286
304
  """
287
305
  output = Output()
306
+ task_id = require_task_id(resolve_task_id(task_id_arg, task_id))
288
307
 
289
308
  selection = resolve_agent_selection(agent_url, server_name)
290
309
  resolved_url = selection.agent_url
@@ -319,9 +338,10 @@ def notification_set(
319
338
 
320
339
 
321
340
  @task_notification.command("get")
341
+ @click.argument("task_id_arg", metavar="[TASK_ID]", required=False)
322
342
  @click.option("--url", "agent_url", help="Agent URL")
323
343
  @click.option("--server", "-s", "server_name", help="Named server from servers.toml")
324
- @click.option("--task", "task_id", required=True, help="Task ID")
344
+ @click.option("--task", "task_id", help="Task ID (alias for TASK_ID)")
325
345
  @click.option("--config-id", "-c", help="Specific push notification config ID")
326
346
  @click.option(
327
347
  "--bearer-env", "-b", help="Env var containing bearer token (overrides saved)"
@@ -330,9 +350,10 @@ def notification_set(
330
350
  "--api-key-env", "-k", help="Env var containing API key (overrides saved)"
331
351
  )
332
352
  def notification_get(
353
+ task_id_arg: Optional[str],
333
354
  agent_url: Optional[str],
334
355
  server_name: Optional[str],
335
- task_id: str,
356
+ task_id: Optional[str],
336
357
  config_id: Optional[str],
337
358
  bearer_env: Optional[str],
338
359
  api_key_env: Optional[str],
@@ -341,10 +362,12 @@ def notification_get(
341
362
 
342
363
  \b
343
364
  Examples:
365
+ $ handler task notification get task-123 --server my_agent
344
366
  $ handler task notification get --server my_agent --task task-123
345
367
  $ handler task notification get --url http://localhost:8000 --task task-123
346
368
  """
347
369
  output = Output()
370
+ task_id = require_task_id(resolve_task_id(task_id_arg, task_id))
348
371
 
349
372
  selection = resolve_agent_selection(agent_url, server_name)
350
373
  resolved_url = selection.agent_url
@@ -6,6 +6,7 @@ from typing import Literal
6
6
  import httpx
7
7
  from mcp.server.fastmcp import FastMCP
8
8
 
9
+ from a2a_handler import __version__
9
10
  from a2a_handler.auth import (
10
11
  AuthCredentials,
11
12
  AuthType,
@@ -122,6 +123,11 @@ def create_mcp_server() -> FastMCP:
122
123
  website_url="https://github.com/alDuncanson/handler",
123
124
  )
124
125
 
126
+ # FastMCP exposes no `version` parameter, and the low-level server reports the
127
+ # `mcp` library version when its own is left unset. Set Handler's version so
128
+ # clients inspecting `serverInfo` see the server they are talking to.
129
+ mcp._mcp_server.version = __version__
130
+
125
131
  @mcp.tool()
126
132
  async def validate_agent_card(
127
133
  source: str,
@@ -39,9 +39,6 @@ logger = get_logger(__name__)
39
39
  DEFAULT_SERVER_DIRECTORY = Path(user_config_dir("handler"))
40
40
  SERVERS_FILENAME = "servers.toml"
41
41
  SERVER_SCHEMA_VERSION = 1
42
- DEFAULT_HANDLER_AGENT_NAME = "Handler Agent"
43
- DEFAULT_HANDLER_AGENT_URL = "http://localhost:8000"
44
- DEFAULT_HANDLER_AGENT_SERVER_ID = "global:handler-agent"
45
42
  _ENV_NAME_PATTERN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
46
43
 
47
44
 
@@ -107,22 +104,6 @@ class ServerCatalog:
107
104
  }
108
105
 
109
106
 
110
- def default_handler_agent_server() -> ServerDefinition:
111
- """Return Handler's built-in embedded-agent server entry."""
112
- return ServerDefinition(
113
- server_id=DEFAULT_HANDLER_AGENT_SERVER_ID,
114
- source=ServerSource.GLOBAL,
115
- name=DEFAULT_HANDLER_AGENT_NAME,
116
- agent_url=DEFAULT_HANDLER_AGENT_URL,
117
- origin_label=server_source_label(ServerSource.GLOBAL),
118
- )
119
-
120
-
121
- def is_default_handler_agent_server(server_def: ServerDefinition) -> bool:
122
- """Return whether a server definition is Handler's built-in local agent."""
123
- return server_def.server_id == DEFAULT_HANDLER_AGENT_SERVER_ID
124
-
125
-
126
107
  class ServerConfigError(ValueError):
127
108
  """Raised when server configuration is malformed."""
128
109
 
@@ -425,22 +406,6 @@ def load_server_catalog(
425
406
  load_servers(local_server_dir, ServerSource.REPOSITORY)
426
407
  )
427
408
 
428
- if server_directory is None:
429
- configured_names = {
430
- server_def.name.casefold()
431
- for server_def in (*repository_servers, *global_servers)
432
- if server_def.name is not None
433
- }
434
- configured_urls = {
435
- server_def.agent_url
436
- for server_def in (*repository_servers, *global_servers)
437
- }
438
- if (
439
- DEFAULT_HANDLER_AGENT_NAME.casefold() not in configured_names
440
- and DEFAULT_HANDLER_AGENT_URL not in configured_urls
441
- ):
442
- global_servers = (*global_servers, default_handler_agent_server())
443
-
444
409
  return ServerCatalog(
445
410
  repository_servers=repository_servers,
446
411
  global_servers=global_servers,
@@ -24,7 +24,6 @@ from a2a_handler.tui.commands import (
24
24
  )
25
25
  from a2a_handler.tui.components import AgentCardPanel, TabbedMessagesPanel
26
26
  from a2a_handler.tui.server.save import save_connections_to_workspace
27
- from a2a_handler.tui.server.tab import shutdown_default_handler_agent
28
27
  from a2a_handler.tui.server.tabs import ServerTabs
29
28
  from a2a_handler.tui.server.workspace import (
30
29
  remove_workspace_server,
@@ -409,7 +408,6 @@ class HandlerTUI(App[Any]):
409
408
  async def on_unmount(self) -> None:
410
409
  if self._tui_log_handler is not None:
411
410
  self._tui_log_handler.set_callback(None)
412
- shutdown_default_handler_agent()
413
411
  logger.info("Shutting down TUI application")
414
412
 
415
413