0.159.0

Added

  • MCP: единый OAuth 2.1 стек — discovery / DCR / PKCE / resource-indicator / refresher (ADR docs/adr/2026-07-02-mcp-full-coverage.md, Фаза 1, ). Полноценная поддержка подключения ЛЮБОГО MCP-сервера с OAuth 2.1: автоматическое обнаружение Authorization Server (RFC 9728 → RFC 8414 → OIDC fallback), автоматическая регистрация клиента (RFC 7591 DCR), обязательный PKCE S256 (OAuth 2.1), RFC 8707 resource indicator, три grant-типа (pkce / authorization_code / client_credentials), прозрачный refresh с singleflight (RFC 9700 rotating refresh protection). Единый стек заменяет раздвоенный путь (handler browser-flow + DBTokenStore) на пакет internal/mcp/oauth/.
  • Новый пакет internal/mcp/oauth/ (5 файлов, ~850 строк):
  • discovery.go — RFC 9728 (/.well-known/oauth-protected-resource → AS base URL) → RFC 8414 (oauth-authorization-server → endpoints) → OIDC fallback (openid-configuration). Same-origin issuer cross-check против MCP-сервера (не fetch-URL — устранена тавтология из goclaw). Кэш 5 мин. Resource URI санируется (strip query/fragment per RFC 8707 §2).
  • dcr.go — RFC 7591 Dynamic Client Registration. Public client + PKCE (token_endpoint_auth_method: "none"). Принимает 200/201.
  • flow.go — Authorization Code + PKCE S256 обязательно (OAuth 2.1 §4.1.1). state (16 байт, anti-replay, TTL 10 мин). RFC 8707 resource indicator. 3 grant-типа: pkce (public), authorization_code (confidential + client_secret), client_credentials (неинтерактивный). Валидация grant_type (unknown → error).
  • refresher.gosingleflight (golang.org/x/sync/singleflight) коалесит параллельные load+refresh для одного сервера — защита rotating refresh tokens (RFC 9700) от двойного использования. In-memory кэш, буфер 60с до истечения. Handling отсутствующего expires_in (RFC 6749 §5.1 OPTIONAL → non-expiring).
  • settings.go — парсинг MCPServer.Settings JSONB: {oauth:{auth_type, use_dcr, endpoints, client_id, client_secret, grant_type, scope, resource}, _discovered:{issuer, endpoints, dcr_creds}}. Nil-safe на ошибке парсинга (возвращает *Settings, не nil).
  • Миграция 100 (SQLite + PG): ALTER TABLE mcp_servers ADD settings TEXT/JSONB DEFAULT '{}'. Down-миграция SQLite — явный CREATE TABLE с сохранением UNIQUE constraint на name (не CREATE TABLE AS SELECT).
  • Store: поле Settings в MCPServer (store.go, sqlite.go, postgres.go) — CRUD во всех запросах. Config import/export — settings как json.RawMessage.
  • Handler: MCPHandler.SetOAuthComponents(fm, d, r) — inject FlowManager/Discoverer/Refresher. OAuthAuthorize — discovery/DCR (если settings.oauth.auth_type == "oauth") или legacy (manual endpoints from extractBaseURL). client_credentials grant — server-side mint, без browser redirect, uniform response shape (authorization_url:"" + completed:true). OAuthCallbackflowMgr.ExchangeCode (state validation + SSRF-safe token exchange). Новые endpoints: GET /oauth/status (token status), DELETE /oauth/token (revoke + cache invalidation).
  • Refresher cache invalidation при persistOAuthTokens и OAuthRevoke — race condition между in-flight refresh и новым токеном устранён.
  • Error leak prevention: API responses не содержат err.Error() от внутренних OAuth-вызовов (логи — детальные, HTTP — generic).
  • main.go wiring: mcp.NewSSRFSafeClient(providers.SSRFGuard) — shared OAuth HTTP client (global guard, 15s timeout, default TLS). Per-server DCR/exchange через mcp.OAuthExchangeClient (per-server allow_local/TLS).
  • Проверки: go test ./internal/... — зелёные; 14 новых тестов в internal/mcp/oauth/ (settings parsing, discovery mock, PKCE flow + anti-replay, client_credentials, state expiry, sameOrigin, deriveBase); TestOAuthCallback_FailClosedWithoutFlowManager (503 без FlowManager); go vet + golangci-lint — 0 issues; make build-cross — проходит; цикл ocr review (3 итерации) — все HIGH/MEDIUM устранены (issuer tautology, response shape, discovery fallback, error leaks, nil-safety, grant validation, refresher race), 0 HIGH/MEDIUM в финальном проходе (46 оставшихся — LOW/improvement: test coverage, cache eviction, singleflight для discovery, FlowManager.Close — отложены в Фазы 2+).