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.
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/PKG-INFO +3 -4
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/README.md +1 -1
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/pyproject.toml +7 -3
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/pyproject.toml.orig +13 -3
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/cli/__init__.py +1 -1
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/cli/_helpers.py +23 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/cli/server.py +1 -43
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/cli/task.py +34 -11
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/mcp/server.py +6 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/servers.py +0 -35
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/app.py +0 -2
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/server/tab.py +0 -194
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/server/views.py +0 -4
- a2a_handler-0.1.26/src/a2a_handler/server/__init__.py +0 -79
- a2a_handler-0.1.26/src/a2a_handler/server/agent.py +0 -133
- a2a_handler-0.1.26/src/a2a_handler/server/app.py +0 -221
- a2a_handler-0.1.26/src/a2a_handler/server/card.py +0 -164
- a2a_handler-0.1.26/src/a2a_handler/server/ollama.py +0 -75
- a2a_handler-0.1.26/src/a2a_handler/server/tools/__init__.py +0 -23
- a2a_handler-0.1.26/src/a2a_handler/server/tools/a2a_docs.py +0 -140
- a2a_handler-0.1.26/src/a2a_handler/server/tools/handler_docs.py +0 -44
- a2a_handler-0.1.26/src/a2a_handler/server/tools/source.py +0 -109
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/LICENSE +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/__init__.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/auth.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/cli/card.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/cli/mcp.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/cli/message.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/cli/schema.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/cli/session.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/common/__init__.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/common/config.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/common/dotenv.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/common/input_validation.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/common/logging.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/common/output.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/mcp/__init__.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/service.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/session.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/__init__.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/app.tcss +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/commands/__init__.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/commands/palette.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/commands/screens.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/components/__init__.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/components/artifacts.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/components/auth.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/components/card.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/components/headers.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/components/input.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/components/logs.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/components/messages.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/components/tasks.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/server/__init__.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/server/save.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/server/session.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/server/tabs.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/server/types.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/tui/server/workspace.py +0 -0
- {a2a_handler-0.1.26 → a2a_handler-0.2.0}/src/a2a_handler/validation.py +0 -0
- {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.
|
|
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
|
-

|
|
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
|
-

|
|
18
18
|
|
|
19
19
|
## Install
|
|
20
20
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "a2a-handler"
|
|
3
|
-
version = "0.
|
|
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.
|
|
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
|
|
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
|
|
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",
|
|
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 --
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
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
|
|