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.go— singleflight (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.SettingsJSONB:{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 fromextractBaseURL).client_credentialsgrant — server-side mint, без browser redirect, uniform response shape (authorization_url:""+completed:true).OAuthCallback—flowMgr.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+).