Skip to content

About

What's not here is all in next ...

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Repository files navigation

Next Chats

中文版 README(简体中文)

A B/S multimodal AI chat platform based on the DeepSeek Harness principles: LLM + MCP (Model Context Protocol) + Skill plugin orchestration, with a Cordis-driven, Three.js-powered 3D frontend.

Reference paper: A Programming Paradigm for Spatiotemporal Composability — everything is a plugin, driven by Cordis.

Multilingual UI: English (default) and 简体中文 — switch from the top bar (🌐 EN / 中文); your choice is remembered in localStorage.


Architecture & Flows

Data Model (ER)

erDiagram
    APP_USER {
        Guid id PK
        string auth_type "default=password account; acs/ucs…=internal auth"
        string username "unique together with auth_type"
        string display_name
        string email
        string password_hash
        string password_salt
        string status
        datetime created_at
        datetime last_login_at
    }
    APP_ROLE {
        Guid id PK
        string name
        string code UK
        string description
        bool is_system
        datetime created_at
    }
    CHAT_SESSION {
        Guid id PK
        Guid user_id FK
        string title
        string status
        Guid llm_provider_id
        string context_json
        datetime created_at
        datetime updated_at
        datetime last_message_at
    }
    CHAT_MESSAGE {
        Guid id PK
        Guid session_id FK
        Guid user_id FK
        string role
        text content
        text reasoning
        string tool_calls_json
        string status
        string model
        int prompt_tokens
        int completion_tokens
        string trace_id
        string client_message_id UK
        datetime created_at
    }
    LLM_PROVIDER {
        Guid id PK
        string name
        string kind
        string base_url
        string api_key_encrypted
        int priority
        bool enabled
        bool is_healthy
        datetime created_at
        datetime updated_at
    }
    LLM_MODEL {
        Guid id PK
        Guid provider_id FK
        string name
        bool enabled
        bool is_vision
        int context_window
        decimal price_in_per_1k
        decimal price_out_per_1k
        int priority
        datetime created_at
    }
    INTERNAL_AUTH_PROVIDER {
        Guid id PK
        string name "acs / ucs…, unique"
        string api "auth center URL"
        string http_method
        string request_format "body(application/json)"
        string username_field
        string password_field
        bool enabled
        int timeout_seconds
        datetime created_at
    }
    INTERNAL_AUTH_SUCCESS_RULE {
        Guid id PK
        Guid provider_id FK
        string field "JSON path, e.g. sessionID"
        string operator "NotEmpty / Equals"
        string expected_value
    }
    MCP_SERVER {
        Guid id PK
        string name
        string transport
        string endpoint
        string headers_json
        string stdio_command
        bool enabled
        bool is_vision
        int timeout_seconds
        string description
        datetime created_at
    }
    MCP_CATALOG_ITEM {
        Guid id PK
        Guid mcp_server_id FK
        string kind
        string name
        string description
        string schema_json
        bool enabled
    }
    PROMPT {
        Guid id PK
        string name
        string description
        text content
        bool enabled
        int version
        datetime created_at
    }
    SKILL {
        Guid id PK
        string name
        string meta_tool_name
        text instruction
        bool enabled
        string model_override
        int max_nested_steps
    }
    TOOL_APPROVAL {
        Guid id PK
        string trace_id
        Guid user_id FK
        Guid session_id FK
        string mcp_server_name
        string tool_name
        string arguments_json
        string status
        datetime created_at
        datetime decided_at
        datetime expires_at
    }
    AUDIT_LOG {
        Guid id PK
        string trace_id
        Guid user_id FK
        string category
        string action
        string target
        string detail_json
        string ip
        bool is_suspicious
        datetime created_at
    }
    USER_SETTING {
        Guid id PK
        Guid user_id FK
        string key
        string value_json
    }
    TOKEN_USAGE_RECORD {
        Guid id PK
        string trace_id UK
        Guid user_id FK
        Guid session_id FK
        string provider_name
        string model
        int total_tokens
        decimal cost
        int ttft_ms
        datetime created_at
    }

    APP_USER ||--o{ CHAT_SESSION : "owns"
    APP_USER ||--o{ CHAT_MESSAGE : "sends"
    APP_USER }o--o{ APP_ROLE : "member_of (user_roles)"
    APP_USER ||--o{ USER_SETTING : "has"
    APP_ROLE }o--o{ MCP_SERVER : "can_use (role_mcp_servers)"
    APP_ROLE }o--o{ PROMPT : "can_use (role_prompts)"
    APP_ROLE }o--o{ SKILL : "can_use (role_skills)"
    APP_ROLE }o--o{ LLM_MODEL : "can_use (role_model_bindings)"
    APP_ROLE }o--o{ INTERNAL_AUTH_PROVIDER : "default roles (internal_auth_provider_roles)"
    LLM_PROVIDER ||--o{ LLM_MODEL : "models"
    INTERNAL_AUTH_PROVIDER ||--o{ INTERNAL_AUTH_SUCCESS_RULE : "success rules"
    MCP_SERVER ||--o{ MCP_CATALOG_ITEM : "catalog"
    CHAT_SESSION ||--o{ CHAT_MESSAGE : "contains"
    CHAT_SESSION }o--o| LLM_PROVIDER : "uses"
    TOOL_APPROVAL }o--|| APP_USER : "requested_by"
    TOOL_APPROVAL }o--|| CHAT_SESSION : "in"
    AUDIT_LOG }o--o| APP_USER : "actor"
    TOKEN_USAGE_RECORD }o--o| APP_USER : "accrued_by"
Loading

System Architecture

flowchart LR
    subgraph Web["Web (Vue 3 + Cordis)"]
        UI[Chat UI / 3D Theme] --> K[Kernel Plugins]
        K -->|SSE / REST| HTTP[Vite proxy → /api]
    end

    subgraph Api["NextChats.Api (ASP.NET Core)"]
        Ctl[Controllers / RBAC / Audit] --> Mod[Error Localization Middleware]
        Ctl --> Orch{{ChatOrchestrator}}
        Ctl --> Adm[Admin: Providers / MCP / Prompts / Skills / RBAC / Internal Auth / Approvals]
        Ctl --> Auth[Auth: default password / internal auth]
        Auth -->|HTTP| IA[(Internal Auth Center acs / ucs…)]
    end

    Orch --> Loop{{AgentLoopEngine}}
    Loop -->|ReAct think→act→observe| Router[LLM Router]
    Router --> OpenAI[OpenAI-Compatible Client]
    Router --> Mock[Mock Client: localized output]
    Orch --> Driver[MCP Driver]
    Driver --> MCP1[(MCP Server: Demo 5300)]
    Driver --> MCP2[(MCP Server: more…)]
    Orch --> Skills[Skill Execution Engine]
    Orch --> Policy[Policy Engine / Approval Coordinator]

    Api --> DB[(SQLite: config / chat / audit)]
    UI --> V3[Three.js scenes]

    style Orch fill:#e3f2fd
    style Loop fill:#fff3e0
Loading

Chat (ReAct) Flow — SSE streaming

sequenceDiagram
    participant U as User (Browser)
    participant K as Cordis Kernel
    participant C as ChatController
    participant O as ChatOrchestrator
    participant L as AgentLoopEngine
    participant LLM as LLM Client
    participant M as MCP Driver / Skills

    U->>K: type message (+ optional images)
    K->>C: POST /api/chat/stream (SSE)
    C->>O: ChatStreamRequest (lang, images)
    O-->>C: SESSION_NOT_FOUND? / INPUT_FLAGGED? (error event)
    O->>L: AgentRunRequest (tools, context window)
    loop ReAct round
        L->>LLM: stream prompt + tools
        LLM-->>L: reasoning / text / tool calls chunks
        L-->>U: thinking_start · thinking_delta · text_delta (SSE)
        alt tool call
            L->>Policy: evaluate (allow/deny/approval)
            Policy-->>U: approval_updated (pending) → user approves
            L->>M: execute tool (approval / args)
            M-->>L: tool result
            L-->>U: tool_start · tool_result (SSE)
        end
    end
    L-->>U: done (usage: tokens / ttft / cost)
    O->>DB: persist assistant message + usage + audit (trace_id)
    O-->>C: SSE end event
    C-->>K: event stream → UI renders
Loading

Image / Vision Flow (image_source standard base64)

sequenceDiagram
    participant U as User (Browser)
    participant B as ChatInputBar
    participant C as ChatController
    participant O as ChatOrchestrator
    participant V as Vision MCP Tool (image_source)
    participant LLM as LLM (Mock / real)
    participant P as Provider · MCP Vision Flags

    U->>C: GET /api/chat/vision-config
    C->>P: provider.IsVision ∨ bound MCP.IsVision
    P-->>U: supported: true → show upload button + Ctrl+V

    U->>B: paste (Ctrl+V) or upload N images
    B->>B: validate type/size → dataURL → base64
    B->>C: POST /api/chat/stream { message, images[] }
    C->>O: ChatStreamRequest.Images (standard base64)
    loop each image (multi-image recognized one by one)
        O->>V: call vision tool { image_source: base64 }
        V-->>O: recognition text (per image)
    end
    O->>LLM: system + history + [vision texts] + user(message + <image_source> blocks)
    LLM-->>U: streamed answer (SSE) using the recognized text
Loading

Tech Stack

Layer Technology
Backend .NET 10 · ASP.NET Core · EF Core (SQLite now → MySQL 8 ready)
MCP ModelContextProtocol SDK 2.2.0 (latest stable) · Streamable HTTP transport · STDIO extensible
Frontend Vue 3 · TypeScript · Vite · Element Plus · Three.js · Cordis ^3.18.1 (plugin kernel)
Security JWT dual-token (30-min access + rotating refresh, revoked on disable/logout) · PBKDF2-SHA256(210k) password hashing · AES-256-GCM secret encryption · sanitized audit logs
i18n vue-i18n 10 (en / zh-CN, English default) · X-Lang error localization on the API

Repository Layout

next-chats/
├── src/
│   ├── NextChats.Core/           # Domain model, orchestration, engines, drivers (no ASP.NET dependency)
│   ├── NextChats.Infrastructure/ # EF Core data, caching, security services (swappable)
│   └── NextChats.Api/            # Web API (SSE streaming, admin, RBAC, audit, metrics, i18n)
├── samples/McpDemoServer/        # Demo MCP Server (Streamable HTTP)
├── e2e/                          # Internal-auth E2E (mock auth center + PowerShell suite)
└── web/                          # Vue 3 + Cordis frontend
    ├── src/i18n/                 # en / zh dictionaries (per-domain locale files)
    └── scripts/smoke*.mjs        # End-to-end smoke scripts (Node fetch streaming)

Quick Start

# 1. Start the demo MCP Server (port 5300)
dotnet run --project samples/McpDemoServer -c Debug

# 2. Start the API (port 5210; DB is auto-created and seeded on first boot)
dotnet run --project src/NextChats.Api -c Debug

# 3. Start the frontend (Vite proxies /api → 5210)
cd web && npm install && npm run dev
# Open http://localhost:5173  (seeded admin: admin / admin123)

Running the built DLL and choosing the port

Running the published assembly directly (no IIS, no dotnet run) means the listening address comes from --urls:

dotnet NextChats.Api.dll --urls "http://*:6500"
Port source Example Priority
Command line --urls dotnet NextChats.Api.dll --urls "http://*:6500" highest
ASPNETCORE_URLS env var $env:ASPNETCORE_URLS="http://*:6500" middle
launchSettings.json → applicationUrl only applies to dotnet run (this repo: 53803 / 53805) —
appsettings.json → root "Urls" or Kestrel:Endpoints persistent default low
Kestrel default http://localhost:5000 lowest

localhost binds the loopback adapter only. --urls "http://localhost:6500" listens on 127.0.0.1 / ::1, so http://<hostname>:6500/ (which resolves to a NIC address, e.g. 10.0.5.179) is answered with net::ERR_CONNECTION_REFUSED. The SPA shell may still render from browser cache and then fail on uncached assets (Unable to preload CSS for /assets/...). Bind all interfaces instead:

dotnet NextChats.Api.dll --urls "http://*:6500"        # dual-stack: IPv6 + IPv4
dotnet NextChats.Api.dll --urls "http://0.0.0.0:6500"  # IPv4 only
dotnet NextChats.Api.dll --urls "http://[::]:6500"     # IPv6 dual-stack

Verify the listener is 0.0.0.0 / :: (not 127.0.0.1 / ::1) and that the hostname answers:

Get-NetTCPConnection -LocalPort 6500 | Select-Object LocalAddress, State
curl.exe -s -o NUL -w "%{http_code}" "http://<hostname>:6500/"

Run the DLL from its own directory (appsettings.json and the SQLite path data/nextchats.db are relative), and only one process can own a port — stop the IIS site first if it is still bound to the same port. IIS binds all addresses by default, which is exactly why the same hostname URL works under IIS but fails with --urls "http://localhost:6500".

Docker Deployment

The image bundles the Vue frontend and the .NET API: the API serves the SPA itself, so one container = the whole product (UI + API on port 8080). State lives in named volumes (SQLite DB, chat attachments, logs).

# 1. Create .env with two REQUIRED secrets (compose refuses to start without them)
cp .env.example .env
#    edit .env, e.g.:  NEXTCHATS_JWT_KEY=$(openssl rand -base64 48)
#                      NEXTCHATS_ENCRYPTION_KEY=$(openssl rand -base64 32)

# 2. Build & run
docker compose up --build -d

# 3. Verify, then open http://localhost:8080  (seeded admin: admin / admin123 — change it right away)
curl http://localhost:8080/health
Item Details
Port NEXTCHATS_PORT (host, default 8080) → container 8080
Secrets NEXTCHATS_JWT_KEY (signs login tokens) and NEXTCHATS_ENCRYPTION_KEY (encrypts stored LLM/MCP keys). Keep the encryption key stable — after changing it, stored keys can no longer be decrypted
Data (volumes) next-chats-data → /app/data (SQLite, back this up), next-chats-uploads → /app/uploads, next-chats-logs → /app/logs
Config Any appsettings.json key can be overridden by an environment variable, with __ as the level separator, e.g. Policy__MaxReActSteps=30
Logs docker compose logs -f next-chats (also written to the logs volume)
Health The image has a built-in HEALTHCHECK on /health
Runs as non-root user ($APP_UID)

Optional — demo MCP server (Streamable HTTP, handy for a first try):

docker compose --profile demo up -d
# Admin UI → MCP servers → add  http://mcp-demo:5300/mcp

Optional — agent workspaces on the host: uncomment the /workspaces volume in docker-compose.yml, then create a workspace in the admin UI whose root path is /workspaces/<name>.

Upgrade / backup:

git pull && docker compose up --build -d      # named volumes are kept
# backup the SQLite volume (the volume name is prefixed with the compose project name)
docker run --rm -v next-chats_next-chats-data:/d -v "$PWD":/b busybox tar czf /b/nextchats-data.tgz -C /d .

The compose file does not terminate TLS. For public access put a reverse proxy in front (allow PUT / DELETE, forward Authorization, and set proxy_buffering off for SSE).

Deployment notes (IIS / reverse proxy)

Symptom: Admin-side PUT / DELETE calls (enable/disable, edit, delete MCP servers, ...) all return 405 Method Not Allowed, while GET / POST work.

Cause: IIS's WebDAV module intercepts the PUT / DELETE verbs and answers 405 itself, so the request never reaches the app (an app response would be 401 / 404).

Fix (either):

  1. Use the shipped src/NextChats.Api/web.config (dotnet publish copies it into the publish output); it removes the WebDAVModule module and the WebDAV handler. For an already deployed site, replace the published web.config with this template (keep processPath / arguments) and recycle the app pool — no code change needed.
  2. Or uninstall / disable WebDAV on the server: IIS Manager → server level → Modules → remove WebDAVModule; Handler Mappings → remove WebDAV.

Self-check: an unauthenticated PUT to any admin endpoint should return 401; 405 means WebDAV (or a reverse proxy that does not allow the verb) is still intercepting it.

If a reverse proxy such as nginx sits in front, allow PUT / DELETE (and forward the Authorization header and set proxy_buffering off for SSE as needed).

Browser automation (Playwright MCP): the MCP server spawns a headed browser in the session of its own process. Run the API standalone (dotnet NextChats.Api.dll ...) from the logged-on user's desktop session so the window is visible and login-protected sites can be signed in manually. Under IIS (w3wp) the API lives in Session 0, whose GUI is isolated from the desktop — the browser still works, but its window is never visible to the user.

When any browser tool is available, the server injects a built-in browser workflow guide into the system prompt: it opens the URL immediately (no cookies/tokens requested first), keeps the headed browser open, and when a page hits a login/SSO wall it tells the user to sign in in the already-open window and keeps polling (browser_wait_for + re-check) for up to 10 minutes in the same turn, then continues the extraction automatically — the user does not have to repeat the request. This guide is injected regardless of the selected prompt (even with "no prompt").

Core Design

Authentication (default accounts + internal auth)

  • default(system accounts): local password accounts (PBKDF2-SHA256), created by an admin in User Management; the login page defaults to default.
  • Internal auth (acs / ucs…): admins maintain auth-center configs in Internal Auth Management — API URL, HTTP method, the request-body field names for the username/password (body(application/json)), success-response rules (one or more fields that must be not empty or equal a fixed value, dotted paths such as data.sessionID supported), and the category's default roles (multi-select).
  • The login page shows a sign-in-method radio group (default + enabled auth centers, from GET /api/auth/providers); choosing an internal method makes the backend validate the credentials against the configured auth center.
  • After a successful validation the user is auto-provisioned or reused: internal users are unique by (AuthType, Username) (a default and an acs account may share a username); when the user already exists their roles are read directly, otherwise a user is created (no password, displayName = username, status Active, bound to the provider's default roles).
  • Internal-auth users share the same JWT mechanism as default accounts: chat / catalog / permissions behave identically afterwards (role → model/MCP/Prompt/Skill bindings decide the visible scope).
  • Dual-token session (double-token): login issues a short-lived access token (30 min) plus a persisted refresh token (7 days, stored as SHA-256 hash). When the access token expires, the frontend silently exchanges the refresh token (POST /api/auth/refresh) and replays the request — active users are never logged out mid-work, idle sessions expire after 7 days. Every refresh rotates the token (the old one is revoked and recorded as replaced, so replays are rejected). Re-login, logout, and user disable revoke that user's refresh tokens immediately (a disabled account can no longer renew, and its access token dies within 30 min).
sequenceDiagram
    participant U as User (Browser)
    participant C as AuthController
    participant P as InternalAuthProvider config
    participant A as Auth Center (external)
    participant DB as Users table

    U->>C: POST /api/auth/login { authType: "acs", username, password }
    C->>P: load provider (api / fields / success rules / default roles)
    C->>A: HTTP POST api { username_field: …, password_field: … }
    A-->>C: response JSON
    C->>C: evaluate success rules (field not empty / equals value)
    alt rule satisfied
        C->>DB: find user by (AuthType=acs, Username)
        alt not exists
            C->>DB: auto-create (no password, default roles)
        end
        C-->>U: JWT (same mechanism as default accounts) + user info
    else rule failed / auth center error
        C-->>U: 401 (AUTH_INVALID_CREDENTIALS / AUTH_PROVIDER_ERROR)
    end
Loading

Orchestration (Core)

  • Effective tool intersection: role binding ∩ user selection ∩ MCP global enabled per user, with data isolation.
  • Active Skill matching: Skills are exposed to the model as meta tools (skill_<slug>); instructions are lazy-loaded to avoid token explosion.
  • Prompt building: a template engine ({{var}} / #if / #each / #section) renders system prompts.
  • ReAct loop (AgentLoopEngine): producer–consumer Channel architecture decouples LLM streaming from the main loop; safe interruption.
  • Workspace coding sessions — completion standard + AGENTS.md: when a session is bound to a workspace, the system prompt gets two extra blocks.
    • Completion standard (only at WorkspaceWrite level or above): after changing files the model must build/test via ws_exec, fix failures and re-run, never weaken tests to make them pass, and end with a short "Verification" section listing what ran, the result, and what could not be verified. Read-only sessions don't get it (they cannot run anything).

    • Project guide: the workspace root's AGENTS.md (if present) is injected as-is, so each workspace declares its own build/test/verification commands, e.g.

      # AGENTS.md
      - build: dotnet build -c Release
      - test: dotnet test
      - frontend: cd web && npm run build

      Capped at 12,000 characters (longer files are truncated); read through the workspace path sandbox (a symlink/junction pointing outside the root is ignored at levels below FullAccess); a missing, empty or unreadable file is silently skipped. The guide cannot override the permission level, safety rules or approval policy.

    • This is prompt-level guidance, not enforcement: whether the model verifies still depends on the model. An engine-side "don't finish before verifying" gate is a possible later step. Only normal workspace sessions are affected (team mode is unchanged).

Team collaboration (TeamOrchestrator)

  • Execution model: each team message runs 「responsible engineer drafts a solution → assistants give independent suggestions (parallel or sequential, isolated) → responsible evaluates and produces the next-round plan (early stop on consensus) → final wrap-up」, up to maxRounds.
  • SSE orchestration: a single Channel decouples production/consumption; events carry round/phase/engineerId: team_start → team_round → team_text (open segment) → team_think_delta (reasoning stream) → team_tool_start/team_tool_result (tool calls) → team_delta (text stream) → team_end → end. Interleaved parallel suggestions are routed to the right collapsible segment (Round / participant double-folding) by round+phase+engineer.
  • Tool loop (same lever as normal chat): engineer calls expose the same tool set as normal chat (settings-enabled MCP/skills ∩ role binding ∩ built-ins http_fetch/mcp_* ∩ session workspace ws_*). Models may take multiple decision steps: thinking streams live → tool calls execute directly (no approval in team mode, like Autonomous) → results feed back into messages → until direct text output. Tool cards reuse the normal-chat ToolCard component and the same toolTrace persistence shape.
  • Tool-loop guard: at most 8 tool decision steps per engineer call; if a model keeps calling tools without ever emitting text (occasional with deep-reasoning models), one final tool-less call is forced so the answer is produced from the tool results — avoiding “empty solution”. Thinking accumulates across steps; text streams live.
  • Stop on consensus: after consensus is detected in an eval round the loop converges early (no further rounds). Detection is dual-channel: the explicit [CONSENSUS] marker, or natural-language consensus phrasing in the output tail ("no remaining dispute", "达成一致"…); only the tail is checked to avoid false positives from “partly agreed but still disputed”.
  • Failure tolerance: solution/suggestion/eval/final calls retry once on transient errors or empty replies (tokens/cost from both attempts are counted); only then a TEAM_*_FAIL is recorded, with the reason persisted as placeholder content, and the session stays usable.
  • Config semantics: disabling team mode allows an empty/missing engineer list (config is kept for reuse and completeness checks are skipped); strict checks (1 responsible + ≥1 assistant, unique names, model availability/role binding) apply only when enabling.
  • Token / timing semantics: TTFT = real first-token latency per segment (first reasoning/text delta); total time = wall-clock sum of segments; ReasoningTokens is a separate field (shown in the usage panel) and included in cost (output price). RoundsJson persists text/thinking/tools per segment for faithful replay rendering.
  • Engineer model settings: reasoning enabled (ThinkingEnabled/EnableReasoning, effort Medium); reasoning deltas stream as team_think_delta and persist as collapsible blocks.

Server-side Unified Configuration (Admin)

  • LLM providers (enable flag / model / endpoint / encrypted api-key / temperature / context-window)
  • MCP servers (Name / Endpoint / Headers JSON encrypted; "Fetch" auto-discovers tools/prompts/resources with schemas; per-item disable)
  • Multiple Prompts & Skills (pluggable, lazy-load)
  • RBAC: users / roles; role ↔ MCP / Prompt / Skill / LLM model bindings —— bound models are the only models the role's users can see/select, and direct API calls with an unauthorized model id are rejected server-side (MODEL_NOT_AUTHORIZED)
  • Internal Auth: auth-center configs (acs / ucs…) with success-response rules and default roles (see Authentication above)

Engines (Core)

  • LLM Router: multi-vendor priority + round-robin + failover (mark-unhealthy circuit breaking); OpenAI-compatible & Mock clients.
  • MCP driver: strictly follows the latest MCP spec (see MCP 2026-07 revision); connection pooling; call retry with backoff. MCP errors go into the loop — tool errors are fed back to the model as tool results for retry/explanation; sessions never break because of an MCP error.
  • Policy engine: Allow → Deny → RequireApproval with dangerous-operation heuristics (delete_all etc.); approvals support pending/approved/rejected/expired.
  • Context manager: token estimate → compress (LLM summary) → truncate; never exceeds the model length limit and never interrupts the session.

Observability & Cost

  • trace_id (trc_...) flows through Orchestration / LLM / MCP / Audit; token accounting in/out; metrics: TTFT, tokens, cost, tool latency, approval count (/api/admin/metrics/usage).
  • Idempotent writes: unique (UserId, ClientMessageId) key; key chat logs persist across restarts.

Logging, three channels

  • To the user: friendly message + error code (no stack / endpoint / header)
  • To the model: tool errors are re-fed as tool results (retryable, explainable)
  • To the logs: full context (sanitized)

Internationalization

  • Frontend: vue-i18n with per-domain locale files under web/src/i18n/locales/{en,zh}/; English is the default locale.
  • Backend: every user-visible message lives in one dictionary (src/NextChats.Core/Localization/Texts.cs, en/zh) — no hard-coded UI text in code. The API localizes errors via the X-Lang request header (or Accept-Language), so non-browser clients get the same language, e.g. curl -H "X-Lang: en"; seeded content (roles, prompts, skills, demo provider) is English.
  • README: this English file and README.zh.md are linked at the top.

Smoke Verification (web/scripts/ + e2e/)

node scripts/smoke.mjs          # login → create session → SSE streaming chat → persistence → metrics
node scripts/smoke-tools.mjs    # ReAct tool call (tool:echo) → dangerous approval flow (danger:delete_all)

# Internal-auth E2E (mock auth center + PowerShell suite, see e2e/README.md)
node e2e/mock-auth.js           # start mock auth center (127.0.0.1:53131)
powershell -File e2e/run-internal-auth-e2e.ps1   # assert CRUD / providers / auto-provision / uniqueness / role-based catalog

MCP OAuth 2.1 (remote MCP servers)

Remote MCP servers protected by OAuth are supported in addition to static-header HTTP servers (e.g. Coralogix https://api.ap2.coralogix.com/mgmt/api/v1/mcp):

  1. Admin → MCP Servers → create/edit an HTTP server and set Auth mode = OAuth 2.1 (browser authorization).
  2. Click Authorize: the backend discovers the protected-resource metadata (RFC 9728) and the authorization-server metadata (RFC 8414), dynamically registers a client (RFC 7591) when a registration_endpoint exists, then returns an authorization URL carrying PKCE (S256) and a state value; NextChats opens it in the browser.
  3. Sign in to the provider and click Authorize; the provider redirects to /api/admin/mcp-servers/{id}/oauth/callback, where the authorization code is exchanged for tokens (AES-GCM encrypted in SQLite) and the page reports success.
  4. McpDriver injects Authorization: Bearer <access_token> automatically, refreshes it with the refresh_token when it expires (or when starting a tool call near expiry), and the Revoke button clears the stored tokens.

The callback route is anonymous (the browser follows a redirect) but validated by the OAuth state (15-minute TTL); token/config values are never returned by the API — only masked authorization status.

Redirect URI rule: for http requests the callback falls back to http://localhost:{port} (RFC 8252 native-client loopback), because providers such as Coralogix reject non-loopback http redirect URIs during dynamic registration (invalid_redirect_uri). The browser therefore must run on the same machine as NextChats when authorizing; https:// hosts keep the request host.

Coralogix data region: the authorization page's “Your data region” is decided by the MCP endpoint host. Use the host matching your Coralogix data region:

Region MCP endpoint
EU1 https://api.eu1.coralogix.com/mgmt/api/v1/mcp
EU2 https://api.eu2.coralogix.com/mgmt/api/v1/mcp
AP1 https://api.ap1.coralogix.com/mgmt/api/v1/mcp
AP2 https://api.ap2.coralogix.com/mgmt/api/v1/mcp
AP3 https://api.ap3.coralogix.com/mgmt/api/v1/mcp
US1 https://api.us1.coralogix.com/mgmt/api/v1/mcp
US2 https://api.us2.coralogix.com/mgmt/api/v1/mcp

(api.coralogix.com currently resolves to EU1.) The MCP server dialog shows a Coralogix region selector that rewrites the endpoint host; changing the endpoint clears the previous OAuth registration/tokens, so authorize again.

MCP References

  • What's new in MCP (2026-07-28)
  • ModelContextProtocol C# SDK 2.2.0: McpClient.CreateAsync + HttpClientTransport (Streamable HTTP) / StdioClientTransport

License

This project is licensed under the Apache License 2.0.

About

What's not here is all in next ...

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages