External Intent Handlers¶
External intent handlers are standalone Python processes that connect to the desktop agent via WebSocket and register as intent handlers. Unlike embedded plugins, external handlers run in separate processes and can be written by third parties.
Overview¶
External handlers:
- Connect to the agent's WebSocket endpoint (
/ws) - Complete the WCP (Web Connection Protocol) handshake using self-registration
- Register handlers for specific intents
- Receive forwarded intent requests and send results back
Using the Client Library¶
The fdc3.client package provides a simple client for building external handlers:
from fdc3.client import FDC3Client
async with FDC3Client("ws://localhost:8000/ws", handler_id="my-handler") as client:
# Wait for WCP handshake to complete
if not await client.wait_for_handshake():
raise RuntimeError("Handshake failed")
# Register for specific intents
handler_uuid = await client.register_handler(
handler_id="my-handler",
intents=["ViewChart", "AnalyzeData"],
priority=10,
)
# Handle incoming intents
async def on_intent(request):
# request has: request_uuid, intent, context, source
result = await process_intent(request.intent, request.context)
await client.send_intent_result(request.request_uuid, result=result)
# Register handler using the public EventEmitter
client.forwarded_intent_handlers.add(on_intent)
await client.run_forever()
FDC3Client API¶
Constructor¶
FDC3Client(
agent_url: str, # WebSocket URL (e.g., "ws://localhost:8000/ws")
handler_id: str = "external-handler", # Unique handler identifier
ping_interval: float = 20.0, # WebSocket ping interval in seconds
)
Methods¶
| Method | Description |
|---|---|
connect() |
Connect to the agent and initiate WCP handshake |
wait_for_handshake(timeout=10.0) |
Wait for WCP handshake to complete; returns True if successful |
register_handler(handler_id, intents, priority=0, metadata=None) |
Register as a handler for the specified intents; returns handler_uuid |
unregister_handler(handler_uuid) |
Unregister a previously registered handler |
send_intent_result(request_uuid, result=None, error=None) |
Send the result (or error) for a forwarded intent |
forwarded_intent_handlers |
EventEmitter used to subscribe to forwarded intent events (use add/remove) |
close() |
Close the WebSocket connection |
run_forever() |
Block until the connection is closed |
Protocol Messages¶
External handlers use these message types:
registerExternalHandler (client → agent)¶
{
"type": "registerExternalHandler",
"payload": {
"handler_id": "my-handler",
"intents": ["ViewChart", "AnalyzeData"],
"priority": 10,
"metadata": {}
},
"meta": { "requestUuid": "uuid-string" }
}
registerExternalHandlerResponse (agent → client)¶
{
"type": "registerExternalHandlerResponse",
"payload": { "handler_uuid": "uuid-string" },
"meta": { "requestUuid": "uuid-string" }
}
forwardedIntent (agent → client)¶
{
"type": "forwardedIntent",
"payload": {
"request_uuid": "uuid-string",
"intent": "ViewChart",
"context": { "type": "fdc3.chart", "data": {} },
"source": { "appId": "app-id", "instanceId": "instance-id" },
"timeout": 30
}
}
intentResult (client → agent)¶
{
"type": "intentResult",
"payload": {
"request_uuid": "uuid-string",
"result": { "data": "value" },
"error": null
}
}
Example External Handler¶
See the repository examples at https://github.com/jnsquire/py-fdc3-desktop-agent/blob/main/examples/external_handler.py and https://github.com/jnsquire/py-fdc3-desktop-agent/blob/main/examples/external_handler_viewchart.py for complete working examples. The agent includes an Admin UI (http://localhost:8000/admin) with a Raise Intent form that can be used to trigger intents against registered handlers for quick testing.
Run the ViewChart example with:
python -m examples.external_handler --agent-url ws://localhost:8000/ws --handler-id my-handler
# ViewChart-only example
python -m examples.external_handler_viewchart --agent-url ws://localhost:8000/ws --handler-id viewchart-handler --open
Creating an External Handler Package¶
To package an external handler for distribution:
pyproject.toml¶
[project]
name = "my-fdc3-handler"
version = "0.1.0"
dependencies = [
"websockets>=11.0",
]
[project.scripts]
my-fdc3-handler = "my_handler.main:main"
Handler Implementation¶
# my_handler/main.py
import asyncio
from fdc3.client import FDC3Client
async def run():
async with FDC3Client("ws://localhost:8000/ws", handler_id="my-handler") as client:
await client.wait_for_handshake()
await client.register_handler("my-handler", ["MyIntent"], priority=5)
async def on_intent(req):
# Handle the intent
await client.send_intent_result(req.request_uuid, result={"handled": True})
client.forwarded_intent_handlers.add(on_intent)
await client.run_forever()
def main():
asyncio.run(run())
Security Considerations¶
External handlers connect via WebSocket and are trusted once connected. Consider these security measures:
-
Network access: Run the agent on
localhostor behind a firewall to limit who can connect. -
Origin checking: Configure
FDC3_ALLOWED_ORIGINSto restrict WebSocket connections to known origins. -
Handler priority: External handlers with high priority can intercept intents before other handlers. Monitor what handlers are registered.
-
Connection monitoring: The agent logs handler registrations. External handlers are automatically unregistered when their WebSocket connection closes.
-
Authentication (future): For production environments, consider adding API key validation or certificate-based authentication before allowing external handler registration.
Intent Resolution Order¶
When an intent is raised, resolution follows this order:
- System intents (
fdc3.StartApp, etc.) - Embedded plugins (by priority, highest first)
- External handlers (by priority, highest first)
- Standard app resolution (app directory lookup)