opentool_daemon 0.5.0 copy "opentool_daemon: ^0.5.0" to clipboard
opentool_daemon: ^0.5.0 copied to clipboard

A HTTP Daemon Server to manage native OpenTool Servers.

OpenTool Daemon HTTP API Documentation #

English | 中文

OpenTool Daemon is a lightweight HTTP daemon that keeps track of local OpenTool servers and running tool processes. The daemon listens on http://127.0.0.1:19627/opentool-daemon by default and exposes REST + Server-Sent Event (SSE) endpoints that can be consumed by the CLI or any other automation.


Build & Run #

# install dependencies
dart pub get

# build release binaries
dart compile exe bin/opentool_daemon.dart -o build/opentoold  # macOS / Linux
# dart compile exe bin/opentool_daemon.dart -o build/opentoold.exe  # Windows

# run locally
dart run bin/opentool_daemon.dart --config bin/config.json

The daemon will persist metadata under ~/.opentool (servers, tools, config) and write logs to log/daemon.log.

Configuration #

  • bin/config.json is optional. If the file is missing or only contains null fields the daemon falls back to the defaults (host: 127.0.0.1, port: 19627, prefix: /opentool-daemon, log.level: INFO).
  • Any field you provide overrides the default while other properties stay untouched; e.g. { "server": { "port": 20000 } } keeps the host/prefix unchanged.
  • Both --config <path> and --config=<path> load an explicit file. A missing or invalid explicitly selected file fails startup instead of silently selecting another configuration.
  • The repository bin/config.json binds 127.0.0.1. Keep the daemon on loopback unless an authenticated network boundary is provided.
  • Release builds use the compile-time daemon version (0.5.0 in this release), so AOT binaries do not depend on a runtime pubspec.yaml.

API Overview #

All paths below are relative to the base prefix /opentool-daemon.

Group Method Path Description
Manage GET /version Health check and daemon version
Manage POST /apiKey Create a daemon API key (requires sudo token)
Manage GET /apiKeys List daemon API keys (requires sudo token)
Manage DELETE /apiKey/{apiKey} Delete a daemon API key (requires sudo token)
Server GET /servers/list List cached OpenTool servers
Server POST /servers/build Build a server from an Opentoolfile (SSE stream)
Server POST /servers/pull Resolve and pull an exact OpenTool Hub version (SSE)
Server PUT /servers/builtins/reconcile Reconcile host-managed builtin Server references
Server DELETE /servers/{serverId} Delete a server record
Server GET /servers/{serverId}/export Copy an .ots to a destination folder
Server POST /servers/import Import an external .ots file
Server POST /servers/{serverId}/alias Rename a server alias
Tool GET /tools/list List running tools (or all tools)
Tool GET /tools/listWithApiKeys List tools and their API keys (requires daemon API key)
Tool GET /tools/events Subscribe to tool lifecycle events over SSE
Tool POST /tools/create Run a tool from a server definition (SSE stream)
Tool POST /tools/{toolId}/start Restart a previously created tool (SSE stream)
Tool POST /tools/{toolId}/stop Stop a running tool
Tool DELETE /tools/{toolId} Stop and remove a tool
Tool POST /tools/{toolId}/call Call a tool function (JSON RPC)
Tool POST /tools/{toolId}/streamCall Stream tool responses over SSE
Tool GET /tools/{toolId}/load Return the OpenTool JSON spec
Tool POST /tools/{toolId}/alias Rename a tool alias

Notes on Streaming Endpoints #

/servers/build, /servers/pull, /tools/create, /tools/{id}/start, and /tools/{id}/streamCall return text/event-stream. Events follow this format:

event:START|DATA|DONE|ERROR
data:{"json":"payload"}

Use the client in lib/src/client/client.dart or any SSE-capable HTTP library to consume them.

The public Dart client exposes pullServerEvents as a typed Stream; cancelling its subscription closes the HTTP request and cancels the daemon operation. deleteServerDetailed preserves the daemon's artifactRemoved result. Client HTTP failures are reported as DaemonApiException with stable code, message, retryable, and statusCode fields.

The updated startTool protocol completes only after the Tool is ready and the daemon emits DONE. Startup failure, timeout, or a stream that closes before a terminal event causes the client Future to fail. All clients must use these terminal DONE/ERROR semantics.

Hub authentication and publishing are intentionally not daemon responsibilities. The daemon only resolves, downloads, verifies, and registers public Hub artifacts; publisher login and push belong to Hub clients such as the CLI.

Security Headers & Tokens #

  • x-opentool-sudo-token: single-use header that protects the API key endpoints. Generate a token as an administrator (the CLI calls SudoUtil.ensureSudoAndWriteToken) which writes ~/.opentool/opentool-daemon.sudo. There is no HTTP endpoint to mint this token, so a plain HTTP client cannot call /apiKey* until you first obtain the token via the CLI with sudo/admin privileges. Pass the token value in requests to /apiKey*; without it, API key management calls are rejected (403). The daemon validates the token against the file and deletes it (or expires it) after the first successful call.
  • x-opentool-api-key: persistent API keys created via POST /apiKey. Send the key in this header when accessing /tools/listWithApiKeys or any other API key-gated endpoint. Keys are stored in the Hive database under ~/.opentool/db (box name api_keys) and can be revoked via DELETE /apiKey/{apiKey}.

Manage API Examples #

GET /version #

{
  "name": "OpenTool Daemon",
  "version": "0.5.0",
  "apiVersion": 2,
  "capabilities": ["artifactRegistryV2", "builtinReconcileV1", "pullSseV1", "startToolTerminalV1", "toolMaterializationV1"]
}

POST /apiKey (requires x-opentool-sudo-token) #

Set the header to the temporary token dropped at ~/.opentool/opentool-daemon.sudo and optionally name the key:

POST /opentool-daemon/apiKey
x-opentool-sudo-token: <temp-token>
{
  "name": "dev-console"
}

Response:

{
  "name": "dev-console",
  "apiKey": "pk_opentool_123",
  "createdAt": "2024-05-01T12:30:00.000Z"
}

GET /apiKeys (requires x-opentool-sudo-token) #

Returns every stored API key using the same sudo header. Pair this endpoint with a secure UI to audit daemon access tokens.

[
  {
    "name": "dev-console",
    "apiKey": "pk_opentool_123",
    "createdAt": "2024-05-01T12:30:00.000Z"
  }
]

DELETE /apiKey/{apiKey} (requires x-opentool-sudo-token) #

Removes the selected key immediately; running tools that relied on that key will fail the next privileged call.


Server API Examples #

GET /servers/list #

[
  {
    "id": "srv-1",
    "alias": "alpha",
    "registry": "local",
    "namespace": "local",
    "name": "demo-server",
    "version": "latest",
    "source": {
      "registry": "local",
      "namespace": "local",
      "name": "demo-server",
      "version": "latest",
      "artifactDigest": "sha256:...",
      "artifactMediaType": "application/vnd.opentool.ots"
    },
    "platform": {"os": "linux", "arch": "arm64", "abi": "glibc"},
    "size": 123456,
    "createdAt": "2026-08-03T00:00:00.000Z"
  }
]

Server versions are immutable. Register or pull a different version to create a different Server reference.

POST /servers/pull #

The request body selects an exact immutable Hub version. The daemon detects the local platform; clients cannot select an artifact directly.

{
  "registry": "https://www.opentool-hub.com",
  "namespace": "litevar",
  "name": "stock",
  "version": "1.2.3",
  "client": "embedded"
}

SSE stages are resolving, downloading, verifying, registering, and ready. An error event contains code, message, retryable, and an optional Hub requestId. A done event is emitted only after the verified .ots and its Server reference have been persisted.

Registry and artifact URLs may use HTTP or HTTPS. Use HTTPS across untrusted networks because HTTP does not provide transport confidentiality or integrity.

Local Hub debugging

The Hub Backend has not yet been deployed at https://www.opentool-hub.com, but that does not block local development. Two workflows are supported:

  1. Run the automated contract acceptance test without another repository:

    dart test test/service/hub_pull_acceptance_test.dart
    

    It starts temporary HTTP Hub and artifact endpoints and covers macOS arm64/native, Linux arm64/glibc, resolve, download, digest verification, pull registration, restart reconciliation, and rollback failures. On the current POSIX platform it also starts a real test Tool and completes pull → list → run → load/call → stop → restart list.

  2. Run the real Hub Backend locally:

    # Run from opentool-hub/backend; development defaults to in-memory stores.
    OPENTOOL_HUB_PUBLIC_URL=http://127.0.0.1:8080 \
    OPENTOOL_HUB_WEB_ORIGIN=http://127.0.0.1:8080 \
    OPENTOOL_HUB_ADDRESS=127.0.0.1:8080 \
    go run ./cmd/opentool-hub
    

    Publish an .ots matching the daemon platform through the local Hub Web/CLI, then use http://127.0.0.1:8080 as the pull registry. HTTP needs no extra feature flag. In-memory publications are lost when the Hub process restarts.

Production-domain deployment is a release acceptance condition, not a blocker for local implementation and contract tests.

Hub acceptance gate

Run before submission:

dart format --output=none --set-exit-if-changed lib test bin
dart analyze
dart test
dart compile exe bin/opentool_daemon.dart -o /tmp/opentoold

After production deployment, real macOS arm64 and Linux arm64 artifacts must each pass pull → list → run → load/call → stop → daemon restart → list. That gate requires a real Hub, signed download URL, and executable artifact and cannot be replaced by the repository's simulated tests.

POST /servers/build?opentoolfile=/path/to/repo&name=demo&tag=latest #

Streams build progress for each command listed in Opentoolfile. event:DATA packets contain:

{
  "script": "dart run build",
  "output": "..."
}

event:DONE closes the stream once the .ots artifact is committed to the content-addressed Artifact Registry.

GET /servers/{serverId}/export #

Provide a JSON body { "path": "/tmp/output" } describing the destination directory. The daemon copies the .ots into that folder using the pattern <namespace>-<name>-<version>-<os>-<cpu>.ots.

POST /servers/import #

{
  "path": "/tmp/server.ots"
}

Response mirrors OpenToolServerDto for the imported build.


Tool API Examples #

GET /tools/list?all=1 #

Returns every tool entry. Omit all or set it to 0 to only receive running tools. Each entry may include serverId and a server object containing namespace, name, and version to identify the source server used when the tool was created. The server object is omitted when the source server is unavailable.

GET /tools/listWithApiKeys?all=0 (requires x-opentool-api-key) #

Send any daemon API key via the header described earlier to receive the same tool list plus the per-tool API keys:

[
  {
    "id": "tool-1",
    "alias": "alpha",
    "host": "127.0.0.1",
    "port": 9001,
    "apiKey": "tool_pk_abc",
    "status": "RUNNING",
    "serverId": "srv-1",
    "server": {
      "namespace": "hub/opentool",
      "name": "demo",
      "version": "1.0.0"
    }
  }
]

GET /tools/events?snapshot=1 (requires x-opentool-api-key) #

Subscribes to daemon-managed tool lifecycle events. This stream is intended for clients that maintain a live set of currently usable tools.

Event semantics:

  • tool.snapshot: initial snapshot for each cached tool when snapshot=1 (default).
  • tool.draining: emitted before a tool is asked to stop or before deletion starts. Remove the tool from your ready set immediately.
  • tool.ready: emitted only after the daemon can successfully call the tool's /version endpoint.
  • tool.unavailable: emitted when the daemon detects a previously running tool has become unreachable.
  • tool.removed: emitted after the daemon removes the tool metadata entry.

Example:

event:ready
data:{"message":"subscribed"}

event:tool.snapshot
data:{"type":"tool.snapshot","reason":"snapshot","tool":{"id":"tool-1","alias":"alpha","host":"127.0.0.1","port":9001,"status":"running"},"occurredAt":"2026-03-07T00:00:00.000Z"}

event:tool.draining
data:{"type":"tool.draining","reason":"stop_requested","tool":{"id":"tool-1","alias":"alpha","host":"127.0.0.1","port":9001,"status":"running"},"occurredAt":"2026-03-07T00:01:00.000Z"}

event:tool.ready
data:{"type":"tool.ready","reason":"started","tool":{"id":"tool-2","alias":"beta","host":"127.0.0.1","port":9002,"status":"running"},"occurredAt":"2026-03-07T00:01:05.000Z"}

POST /tools/create?serverId=srv-1&hostType=local&timeout=20 #

Starts a tool from the selected server. SSE events deliver command output (event:DATA) or errors (event:ERROR). The daemon allocates a new port, API key, and workspace under ~/.opentool/tools/{toolId}. The request only completes successfully after the tool passes the daemon readiness check, and /tools/events emits tool.ready. Optional query parameters:

  • hostType: local, remote, or omitted for any (pass-through to the tool runtime).
  • timeout: number of seconds before the daemon closes the SSE connection even if the tool keeps starting; the process continues in the background.

Optional request body (persisted on the tool and reused on restart):

{
  "args": ["--foo bar", "--baz qux"]
}

args is a string array appended after the Opentoolfile CMD and stored with the tool; /tools/{toolId}/start will reuse them. It must not include --opentoolServerTag / --opentoolServerHost / --opentoolServerPort / --opentoolServerApiKeys, which are injected by the daemon; supplying them returns 400.

POST /tools/{toolId}/start?timeout=20 exposes the same SSE behavior for restarting an existing tool directory. The daemon emits tool.ready only after the restarted tool becomes reachable.

POST /tools/{toolId}/call #

Request body should follow the OpenTool function-call schema:

{
  "id": "call-001",
  "name": "status",
  "arguments": {"depth": 1}
}

Response is a ToolReturn JSON payload produced by the tool process.

POST /tools/{toolId}/streamCall #

Behaves like /call, but emits SSE packets so long-running invocations can stream tokens or intermediate results.

GET /tools/{toolId}/load #

Returns the OpenTool JSON description parsed from the packaged Opentoolfile.json. Use /tools/{toolId}/alias?alias=new-name (POST) to rename a tool entry, /tools/{toolId}/stop to stop the process, and DELETE /tools/{toolId} to remove it entirely. Stopping emits tool.draining before the stop command is sent; deleting emits tool.draining first and tool.removed after the cache entry is deleted.


Client Library #

lib/src/client/client.dart provides a strongly-typed Dart client that wraps the manage/server/tool APIs, handles SSE parsing, and mirrors every endpoint listed above, including /tools/events. Import it in other Dart packages to integrate with the daemon without reimplementing the HTTP/SSE plumbing.


Need more detail? Check lib/src/controller for DTO definitions and lib/src/service for the exact side effects of each endpoint.

1
likes
130
points
113
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A HTTP Daemon Server to manage native OpenTool Servers.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

archive, crypto, dio, hive, json_annotation, logging, opentool_dart, path, shelf, shelf_router, unique_id_dart, uuid

More

Packages that depend on opentool_daemon