ThinkWatch

ThinkWatchProject活跃维护

自托管的 AI API 与 MCP 网关,给企业统一管控大模型调用:SSO/RBAC、按用户身份鉴权、PII 脱敏、工具调用防护、限流预算与成本审计。

Self-hosted AI API and MCP gateway for organizations: SSO and RBAC, per-user identity for MCP tool calls, PII redaction and tool-call guards, rate limits, budgets, cost accounting and audit logs.

v3.11.0发布于 2026-11-03 06:55 UTC817star +3 / 7 天23fork同步于 2026-11-10 05:58 UTC

怎么上手

由 README 提炼,执行前请确认
make infra
make dev-secrets
make dev-backend
硬件要求按 README 说明

README 未说明显存或 GPU 要求;需 PostgreSQL、Redis,审计依赖 ClickHouse,支持 Docker Compose 与 Kubernetes 部署

有 Docker 镜像
下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

面向组织的自托管 AI API 与 MCP 网关,统一鉴权、限流、脱敏与审计

自托管的 AI API 与 MCP 网关,给企业统一管控大模型调用:SSO/RBAC、按用户身份鉴权、PII 脱敏、工具调用防护、限流预算与成本审计。

核心能力

  • 以真实用户身份执行 MCP 工具调用,令牌加密存储
  • 用占位符替换请求中的密钥与 PII 并在回答中还原
  • 支持任意 OIDC 登录,内置五种角色与自定义角色
  • tw- 虚拟密钥可限定 AI、MCP 或两者,仅存哈希
  • 滑动窗口限流与日周月预算,按模型用户成本核算

容易踩的坑

需先启动 PostgreSQL、Redis 等基础设施,否则后端无法运行;首次使用要在 http://localhost:5173/setup 完成向导创建超级管理员并签发首个密钥;控制台 3001 端口应置于 VPN 或防火墙之后,仅网关 3000 端口对客户端开放

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v3.11.0
  1. v3.11.0ThinkWatch v3.11.0最新正式版2026-11-03 · 2026-11-03 06:55 UTC

    Images

    • ghcr.io/thinkwatchproject/think-watch-server:3.11.0
    • ghcr.io/thinkwatchproject/think-watch-web:3.11.0

    Helm

    helm install think-watch ./think-watch-3.11.0.tgz \
      --namespace thinkwatch --create-namespace
    

    Execution and buffering now adapt automatically to available CPUs and memory, including container limits. CPU work expands with traffic and extra threads retire after ten idle seconds; no runtime configuration change is required.

    Secrets and privacy recognises far more: the built-in redaction catalog grows from 30 rules to 131 in 20 groups, and the API keys saved for the providers are recognised by value, whatever their format. The console shows the rules' names and descriptions as thinkwatch-core writes them, in both languages. A value recognised by mistake can be made an exception without switching its rule off, and long conversations are no longer scanned from the start every turn. A provider can set the slowest generation its streams may keep up, and a stream that falls below it is cut so the client can try again. The thinkwatch-core crates move from v0.83.1 to v0.89.0.

    No new runtime setting. Two new provider columns (providers.min_speed_tokens_per_sec, providers.min_speed_window_secs, added on startup). New metrics cover minimum generation speed, buffering pressure and audit backlog memory. The redaction policy gains one optional field (exceptions), with endpoints of its own under /api/admin/security/redact/exceptions.

    Read before upgrading

    • Buffering is admitted against a process-wide memory budget. Request, response, cache and retained streaming data use limits derived from the container or machine's memory. Temporary pressure returns a retryable 429 with Retry-After: 1; an already-started stream receives an error event. Large upstream responses and stream timelines are bounded, and new audit entries are dropped with metrics if the separate audit byte budget fills. See automatic process resources for the limits and monitoring. These allocation budgets are not a hard RSS cap.
    • UUIDs and blockchain credentials gain built-in rules. Core v0.88.0 added 11 rules, enabled by default, including UUIDs, wallet keys and accounts, exchange credentials and recovery material. Monitor records findings; installations with protection On replace matching message text. UUIDs, hashes and public accounts do not independently trigger credential-exfiltration blocking. Review any workflows that send order or trace identifiers as message text.
    • New credential rules ship on. Under the On state of Secrets and privacy (security.redact mode enforce), values they find start being replaced with placeholders: among them credentials in request headers, command lines, URLs and bot webhooks, and secrets assigned in .env-style configuration (DB_PASSWORD=…). Under Monitor, the factory state, they are only recorded. A rule that gets in the way can be switched off on its own in the console.
    • Some keys are reported under a rule of their own. sk-or-v1- (OpenRouter), sk-lf- (Langfuse), sk-svcacct-, sk-admin- and sk-api- keys were reported as openai-api-key; they now have their own rule ids, and so do audit events and the gateway_redaction_found_total metric's kind label, which follows the new groups (ai-services, dev-platforms, cloud, …) instead of api-keys and personal.

    Added

    • Expanded redaction rules (thinkwatch-core v0.85.0 and later): about 50 more prefixed credentials (AI services, code hosting, cloud, payments and collaboration), credentials by position (request headers, command lines, URL parameters, bot webhooks, configuration assignments), cryptocurrency recovery phrases and private keys, and personal information: identity documents, foreign ID numbers, IBAN, phone numbers, addresses and coordinates, vehicles, device identifiers and va
  2. v3.10.0ThinkWatch v3.10.02026-10-25 · 2026-10-25 09:09 UTC
    查看更新说明

    Images

    • ghcr.io/thinkwatchproject/think-watch-server:3.10.0
    • ghcr.io/thinkwatchproject/think-watch-web:3.10.0

    Helm

    helm install think-watch ./think-watch-3.10.0.tgz \
      --namespace thinkwatch --create-namespace
    

    Upstreams that fail for the moment before any of the answer reached the client are now tried again instead of the error going straight back, and a model can set the reasoning effort its requests carry, with the gateway adapting when an upstream refuses a level. The thinkwatch-core crates move from v0.81.0 to v0.83.1.

    One new setting (gateway.retry_wait_secs), one new column (models.reasoning_effort, added on startup) and one new metric; no route or setting is removed.

    Read before upgrading

    • models gains a nullable column, reasoning_effort. The schema applied at startup adds it (ADD COLUMN IF NOT EXISTS); existing models keep it empty and their requests go out unchanged.
    • A reasoning level an upstream refuses is changed and sent again, for every model. This applies whether or not the model has a reasoning setting and cannot be turned off: when an upstream answers 400 because of the reasoning level a request carries, the request goes out once more with a level the upstream accepts, or without one, and the change is remembered for that provider and model for 7 days. The level sent can differ from the one the caller asked for, in either direction.
    • A request whose upstreams fail for the moment can take up to two minutes to fail. When every route of a model failed with an overload or a server error, or could not be reached, before any of the answer reached the client, the gateway now waits and sends the request again instead of returning the error at once, for up to gateway.retry_wait_secs (120 seconds by default) after the request arrived. Clients and load balancers with a shorter timeout than that may give up first. Set gateway.retry_wait_secs to 0 to keep the earlier behaviour.
    • A whole answer that runs into the request timeout is now an error with a log row. A non-streaming request's upstream attempt is cut 2 seconds before the gateway's 120-second request timeout (counted from when the request reached the gateway port), and the client gets the last upstream error, or a 504 ProviderTimeout when there was none, with its gateway_logs row, instead of an empty 408. A retry starts only when its wait and another attempt as long as the last one fit before then.
    • A stream's opening events arrive with its first content. While a stream can still be sent again, what the upstream sends before any content (Anthropic message_start and ping, Responses response.created and response.in_progress, a Chat chunk with only the role) is held back and goes out together with the first content. While it is held, and while the gateway waits to send again, the client receives an SSE comment (: keep-alive) after every 15 seconds without data — except Gemini clients, which never receive one. The status is 200 as before.

    Added

    • A model can set its reasoning (models.reasoning_effort, the Reasoning control in the model editor): off takes reasoning out of requests that have it on, and a level (low, medium, high, xhigh, max) changes the effort of requests whose reasoning is on. Requests without reasoning, such as title generation, go out unchanged. The setting is written the way each hop's upstream format writes it: with off, OpenAI Chat and Responses upstreams receive none, Gemini 2.5 Flash a thinking budget of 0, and Anthropic and Bedrock upstreams no thinking; Gemini models that cannot stop thinking (2.5 Pro, Gemini 3) get no thinking configuration. The response cache keys on the request together with the setting.
    • A reasoning level the upstream refuses is sent again once with one it accepts. On a 400 about the reasoning level, the request goes
  3. v3.9.1ThinkWatch v3.9.12026-10-23 · 2026-10-23 04:29 UTC
    查看更新说明

    Images

    • ghcr.io/thinkwatchproject/think-watch-server:3.9.1
    • ghcr.io/thinkwatchproject/think-watch-web:3.9.1

    Helm

    helm install think-watch ./think-watch-3.9.1.tgz \
      --namespace thinkwatch --create-namespace
    

    A conversation whose history a provider encrypted, such as a Codex conversation over OpenAI's Responses API, now stays with that provider instead of failing on every turn once it moves, and a provider that cannot read such history says why. The thinkwatch-core crates move from v0.77.0 to v0.81.0.

    No setting, schema or API route changes.

    Fixed

    • A conversation whose history a provider encrypted goes back to that provider. OpenAI's Responses API with store: false (Codex always sends it) hands the client encrypted state that comes back every turn: reasoning, remote compactions, Codex's messages between agents, encrypted tool output. Only the account that encrypted it can read it, so a conversation that moved to another provider — after five idle minutes, a tripped breaker or a failover — was refused on every turn from then on with invalid_encrypted_content. The gateway now remembers, for 30 days and whatever the affinity settings say, the provider that last answered each conversation (the authenticated user and the request's x-session-id, or else the session header the client sends by itself, such as Codex's session_id; over HTTP and over the WebSocket). The log row's session_id still comes from x-session-id only. A Responses request carrying encrypted content that cannot be left out goes only to that provider: no strategy pick, no failover, and its open breaker does not stop it. When that provider has no route for the model any more, the request is refused up front with 409, naming the provider and saying to start a new conversation. A request carrying only encrypted reasoning goes to that provider first and still fails over. Requests that name no conversation are routed as before. The request's log row records "pinned_by": "encrypted_history" when the provider was picked this way.
    • A provider that cannot read a conversation's encrypted history says so. Its refusal, as a 4xx answer or as the error event of a stream that opened with 200, now reaches the caller and the log as SealedHistoryRefused: the provider cannot read this conversation's encrypted history because another provider encrypted it; route the conversation back to that provider or start a new conversation. The upstream's own words follow. It no longer counts against the route's breaker.
  4. v3.9.0ThinkWatch v3.9.02026-10-20 · 2026-10-20 15:46 UTC
    查看更新说明

    Images

    • ghcr.io/thinkwatchproject/think-watch-server:3.9.0
    • ghcr.io/thinkwatchproject/think-watch-web:3.9.0

    Helm

    helm install think-watch ./think-watch-3.9.0.tgz \
      --namespace thinkwatch --create-namespace
    

    The Add to ThinkWatch Lite link from a rotated key now carries the key's request limits. A database failure during authentication answers 503 or 500 with the usual JSON error instead of an empty 500, and password hashing no longer runs on the request workers. Monitoring that matches only on 500 sees a database outage during authentication as a 503 from this release on. The thinkwatch-core crates stay at v0.77.0.

    No setting, schema or API route changes.

    Added

    • The rotated-key dialog's Add to ThinkWatch Lite link carries the key's request limits. The key's own enabled, unexpired AI-gateway rules that count requests over a window of whole minutes up to 60, or whole hours up to 24 (the console's 1 minute, 5 minutes, 1 hour, 5 hours and 24 hours among them), go into the link's limits parameter as Lite's rolling windows of the same length; of two rules on one window the smaller count goes, and at most 16 windows, those allowing the fewest requests per second. Token rules stay out, since they count weighted tokens and Lite counts plain ones; so do sliding 7-day rules (Lite's week is a calendar week), budgets, MCP rules and the owner's limits. A link with limits needs ThinkWatch Lite 2026.10.26 or later, which the dialog states under the button. Reading the rules needs rate_limits:read in a scope covering the key; without it, or when the rules cannot be read, the link is built without limits as before. A new key has no limits of its own yet, so its link carries none.

    Fixed

    • A database outage during authentication is a 503 with an error body, not an empty 500. When the session or API-key check could not reach Postgres (most often a connection-pool timeout), the console and the gateway answered 500 Internal Server Error with no body and, for the permission lookup, no log line. These lookups now answer like every handler does: 503 Service Unavailable with the usual JSON error when the database is unavailable, 500 with it for any other database error, and an error log naming the lookup that failed.
    • Password hashing no longer holds up other requests. Logins, password changes and resets, new users, turning off two-factor authentication and first-run setup ran Argon2 (tens of milliseconds of CPU by design) on the server's async workers, so every other request scheduled on that worker waited for it. They now run it on the blocking thread pool, at most one run per CPU at a time as before, so a burst of logins holds no more memory than it did (19 MiB per run). The hashes and the constant-time login are unchanged.
  5. v3.8.3ThinkWatch v3.8.32026-10-20 · 2026-10-20 11:52 UTC
    查看更新说明

    Images

    • ghcr.io/thinkwatchproject/think-watch-server:3.8.3
    • ghcr.io/thinkwatchproject/think-watch-web:3.8.3

    Helm

    helm install think-watch ./think-watch-3.8.3.tgz \
      --namespace thinkwatch --create-namespace
    

    Tool-call inspection in streamed answers now decides only on arguments that have settled, and a hit that is recorded carries the whole call. The thinkwatch-core crates move from v0.75.0 to v0.77.0. The web image is built with Node.js 24.22.0 and pnpm 12.14.0.

    No setting, schema or API route changes.

    Changed

    • The thinkwatch-core crates move from v0.75.0 to v0.77.0.
    • Since a recorded hit now waits for the complete call, a streamed tool call that never completes (the upstream stream breaks off, or the client leaves, in the middle of it) leaves no tool-inspection row. Such a call was never whole on the client side either. Rules that cut still check the arguments while they arrive.
    • The web image's build stage uses Node.js 24.22.0 and pnpm 12.14.0. The served image (nginx) is unchanged.
    • The development compose file (deploy/docker-compose.dev.yml) runs Zitadel v4.19.5.

    Fixed

    • Tool-call inspection no longer judges arguments that are still arriving. A streamed tool call was checked against its arguments as far as they had come in. A host cut off in the middle of its name (http://127 before .0.0.1) looked external, and a pattern anchored at the end could match the half-received tail. A hit now counts only once it is settled; rules that cut still act as early as possible.
    • A recorded hit carries the whole call. A hit that is only recorded, and every hit in observe mode, was recorded with the arguments as they stood at the first match. It is now recorded once the call is complete. A Chat Completions call with no finish_reason or [DONE] is checked when the stream ends.
    • -T is an upload only in curl. The upload rule took any -T for curl -T, so docker compose exec -T next to a URL was recorded as a file upload.

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    ThinkWatch

    English | 中文

    A self-hosted AI API and MCP gateway for organizations. Every model request and every MCP tool call passes through one gateway, where it is authenticated against the organization's identity provider, checked against limits and budgets, inspected by security guards, priced, and written to the audit log. It plays the role for AI access that a bastion host plays for server access.

    For individual developers: ThinkWatch Lite is a free desktop app (MIT) that runs a local gateway for Claude Code, Codex and other AI clients on macOS, Windows and Linux.

    Sponsors: Want to appear here?

                        ┌──────────────────────────────────────┐
     Claude Code ──────>│                                      │──> OpenAI
     Cursor ───────────>│    Gateway  :3000                    │──> Anthropic
     Custom Agent ─────>│    AI API + MCP                      │──> Google Gemini
     CI/CD Pipeline ───>│                                      │──> Azure OpenAI / AWS Bedrock
                        └──────────────────────────────────────┘
                        ┌──────────────────────────────────────┐
     Admin Browser ────>│    Console  :3001                    │
                        │    Management UI + Admin API         │
                        └──────────────────────────────────────┘
    

    Highlights

    • MCP tool calls run as the real user. Each user connects their own GitHub, Notion, Linear, Slack or Atlassian account through OAuth or a personal token, so the upstream's own audit log shows who acted. Tokens are encrypted at rest, tool lists are cached per user, and each tool can be granted per role and per API key.
    • Security guards on every request. Secrets & privacy replaces credentials and personal data anywhere in a request with placeholders such as <<TW_EMAIL_1>> before it goes upstream, and restores them in the answer, streamed ones included. Dangerous commands checks the tool calls in each response, and the content filter looks for prompt-injection phrases, hidden characters and chosen words in what the caller sent and in the text of answers, then refuses the request, deletes or replaces them, or records them.
    • Identity from the organization's directory. Sign-in works through any OIDC provider (Zitadel, Okta, Azure AD and others), with optional TOTP. Five built-in roles, from Super Admin to Viewer, and custom roles decide who may use which models, tools and admin pages.
    • One key for AI and MCP. Users receive tw- virtual keys that can be scoped to the AI gateway, the MCP gateway or both. Keys are stored only as hashes and rotate with a grace period.
    • Rate limits and budgets. Sliding windows from one minute to one week limit requests or tokens, and daily, weekly or monthly budgets cap spending. Both attach to users, API keys or roles, and rate limits apply to MCP tool calls as well as model requests.
    • Cost accounting that finance can use. Spend is reported by model, user, provider and cost center, with CSV chargeback reports and a month-end forecast. Per-model weights make expensive models count for more against the same quota.
    • Audit trail in ClickHouse. Every model request and tool call is recorded with user, parameters, response, latency and errors, and captured bodies can be redacted with the secrets & privacy rules before storage (off by default). Events can be forwarded to a SIEM over Syslog, Kafka (through a REST proxy) or signed webhooks.
    • One endpoint for every client. OpenAI Chat Completions, OpenAI Responses, Anthropic Messages and Gemini requests are served on one port and converted to whatever the upstream speaks. Routing spreads traffic by weight, latency or health, and a circuit breaker takes failing upstreams out of rotation.

    Quick start

    # 1. Start infrastructure
    make infra
    
    # 2. Generate dev secrets + start backend (gateway :3000 + console :3001)
    make dev-secrets       # writes .env from .env.example with random secrets
    make dev-backend
    
    # 3. Start frontend dev server
    cd web && pnpm install && pnpm dev
    
    # 4. Complete the setup wizard at http://localhost:5173/setup
    

    The setup wizard creates the first Super Admin account, sets the site name and issues a first API key; providers are added in the console afterwards. The console then has copy-paste setup instructions for Claude Code, Cursor, Continue, Cline, the OpenAI and Anthropic SDKs, and cURL.

    Deployment

    OptionCommandNotes
    Docker Composemake deployGenerates .env.production with random secrets on first run
    Kubernetesmake helm-deployHelm chart in deploy/helm/think-watch; secrets are generated on install and kept on upgrade

    The gateway (port 3000) is the only part that clients need to reach. The console (port 3001) serves the management UI and admin API and belongs behind a VPN or firewall. See the Deployment Guide for TLS, hardening and production settings.

    Behaviour worth knowing

    MCP identity

    • A user may connect several accounts to one server (work and personal, for example) and pin each tw- key to one of them.
    • Adding a server takes its URL: the gateway discovers the OAuth endpoints and registers itself when the upstream supports dynamic client registration. The MCP Store ships 37 ready-made templates.
    • A user who has not yet connected an account still sees the tool list; calling a tool returns JSON-RPC error -32050 with the authorization URL, which compliant MCP clients can show.
    • Responses from servers that use per-user credentials are cached per user and account, never shared.

    Security guards

    • There are three guards — secrets & privacy, dangerous commands and the content filter — each on, monitoring or off. A new installation starts all three monitoring: hits go to the audit log, and from there to the log forwarders, and nothing is changed until a guard is switched on. Then each rule does what its own action says.
    • Every rule is listed on the console's security page, built-in and custom, grouped, with a search; a group can be switched on or off at once. Every rule has an action of its own: replace with a placeholder or record (secrets & privacy), cut off or record (dangerous commands), refuse, delete, replace or record (content filter). Custom rules can be added, and a sample can be tried against one rule or a whole guard first. The page's log lists what the guards found, read from the audit log.
    • Secrets & privacy searches the whole request, system prompt and earlier answers included, but not field names or base64 payloads. Custom rules also skip the values a protocol needs exactly as they are, such as ids, call ids, types, roles, tool names and the model name; built-in rules still search them. A custom rule looks for its text as written or for a regular expression, ignoring case unless it is set to be case-sensitive. A match becomes <<TW_LABEL_n>> — SECRET for credentials, ID_NUMBER, CARD_NUMBER, EMAIL and PHONE for personal data, a label of its own for a custom rule — and is restored in the answer. E-mail addresses and phone numbers ship switched off.
    • A content rule matches a phrase, a regular expression or code points (U+200B, U+E0000–U+E007F), case-sensitive or not, and checks requests (the caller's messages and tool results), the text of answers, or both. It refuses the request, deletes what it matched, replaces it with a text of its own (*** unless it says otherwise), or records only. Answers are checked as they stream: only the tail that could still grow into a match is held back, and what a rule deletes or replaces never reaches the client. A rule that checks answers cannot refuse, nor match code points. Hidden characters are content rules: Unicode tag characters and bidirectional controls ship on, zero-width and private-use characters off.
    • A dangerous-command rule cuts the response at the call or records it. Besides the dangerous-command rules, two built-in rules catch a credential sent to an unknown host and a local file uploaded to an external host.
    • A model's maximum output tokens, set on the Models page, caps max_tokens on every request to that model; it replaces the old output length guardrail.

    Limits and budgets

    • Every limit and budget is checked before the request, against what earlier requests used; tokens are counted after the response, so one request can cross a token limit or a budget before the next is refused. A refused request counts against nothing, and its 429 says in Retry-After when the limit frees.
    • A limit set on an API key applies on top of its owner's, on a counter of its own.
    • A route's RPM and TPM caps count over a sliding minute. A route at its cap is skipped for the model's next route; when every route is at its cap, the request gets 429. Such a request has passed the caller's own limits and counts on them.
    • GET /v1/usage on the gateway port tells a client, with its API key, what it has used today and this month and what room its limits leave.
    • Redis can be a single node or a Redis Cluster.
    • If Redis is unavailable, or the database fails while a request's limits are loaded, limits fail open by default. Setting security.rate_limit_fail_closed refuses those requests instead.
    • Budget alerts fire once per period at 50%, 80%, 95% and 100%.

    Documentation

    Product page: thinkwat.ch/thinkwatch · Full documentation: thinkwat.ch/docs

    DocumentDescription
    ArchitectureSystem design, dual-port model, data flow
    Deployment GuideDocker Compose, Kubernetes, TLS, production hardening
    ConfigurationEnvironment variables and settings
    API ReferenceGateway and console endpoints
    SecurityAuth model, encryption, RBAC, threat model
    Secret RotationRotating provider keys, JWT secrets and admin credentials

    Built on ThinkWatch Core

    ThinkWatch uses four crates from ThinkWatch Core (MIT): tw-dialect for conversion between API formats and usage parsing, tw-guard for the security guards, tw-breaker for the circuit-breaker state machine, and tw-bedrock for Amazon Bedrock signing, event streams and the model catalog.

    ThinkWatch Lite is the desktop edition for individual developers, a local gateway for Claude Code, Codex and other clients on macOS, Windows and Linux (MIT).

    Contributing

    Contributions are welcome. Please open an issue to discuss before submitting a PR for major changes.

    Your first pull request asks you to sign the Contributor License Agreement once; CONTRIBUTING.md explains how.

    License

    ThinkWatch is source-available under the Business Source License 1.1. Non-production use is free. Production use is free up to both 10,000,000 Billable Tokens and 10,000 MCP Tool Calls per UTC calendar month; above either threshold, a commercial license is required and priced by usage tiers.

    See LICENSING.md for the production-use thresholds, the Billable Token and MCP Tool Call definitions, the tiering model, and the changeover to GPL-2.0-or-later.