feat: MCP OAuth (#178)

* feat: MCP OAuth

* fix: redirect url and oauth
This commit is contained in:
Acbox Liu
2026-03-04 00:41:05 +08:00
committed by GitHub
parent f0517a3a1f
commit 64609c2101
33 changed files with 4037 additions and 97 deletions
+31
View File
@@ -170,6 +170,11 @@ CREATE TABLE IF NOT EXISTS mcp_connections (
type TEXT NOT NULL,
config JSONB NOT NULL DEFAULT '{}'::jsonb,
is_active BOOLEAN NOT NULL DEFAULT true,
status TEXT NOT NULL DEFAULT 'unknown',
tools_cache JSONB NOT NULL DEFAULT '[]'::jsonb,
last_probed_at TIMESTAMPTZ,
status_message TEXT NOT NULL DEFAULT '',
auth_type TEXT NOT NULL DEFAULT 'none',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT mcp_connections_type_check CHECK (type IN ('stdio', 'http', 'sse')),
@@ -178,6 +183,32 @@ CREATE TABLE IF NOT EXISTS mcp_connections (
CREATE INDEX IF NOT EXISTS idx_mcp_connections_bot_id ON mcp_connections(bot_id);
CREATE TABLE IF NOT EXISTS mcp_oauth_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
connection_id UUID NOT NULL UNIQUE REFERENCES mcp_connections(id) ON DELETE CASCADE,
resource_metadata_url TEXT NOT NULL DEFAULT '',
authorization_server_url TEXT NOT NULL DEFAULT '',
authorization_endpoint TEXT NOT NULL DEFAULT '',
token_endpoint TEXT NOT NULL DEFAULT '',
registration_endpoint TEXT NOT NULL DEFAULT '',
scopes_supported TEXT[] NOT NULL DEFAULT '{}',
client_id TEXT NOT NULL DEFAULT '',
client_secret TEXT NOT NULL DEFAULT '',
access_token TEXT NOT NULL DEFAULT '',
refresh_token TEXT NOT NULL DEFAULT '',
token_type TEXT NOT NULL DEFAULT 'Bearer',
expires_at TIMESTAMPTZ,
scope TEXT NOT NULL DEFAULT '',
pkce_code_verifier TEXT NOT NULL DEFAULT '',
state_param TEXT NOT NULL DEFAULT '',
resource_uri TEXT NOT NULL DEFAULT '',
redirect_uri TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_mcp_oauth_tokens_connection_id ON mcp_oauth_tokens(connection_id);
-- Bot history is bot-scoped (one history container per bot).
CREATE TABLE IF NOT EXISTS bot_channel_configs (
@@ -0,0 +1,12 @@
-- 0022_mcp_probe_and_oauth (rollback)
-- Remove probe status fields and auth_type from mcp_connections; drop mcp_oauth_tokens table
DROP INDEX IF EXISTS idx_mcp_oauth_tokens_connection_id;
DROP TABLE IF EXISTS mcp_oauth_tokens;
ALTER TABLE mcp_connections
DROP COLUMN IF EXISTS status,
DROP COLUMN IF EXISTS tools_cache,
DROP COLUMN IF EXISTS last_probed_at,
DROP COLUMN IF EXISTS status_message,
DROP COLUMN IF EXISTS auth_type;
@@ -0,0 +1,34 @@
-- 0022_mcp_probe_and_oauth
-- Add probe status fields and auth_type to mcp_connections; create mcp_oauth_tokens table
ALTER TABLE mcp_connections
ADD COLUMN IF NOT EXISTS status TEXT NOT NULL DEFAULT 'unknown',
ADD COLUMN IF NOT EXISTS tools_cache JSONB NOT NULL DEFAULT '[]'::jsonb,
ADD COLUMN IF NOT EXISTS last_probed_at TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS status_message TEXT NOT NULL DEFAULT '',
ADD COLUMN IF NOT EXISTS auth_type TEXT NOT NULL DEFAULT 'none';
CREATE TABLE IF NOT EXISTS mcp_oauth_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
connection_id UUID NOT NULL UNIQUE REFERENCES mcp_connections(id) ON DELETE CASCADE,
resource_metadata_url TEXT NOT NULL DEFAULT '',
authorization_server_url TEXT NOT NULL DEFAULT '',
authorization_endpoint TEXT NOT NULL DEFAULT '',
token_endpoint TEXT NOT NULL DEFAULT '',
registration_endpoint TEXT NOT NULL DEFAULT '',
scopes_supported TEXT[] NOT NULL DEFAULT '{}',
client_id TEXT NOT NULL DEFAULT '',
client_secret TEXT NOT NULL DEFAULT '',
access_token TEXT NOT NULL DEFAULT '',
refresh_token TEXT NOT NULL DEFAULT '',
token_type TEXT NOT NULL DEFAULT 'Bearer',
expires_at TIMESTAMPTZ,
scope TEXT NOT NULL DEFAULT '',
pkce_code_verifier TEXT NOT NULL DEFAULT '',
state_param TEXT NOT NULL DEFAULT '',
resource_uri TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_mcp_oauth_tokens_connection_id ON mcp_oauth_tokens(connection_id);
@@ -0,0 +1,4 @@
-- 0023_mcp_oauth_redirect_uri
-- Remove redirect_uri column from mcp_oauth_tokens
ALTER TABLE mcp_oauth_tokens DROP COLUMN IF EXISTS redirect_uri;
@@ -0,0 +1,4 @@
-- 0023_mcp_oauth_redirect_uri
-- Add redirect_uri column to mcp_oauth_tokens for per-flow callback URL storage
ALTER TABLE mcp_oauth_tokens ADD COLUMN IF NOT EXISTS redirect_uri TEXT NOT NULL DEFAULT '';
+23 -7
View File
@@ -1,19 +1,19 @@
-- name: GetMCPConnectionByID :one
SELECT id, bot_id, name, type, config, is_active, created_at, updated_at
SELECT id, bot_id, name, type, config, is_active, status, tools_cache, last_probed_at, status_message, auth_type, created_at, updated_at
FROM mcp_connections
WHERE bot_id = $1 AND id = $2
LIMIT 1;
-- name: ListMCPConnectionsByBotID :many
SELECT id, bot_id, name, type, config, is_active, created_at, updated_at
SELECT id, bot_id, name, type, config, is_active, status, tools_cache, last_probed_at, status_message, auth_type, created_at, updated_at
FROM mcp_connections
WHERE bot_id = $1
ORDER BY created_at DESC;
-- name: CreateMCPConnection :one
INSERT INTO mcp_connections (bot_id, name, type, config, is_active)
VALUES ($1, $2, $3, $4, $5)
RETURNING id, bot_id, name, type, config, is_active, created_at, updated_at;
INSERT INTO mcp_connections (bot_id, name, type, config, is_active, auth_type)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id, bot_id, name, type, config, is_active, status, tools_cache, last_probed_at, status_message, auth_type, created_at, updated_at;
-- name: UpdateMCPConnection :one
UPDATE mcp_connections
@@ -21,9 +21,25 @@ SET name = $3,
type = $4,
config = $5,
is_active = $6,
auth_type = $7,
updated_at = now()
WHERE bot_id = $1 AND id = $2
RETURNING id, bot_id, name, type, config, is_active, created_at, updated_at;
RETURNING id, bot_id, name, type, config, is_active, status, tools_cache, last_probed_at, status_message, auth_type, created_at, updated_at;
-- name: UpdateMCPConnectionProbeResult :exec
UPDATE mcp_connections
SET status = $3,
tools_cache = $4,
last_probed_at = now(),
status_message = $5,
updated_at = now()
WHERE bot_id = $1 AND id = $2;
-- name: UpdateMCPConnectionAuthType :exec
UPDATE mcp_connections
SET auth_type = $2,
updated_at = now()
WHERE id = $1;
-- name: DeleteMCPConnection :exec
DELETE FROM mcp_connections
@@ -36,4 +52,4 @@ ON CONFLICT (bot_id, name)
DO UPDATE SET type = EXCLUDED.type,
config = EXCLUDED.config,
updated_at = now()
RETURNING id, bot_id, name, type, config, is_active, created_at, updated_at;
RETURNING id, bot_id, name, type, config, is_active, status, tools_cache, last_probed_at, status_message, auth_type, created_at, updated_at;
+82
View File
@@ -0,0 +1,82 @@
-- name: GetMCPOAuthToken :one
SELECT id, connection_id, resource_metadata_url, authorization_server_url,
authorization_endpoint, token_endpoint, registration_endpoint,
scopes_supported, client_id, client_secret, access_token, refresh_token,
token_type, expires_at, scope, pkce_code_verifier, state_param,
resource_uri, redirect_uri, created_at, updated_at
FROM mcp_oauth_tokens
WHERE connection_id = $1
LIMIT 1;
-- name: GetMCPOAuthTokenByState :one
SELECT id, connection_id, resource_metadata_url, authorization_server_url,
authorization_endpoint, token_endpoint, registration_endpoint,
scopes_supported, client_id, client_secret, access_token, refresh_token,
token_type, expires_at, scope, pkce_code_verifier, state_param,
resource_uri, redirect_uri, created_at, updated_at
FROM mcp_oauth_tokens
WHERE state_param = $1
LIMIT 1;
-- name: UpsertMCPOAuthDiscovery :one
INSERT INTO mcp_oauth_tokens (connection_id, resource_metadata_url, authorization_server_url,
authorization_endpoint, token_endpoint, registration_endpoint, scopes_supported,
resource_uri)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
ON CONFLICT (connection_id)
DO UPDATE SET resource_metadata_url = EXCLUDED.resource_metadata_url,
authorization_server_url = EXCLUDED.authorization_server_url,
authorization_endpoint = EXCLUDED.authorization_endpoint,
token_endpoint = EXCLUDED.token_endpoint,
registration_endpoint = EXCLUDED.registration_endpoint,
scopes_supported = EXCLUDED.scopes_supported,
resource_uri = EXCLUDED.resource_uri,
updated_at = now()
RETURNING id, connection_id, resource_metadata_url, authorization_server_url,
authorization_endpoint, token_endpoint, registration_endpoint,
scopes_supported, client_id, client_secret, access_token, refresh_token,
token_type, expires_at, scope, pkce_code_verifier, state_param,
resource_uri, redirect_uri, created_at, updated_at;
-- name: UpdateMCPOAuthPKCEState :exec
UPDATE mcp_oauth_tokens
SET pkce_code_verifier = $2,
state_param = $3,
client_id = $4,
redirect_uri = $5,
updated_at = now()
WHERE connection_id = $1;
-- name: UpdateMCPOAuthTokens :exec
UPDATE mcp_oauth_tokens
SET access_token = $2,
refresh_token = $3,
token_type = $4,
expires_at = $5,
scope = $6,
pkce_code_verifier = '',
state_param = '',
updated_at = now()
WHERE connection_id = $1;
-- name: ClearMCPOAuthTokens :exec
UPDATE mcp_oauth_tokens
SET access_token = '',
refresh_token = '',
expires_at = NULL,
scope = '',
pkce_code_verifier = '',
state_param = '',
redirect_uri = '',
updated_at = now()
WHERE connection_id = $1;
-- name: UpdateMCPOAuthClientSecret :exec
UPDATE mcp_oauth_tokens
SET client_secret = $2,
updated_at = now()
WHERE connection_id = $1;
-- name: DeleteMCPOAuthToken :exec
DELETE FROM mcp_oauth_tokens
WHERE connection_id = $1;