Skip to content

Releases: burrellka/URSA-OSCAR

URSA-OSCAR 1.1.5 — RFC 7591 Dynamic Client Registration on MCP server

Choose a tag to compare

@burrellka burrellka released this 01 Jun 02:03

URSA-OSCAR 1.1.5

MCP spec compliance: RFC 7591 Dynamic Client Registration is now supported.

Fixed in 1.1.5

  • MCP server now accepts non-claude.ai OAuth clients via RFC 7591 DCR (mcp-server/src/ursa_oscar_mcp/auth.py). Prior versions had DCR disabled — the MCP server's only OAuth client was the pre-registered claude.ai one, so KAIROS or any other MCP client following the spec's standard discovery flow got 400 Bad Request at /authorize ("Redirect URI not registered for client") when trying to use its own redirect_uri. The MCP spec requires DCR support for general-purpose servers; URSA now provides it. POST /register per RFC 7591: the caller supplies client_name, redirect_uris, grant_types, response_types, and receives back a fresh client_id + client_secret. Registrations persist to /data/mcp_oauth_clients.json (mode 0600, atomic writes via tmpfile+rename) so they survive container restart. The pre-registered claude.ai client is reconstructed from env vars on every boot and is intentionally excluded from the persisted store (env vars remain the source of truth for it; persisting would risk a stale entry masking an env-var rotation). Startup banner now reports dcr=ENABLED instead of dcr=DISABLED. Five regression tests cover DCR registration with caller-specified redirect_uris, persistence across provider reconstruction, exclusion of the pre-registered client from disk, graceful handling of corrupt JSON stores, and skip-and-continue on individual corrupt entries.

Breaking compose change — MCP /data mount

The MCP container's /data mount must change from :ro to :rw so the DCR client store can be written. Both infra/docker-compose.yml and infra/docker-compose.production.yml have been updated. Operators on a Dockge-managed or hand-edited compose stack must mirror this change in their own compose file before redeploy, otherwise the MCP container will be unable to persist DCR registrations and they will be lost on container restart (the in-memory dict still works for the immediate caller; only persistence is lost).

The trust model is unchanged: the same operator who controls the API and watcher containers (both already :rw) also controls MCP. The /data directory is operator-owned on a private LAN volume.

No data migrations

No schema changes. No env-var changes. The DCR client store is created on first registration; missing file is normal on a fresh install.

Docker images

All four images are available at brain40/ursa-oscar-*:1.1.5 + :latest:

  • brain40/ursa-oscar-api:1.1.5
  • brain40/ursa-oscar-mcp:1.1.5
  • brain40/ursa-oscar-web:1.1.5
  • brain40/ursa-oscar-watcher:1.1.5

Upgrade

  1. Update your compose file: change image: tags to 1.1.5 on all four services. Change the MCP container's /data mount from :ro to :rw.

  2. docker compose pull && docker compose up -d --force-recreate.

  3. Verify by sending a DCR request. The MCP SDK requires the request to include both authorization_code and refresh_token in grant_types (per the MCP connector spec's refresh-token requirement):

    curl -X POST https://your-mcp-host/register \
      -H 'content-type: application/json' \
      -d '{"client_name":"test","redirect_uris":["https://example.test/cb"],"grant_types":["authorization_code","refresh_token"],"response_types":["code"]}'

    You should get a 201 response with a fresh client_id (not the pre-registered claude.ai one) and a client_secret.

    If you forget refresh_token in grant_types, you'll see error: "invalid_client_metadata", error_description: "grant_types must be authorization_code and refresh_token". That comes from the upstream MCP SDK's RegistrationHandler, not from URSA — it's the spec-mandated check. Send both grant types and it works.

Acknowledgments

Bug surfaced by the KAIROS Claude Code dev during a connector integration attempt. The diagnostic walkthrough (URSA's /.well-known advertised endpoints, the specific /authorize 400 error pattern, the recommended RFC 7591 implementation steps) made this fix straightforward to scope.

License

GNU GPL-3.0-or-later, preserving OSCAR's license terms.

Attribution

Built in the spirit and license terms of the OSCAR project. URSA-OSCAR is independent of OSCAR (different codebase, different deployment model) but downstream of OSCAR's file-format work.

URSA-OSCAR 1.1.4 — Local-model malformed-tool-call diagnostic

Choose a tag to compare

@burrellka burrellka released this 24 May 17:43

URSA-OSCAR 1.1.4

Two production-debugged data fixes, one architectural cleanup, and a UX polish for local-model users. The full 1.1.2 → 1.1.4 changeset (a separate 1.1.3 Release was skipped; Docker Hub :1.1.3 images still work for anyone who pulled them; this Release supersedes them).

Fixed in 1.1.4

  • Malformed-tool-call diagnostic for under-capable local models (backend/src/ursa_oscar/api/ai.py). When a local model isn't capable enough to format an OpenAI-style tool call against URSA's full 18-tool surface (Qwen3-4b on CPU is the canonical example), it typically emits {" as text content trying to write a JSON tool-call, then finishes with stop_reason="stop". The chat panel previously rendered the partial JSON literally, leaving the user staring at a confusing single {. The chat handler now detects this shape (text content ≤10 chars starting with {, stop_reason=stop, no tool_calls) and surfaces a friendly diagnostic error with concrete next steps: switch to Claude API, use a larger local model (Qwen3-30b-instruct, Llama-3.3-70b-instruct), or run on GPU instead of CPU. Two regression tests cover the detection firing for the failure shape and not misfiring on legitimate short responses like Yes..

Fixed in 1.1.3

  • Session boundary detection no longer splits on clock-minute boundaries (backend/src/ursa_oscar/analytics/edf_parser.py). The EDF importer grouped files into sessions by hour-and-minute filename prefix. When a session's events file landed at 01:04:53 and the waveforms file landed at 01:05:00 (7 seconds apart but crossing the minute mark), they were treated as two separate sessions: one events-only, one waveforms-only. Mask-on duration inflated by the length of the affected session. Replaced with sliding-window temporal clustering at 30-second tolerance. Operators with affected nights must force re-import to pick up the corrected session boundaries via URSA_OSCAR_WATCH_FORCE_REIMPORT=true and a watcher recreate. Three regression tests cover the minute-boundary case, the 50-second-real-restart case, and the multi-minute-separation case.

  • Thinking-mode model support in the OpenAI-compat adapter (backend/src/ursa_oscar/ai_proxy/providers/openai_compat.py, frontend/src/components/AiChatPanel.tsx). Qwen3 (via LocalAI / Ollama) emits chain-of-thought in delta.reasoning; DeepSeek-R1 uses delta.reasoning_content. Both naming conventions are now read and surfaced as a new reasoning event type. The chat panel renders these as a collapsible "Reasoning" trail above the assistant's content, open by default while in-flight, collapsed once the answer arrives. Reasoning content stays out of the assistant message's content field so follow-up turns don't carry the chain-of-thought back to the LLM. Stream timeout bumped from 120s to 300s on the read path.

  • Settings page image-version chips are now self-introspecting, eliminating the dual-update problem (backend/src/ursa_oscar/api/system.py, mcp-server/src/ursa_oscar_mcp/__main__.py, watcher/src/ursa_oscar_watcher/__main__.py, frontend/vite.config.ts). Previously the chips read from URSA_OSCAR_*_IMAGE_VERSION env vars that the operator had to keep in sync with the image: lines in their compose file. Now each service is the source of truth for its own version: API reads importlib.metadata.version('ursa-oscar-backend'); MCP exposes an unauthenticated /version endpoint the API queries; the watcher writes /data/versions/watcher.txt at startup; the web bundle bakes __URSA_WEB_VERSION__ via Vite at build time. Env-var overrides are retained for testing scenarios but the common case requires no operator coordination. 11 regression tests cover the introspection helpers.

Fixed in 1.1.2

  • Test connection button reflects the saved config, not in-flight edits (frontend/src/pages/SettingsAi.tsx). The button used to enable the moment the operator typed an API key, but the /ai/test endpoint probes the persisted config. Five disabled states now, each with a tooltip naming the missing piece. Local LLM presets skip the key requirement.

  • Gemini provider preset refreshed (backend/src/ursa_oscar/ai_proxy/providers/presets.py). The default model list shipped with gemini-2.0-flash-exp and gemini-1.5-pro (both deprecated by Google as of May 2026) and gemini-1.5-flash (on the deprecation track). Replaced with gemini-3.5-flash. Also rolled OpenRouter's stale google/gemini-2.0-flash to google/gemini-3.5-flash.

README addition

The README now includes a section on the AI value proposition: the MCP-exposed-data-plus-specialized-AI-conversation pattern that motivated URSA in the first place, and the in-app AI assistant as the fallback path for deployments that can't expose a public MCP endpoint. The reusable architectural pattern is linked to the new template repo at https://github.com/burrellka/mcp-server-template.

Re-import note for operators

If your URSA installation has nights where mask-on duration looks inflated (typically 10+ hours when the real value should be 6-7 hours), at least one of those nights hit the minute-boundary bug. The fix corrects future imports automatically; existing nights need a force re-import to rewrite session boundaries.

Easiest path:

  1. In your compose env, set URSA_OSCAR_WATCH_FORCE_REIMPORT=true
  2. docker compose up -d --force-recreate ursa-oscar-watcher
  3. Wait one watcher poll cycle (default 30 seconds). The watcher re-imports every night present on the SD card source.
  4. Set URSA_OSCAR_WATCH_FORCE_REIMPORT=false again (or remove the override) and recreate the watcher.

The re-import is idempotent for nights not affected by the bug; nights with the corrupted session pair get rewritten with the corrected single-session shape.

No data migrations

No schema changes. No env-var changes. Same /data volume layout. Upgrade is docker compose pull && docker compose up -d --force-recreate.

Architecturally locked in 1.0 (carried into 1.1.x)

  • Single-tenant. One operator, one instance.
  • Self-hosted. No cloud sync, no SaaS option.
  • Password authentication only. No SSO / OAuth providers for operator login.
  • No email password recovery. Recovery is filesystem-level (delete auth.json, re-bootstrap).
  • Statistical method declaration in every analytical response.
  • Sample-size discipline as refusals, not warnings (INSUFFICIENT_DATA returns when below the floor).

Docker images

All four images are available at brain40/ursa-oscar-*:1.1.4 + :latest:

  • brain40/ursa-oscar-api:1.1.4
  • brain40/ursa-oscar-mcp:1.1.4
  • brain40/ursa-oscar-web:1.1.4
  • brain40/ursa-oscar-watcher:1.1.4

Upgrade

Operators on 1.0.x, 1.1.0, 1.1.1, 1.1.2, or 1.1.3: bump the four image tags to 1.1.4 and docker compose up -d --force-recreate. No data migrations. After upgrade, follow the Re-import note above if you have affected nights from the session-boundary bug.

Test coverage

  • Backend: 380+ tests pass (including 53/53 AI proxy, 9/9 EDF parser with new regression coverage, 11/11 version-introspection)
  • MCP: 40+ tests pass
  • Watcher: 25+ tests pass
  • Frontend: tsc -b && vite build clean

License

GNU GPL-3.0-or-later, preserving OSCAR's license terms.

Attribution

Built in the spirit and license terms of the OSCAR project. URSA-OSCAR is independent of OSCAR (different codebase, different deployment model) but downstream of OSCAR's file-format work.

URSA-OSCAR 1.1.2 — Settings polish + README AI value-prop

Choose a tag to compare

@burrellka burrellka released this 22 May 16:13

URSA-OSCAR 1.1.2

Settings polish ahead of the community launch.

Fixed in 1.1.2

  • Test connection button reflects the saved config, not in-flight edits (frontend/src/pages/SettingsAi.tsx). The button used to enable the moment the operator typed an API key, but the /ai/test endpoint probes the persisted config. The disagreement produced a confusing 400 ("no provider configured") when the operator clicked Test before clicking Save. Five disabled states now, each with a tooltip that names the missing piece: no provider saved, selected provider not saved, no key saved, in-flight edits diverge from saved state, or ready to fire. Local LLM presets with supports_local_routing=true skip the key requirement (most local servers accept empty auth).

  • Gemini provider preset refreshed (backend/src/ursa_oscar/ai_proxy/providers/presets.py). The default model list shipped with gemini-2.0-flash-exp and gemini-1.5-pro (both deprecated by Google as of May 2026) and gemini-1.5-flash (on the deprecation track). Replaced with gemini-3.5-flash. The freeform model field still accepts any model string for operators who want a specific snapshot. Also rolled OpenRouter's stale google/gemini-2.0-flash default to google/gemini-3.5-flash.

README addition

The README now includes a section on the AI value proposition: the MCP-exposed-data-plus-specialized-AI-conversation pattern that motivated URSA in the first place, and the in-app AI assistant as the fallback path for deployments that can't expose a public MCP endpoint. The reusable architectural pattern is linked to the new template repo at https://github.com/burrellka/mcp-server-template.

No data migrations

No env-var changes. Same /data volume layout. Upgrade is docker compose pull && docker compose up -d --force-recreate.

Architecturally locked in 1.0 (carried into 1.1.x)

  • Single-tenant. One operator, one instance.
  • Self-hosted. No cloud sync, no SaaS option.
  • Password authentication only. No SSO / OAuth providers for operator login.
  • No email password recovery. Recovery is filesystem-level (delete auth.json, re-bootstrap).
  • Statistical method declaration in every analytical response.
  • Sample-size discipline as refusals, not warnings (INSUFFICIENT_DATA returns when below the floor).

Docker images

All four images are available at brain40/ursa-oscar-*:1.1.2 + :latest:

  • brain40/ursa-oscar-api:1.1.2
  • brain40/ursa-oscar-mcp:1.1.2
  • brain40/ursa-oscar-web:1.1.2
  • brain40/ursa-oscar-watcher:1.1.2

Upgrade

Operators on 1.0.x, 1.1.0, or 1.1.1: bump the four image tags to 1.1.2 and docker compose up -d --force-recreate. No data migrations.

Test coverage

  • Backend: 350+ tests pass (including 50/50 AI proxy)
  • MCP: 40+ tests pass
  • Watcher: 25+ tests pass
  • Frontend: tsc -b && vite build clean

License

GNU GPL-3.0-or-later, preserving OSCAR's license terms.

Attribution

Built in the spirit and license terms of the OSCAR project. URSA-OSCAR is independent of OSCAR (different codebase, different deployment model) but downstream of OSCAR's file-format work.

URSA-OSCAR 1.1.1 — Auth fix for in-app chat

Choose a tag to compare

@burrellka burrellka released this 20 May 21:43

URSA-OSCAR 1.1.1

A bugfix-only release ahead of the community launch.

Why 1.1.1 exists

While preparing the 1.1.0 community launch, a launch-day test of the in-app AI chat surfaced two latent auth bugs that would have been visible on the very first user query. 1.1.1 fixes both before the public Release.

If you already pulled :1.1.0, the right move is to bump to :1.1.1 directly. There is no :1.1.0 Release announcement — the tag exists but the Release object is being skipped.

Fixed in 1.1.1

  • In-app AI chat tool calls now authenticate (backend/src/ursa_oscar/ai_proxy/tools.py, backend/src/ursa_oscar/api/ai.py). Phase 6.4 added _AUTH_REQUIRED to every API router but the AI proxy's internal loopback to the API didn't forward the operator's JWT. Every tool call from the chat panel returned {"detail":"Not authenticated."}. The chat endpoint now reads the JWT from either the ursa_oscar_session cookie (browser session) or the Authorization: Bearer header (MCP/CLI client), constructs a Bearer header, and passes it via the new auth_header kwarg on execute_tool. The Test connection button in Settings worked because it calls the LLM provider directly without an URSA loopback — only tool-call queries hit the broken path. New regression test test_chat_forwards_session_cookie_as_bearer_to_loopback covers both auth paths.
  • generate_report MCP tool now authenticates (mcp-server/src/ursa_oscar_mcp/tools/generate_report.py). Same Phase 6.4 oversight in a different code path: this tool used raw httpx.Client() instead of the auth-attaching get_client() helper in client.py. Reports requested from a claude.ai Custom Connector returned 401. Now uses get_client() so the resolved service token is attached.
  • No data migrations. No env-var changes. Same /data volume layout.

1.1.0 content (carried into 1.1.1)

1.1.1 includes everything that was scheduled for 1.1.0:

  • In-app Help system — 37 topics across 7 sections covering installation, every feature, every statistical method, architecture, deployment, and troubleshooting. The Methodology section is verbatim with the PDF report's methodology disclosure (single source of truth, no drift).
  • AI assistant Help integration — the AI assistant queries Help content via the new get_help_topic MCP tool, grounding responses in URSA-OSCAR-specific documentation rather than general LLM knowledge.
  • About modal — version info, license, OSCAR attribution, GitHub link, accessible from the sidebar footer.
  • No-drift regression test — every tool / endpoint reference in the Help markdown is verified against the actual codebase. Catches stale references on every CI run.

Architecturally locked in 1.0 (carried into 1.1.x)

These are the architectural decisions that 1.0 committed to. They aren't likely to change in any near-term release:

  • Single-tenant. One operator, one instance.
  • Self-hosted. No cloud sync, no SaaS option.
  • Password authentication only. No SSO / OAuth providers for operator login.
  • No email password recovery. Recovery is filesystem-level (delete auth.json, re-bootstrap).
  • Statistical method declaration in every analytical response.
  • Sample-size discipline as refusals, not warnings (INSUFFICIENT_DATA returns when below the floor).

If any of these don't fit your needs, URSA-OSCAR may not be the right tool. See frontend/src/help/content/arch-single-tenant.md for the rationale.

Installation

See the README Quick Start or the in-app Help → Getting Started topics:

Upgrade from earlier versions

Operators on 0.12.x: see Docs/35-migration-0.12-to-0.13.md.

Operators already on 1.0.x or 1.1.0: just bump the four image tags to 1.1.1 and docker compose up -d --force-recreate. No data migrations, no env-var changes.

Docker images

All four images are available at brain40/ursa-oscar-*:1.1.1 + :latest:

  • brain40/ursa-oscar-api:1.1.1
  • brain40/ursa-oscar-mcp:1.1.1
  • brain40/ursa-oscar-web:1.1.1
  • brain40/ursa-oscar-watcher:1.1.1

Test coverage

  • Backend: 350+ tests pass (auth, AI proxy, ingestion, analytics, reports, help-no-drift)
  • MCP: 40+ tests pass (auth provider + tool layer + token resolution)
  • Watcher: 25+ tests pass (loop, API client, token resolution)
  • Frontend: tsc -b && vite build clean

What's next

URSA-OSCAR 1.1.x is feature-stable. Future releases respond to community demand. Items currently under consideration:

  • Multi-profile support (would require re-architecting the single-tenant boundary; non-trivial)
  • Richer authentication models (TOTP, OAuth providers for operator login)
  • Token usage / cost surfacing in the AI chat panel
  • Mobile-narrow UI polish
  • Conversation export (PDF / Markdown)

If any of these matter to you, file a GitHub Issue. Real user feedback shapes priorities; vague intent doesn't.

License

GNU GPL-3.0-or-later, preserving OSCAR's license terms.

Attribution

Built in the spirit and license terms of the OSCAR project. URSA-OSCAR is independent of OSCAR — different codebase, different deployment model — but downstream of OSCAR's file-format work. Without years of OSCAR-community reverse-engineering on ResMed's SD-card format, URSA-OSCAR wouldn't exist.