@zephyrcloudio/harness-server

Runtime-neutral Fetch HTTP handling for Ze Harness.

createServerHandler exposes injected SessionServices through validated HTTP routes and SSE.

import { createServerHandler } from '@zephyrcloudio/harness-server';

The Server does not choose a storage implementation or import Node.js. Node config discovery, trusted plugin loading, and loopback hosting live in @zephyrcloudio/harness-server-node.

Long-lived event streams stay observable while nothing changes. A caught-up Session stream repeats its session.cursor, a server stream repeats its server.cursor, and a Turn stream sends turn.heartbeat naming the revision it already sent, at least every 15 seconds. All come from an idle re-read of activity, which also recovers a missed wake.

The SSE projections are exported for embedding hosts that serve them outside the Fetch handler: turnEventStream, sessionEventStream, and serverEventStream. The server stream wraps SessionServices.observation.watchServerActivity and projects Turn rows through the same functions as the Session stream, with one running-Turn memory per Session. A Session's first event on a stream is always session.created: a Session row is session.created the first time the stream sees that Session and session.title afterwards (storage stamps Session rows only on creation and title changes), and a Turn row for a Session the stream has not announced yet is preceded by a session.created read from the current Session, stamped one cursor before the Turn's so a resume between the two replays the Turn. A stream resumed after an existing Session was retitled therefore repeats session.created; consumers upsert by session.id.

HarnessServerOptions accepts an optional providerAuthority service to serve model discovery and provider connection management routes over the versioned v1 protocol. The /v1/runtime/models/events and /v1/servers/:server/sessions/:sessionId/models/events routes are single-shot current-revision checks, not long-lived subscriptions: each emits one revision event and closes. Clients re-fetch the model snapshot endpoints for updates.

The portable Streamable HTTP MCP factory in ./mcp-host requires McpHostBackgroundWork. This host port keeps an abort-ignoring fetch and any late response-body cleanup owned after the transport scope closes. Node and Worker packages supply their platform-specific implementations.

MCP catalog pages validate their standard fields and accept additional server metadata. A server may omit resources/list, resources/templates/list, or prompts/list: a method-not-found response to the first page of one of those optional lists is treated as an empty catalog for that list. Other request errors, tools/list failures, and malformed catalog fields still fail discovery.

Dashboard

createServerHandler({ dashboard: { server } }) serves the dependency-free dashboard (src/dashboard.html, inlined at build time by build/dashboard-asset.mjs) at exactly GET /dashboard, before routing and without authentication. The Node daemon and WorkerHarnessHost both serve this one asset. The page is a single self-contained HTML document that does not assume its host:

  • Server identity. The handler replaces %ZE_HARNESS_SERVER% in <meta name="ze-harness-server"> with dashboard.server. A page served without a valid name shows an error instead of guessing.
  • Relative requests. Every call is document-relative (v1/servers/<server>/…, v1/logs), so the page works under any path prefix; at /dashboard they resolve to /v1/….
  • Login only when needed. On load the page lists Sessions with the tab's stored bearer token, or none, and shows the token form only on 401. A host that vouches for its caller never shows it, and the sign-out chip appears only while a token is in use. When storage is unavailable (an opaque-origin frame) the token is kept in memory only.
  • Optional logs. The log panel hides once v1/logs answers 404 or 503.
  • Headers. content-security-policy: default-src 'none'; style-src 'unsafe-inline'; script-src 'sha256-<page script>'; connect-src 'self'; base-uri 'none'; form-action 'none'. The script hash is computed at build time, so an embedder that appends its own script hashes to script-src (CEF does for injected scripts) keeps the page's script running. There is no frame-ancestors or X-Frame-Options, so any parent may frame the page. The page's API calls must be same-origin with it (connect-src 'self'). An embedder serving it from a custom URL scheme (such as tap-harness://localhost) must register that scheme so its pages get a scheme/host/port origin; a page with an opaque origin has no 'self' to match.

Theme

Every color, font, type size, weight, radius, shadow, and spacing value is a --zh-* custom property on :root; the defaults are the built-in dark theme. The page has one color scheme (dark) and takes no scheme parameter: an embedder restyles it, including a light palette and --zh-color-scheme: light, through tokens. An embedding parent window posts:

iframe.contentWindow.postMessage(
  { type: 'ze-harness:theme', tokens: { '--zh-bg': '#ffffff', '--zh-text': '#1f2328' } },
  '*',
);

The page applies only messages from its parent window, and only entries whose key is one of the tokens below and whose value is a string; everything else is ignored. The message carries theme, never data. Post it after the frame's load event; later messages override earlier ones.

Group Tokens
Scheme --zh-color-scheme (dark)
Colors --zh-bg, --zh-panel, --zh-inset, --zh-border, --zh-text, --zh-dim, --zh-faint, --zh-accent, --zh-ok, --zh-warn, --zh-bad, --zh-steer
Fonts --zh-font-sans, --zh-font-mono
Type size --zh-text-10, -10-5, -11, -11-5, -12, -12-5, -13, -14, -15, -16, -17
Type --zh-weight-normal, --zh-weight-medium, --zh-weight-semibold, --zh-leading, --zh-leading-prose, --zh-tracking-label
Radius --zh-radius-2, -3, -4, -5, -6, -8, --zh-radius-full
Shadow --zh-shadow-card, --zh-shadow-focus, --zh-shadow-focus-thin
Spacing --zh-space-1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -12, -14, -16, -18, -20, -24, -28, -32, -44, -80

Numbered type size, radius, and spacing tokens are named for their default pixel value (--zh-text-10-5 is 10.5px). Tinted surfaces (selection, pills, error banners, focus ring) mix the color tokens, so they follow a recolored palette.

Public TypeScript exports

  • AddSessionPluginAdminInput
  • bearerUnauthorizedResponse
  • ClientWorkdirResolver
  • createServerHandler
  • DaemonLogServiceApi
  • DashboardOptions
  • HarnessServer
  • HarnessServerOptions
  • isDashboardRequest
  • makeLazySessionPluginAdmin
  • makeSessionPluginAdmin
  • ProviderModelRefresher
  • publicSessionPluginInventory
  • RefreshSessionPluginAdminInput
  • RuntimeAdmin
  • RuntimeAdminUnavailable
  • serverEventStream
  • ServerEventStreamInput
  • SessionCatalogAdoptionStore
  • sessionEventStream
  • SessionEventStreamInput
  • SessionPluginActivationValue
  • SessionPluginAdmin
  • SessionPluginAdminConflict
  • SessionPluginAdminFailure
  • SessionPluginAdminInvalid
  • SessionPluginAdminStorageFailure
  • SessionPluginAdminUnavailable
  • SessionPluginRevisionValue
  • SessionPluginsStatusValue
  • SessionProjectResolver
  • turnEventStream
  • TurnEventStreamInput
  • verifyBearerRequest