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
+380
View File
@@ -15,6 +15,45 @@ const docTemplate = `{
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/api/oauth/mcp/callback": {
"get": {
"description": "Handles the OAuth authorization callback, exchanges code for tokens",
"tags": [
"mcp"
],
"summary": "OAuth callback handler",
"parameters": [
{
"type": "string",
"description": "Authorization code",
"name": "code",
"in": "query",
"required": true
},
{
"type": "string",
"description": "State parameter",
"name": "state",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "HTML page that closes the popup",
"schema": {
"type": "string"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/auth/login": {
"post": {
"description": "Validate user credentials and issue a JWT",
@@ -2324,6 +2363,215 @@ const docTemplate = `{
}
}
},
"/bots/{bot_id}/mcp/{id}/oauth/authorize": {
"post": {
"description": "Generate PKCE and return authorization URL for the user to authorize",
"tags": [
"mcp"
],
"summary": "Start OAuth authorization flow",
"parameters": [
{
"type": "string",
"description": "MCP connection ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Optional client_id",
"name": "payload",
"in": "body",
"schema": {
"$ref": "#/definitions/handlers.oauthAuthorizeRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/mcp.AuthorizeResult"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/bots/{bot_id}/mcp/{id}/oauth/discover": {
"post": {
"description": "Probe MCP server URL for OAuth requirements and discover authorization server metadata",
"tags": [
"mcp"
],
"summary": "Discover OAuth configuration for MCP server",
"parameters": [
{
"type": "string",
"description": "MCP connection ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Optional URL override",
"name": "payload",
"in": "body",
"schema": {
"$ref": "#/definitions/handlers.oauthDiscoverRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/mcp.DiscoveryResult"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/bots/{bot_id}/mcp/{id}/oauth/status": {
"get": {
"description": "Returns the current OAuth status including whether tokens are available",
"tags": [
"mcp"
],
"summary": "Get OAuth status for MCP connection",
"parameters": [
{
"type": "string",
"description": "MCP connection ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/mcp.OAuthStatus"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/bots/{bot_id}/mcp/{id}/oauth/token": {
"delete": {
"description": "Clears stored OAuth tokens",
"tags": [
"mcp"
],
"summary": "Revoke OAuth tokens for MCP connection",
"parameters": [
{
"type": "string",
"description": "MCP connection ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"description": "No Content"
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/bots/{bot_id}/mcp/{id}/probe": {
"post": {
"description": "Probe a MCP connection to discover tools and verify connectivity",
"tags": [
"mcp"
],
"summary": "Probe MCP connection",
"parameters": [
{
"type": "string",
"description": "MCP connection ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handlers.ProbeResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"403": {
"description": "Forbidden",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/bots/{bot_id}/memory": {
"get": {
"description": "List all memories in the bot-shared namespace",
@@ -7928,6 +8176,9 @@ const docTemplate = `{
"github_com_memohai_memoh_internal_mcp.Connection": {
"type": "object",
"properties": {
"auth_type": {
"type": "string"
},
"bot_id": {
"type": "string"
},
@@ -7944,9 +8195,24 @@ const docTemplate = `{
"is_active": {
"type": "boolean"
},
"last_probed_at": {
"type": "string"
},
"name": {
"type": "string"
},
"status": {
"type": "string"
},
"status_message": {
"type": "string"
},
"tools_cache": {
"type": "array",
"items": {
"$ref": "#/definitions/mcp.ToolDescriptor"
}
},
"type": {
"type": "string"
},
@@ -8350,6 +8616,26 @@ const docTemplate = `{
}
}
},
"handlers.ProbeResponse": {
"type": "object",
"properties": {
"auth_required": {
"type": "boolean"
},
"error": {
"type": "string"
},
"status": {
"type": "string"
},
"tools": {
"type": "array",
"items": {
"$ref": "#/definitions/mcp.ToolDescriptor"
}
}
}
},
"handlers.RefreshResponse": {
"type": "object",
"properties": {
@@ -8598,6 +8884,22 @@ const docTemplate = `{
}
}
},
"handlers.oauthAuthorizeRequest": {
"type": "object",
"properties": {
"client_id": {
"type": "string"
}
}
},
"handlers.oauthDiscoverRequest": {
"type": "object",
"properties": {
"url": {
"type": "string"
}
}
},
"handlers.skillsOpResponse": {
"type": "object",
"properties": {
@@ -8742,6 +9044,43 @@ const docTemplate = `{
}
}
},
"mcp.AuthorizeResult": {
"type": "object",
"properties": {
"authorization_url": {
"type": "string"
}
}
},
"mcp.DiscoveryResult": {
"type": "object",
"properties": {
"authorization_endpoint": {
"type": "string"
},
"authorization_server_url": {
"type": "string"
},
"registration_endpoint": {
"type": "string"
},
"resource_metadata_url": {
"type": "string"
},
"resource_uri": {
"type": "string"
},
"scopes_supported": {
"type": "array",
"items": {
"type": "string"
}
},
"token_endpoint": {
"type": "string"
}
}
},
"mcp.ExportResponse": {
"type": "object",
"properties": {
@@ -8810,6 +9149,44 @@ const docTemplate = `{
}
}
},
"mcp.OAuthStatus": {
"type": "object",
"properties": {
"auth_server": {
"type": "string"
},
"configured": {
"type": "boolean"
},
"expired": {
"type": "boolean"
},
"expires_at": {
"type": "string"
},
"has_token": {
"type": "boolean"
},
"scopes": {
"type": "string"
}
}
},
"mcp.ToolDescriptor": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"inputSchema": {
"type": "object",
"additionalProperties": {}
},
"name": {
"type": "string"
}
}
},
"mcp.UpsertRequest": {
"type": "object",
"properties": {
@@ -8819,6 +9196,9 @@ const docTemplate = `{
"type": "string"
}
},
"auth_type": {
"type": "string"
},
"command": {
"type": "string"
},
+380
View File
@@ -6,6 +6,45 @@
"version": "1.0.0"
},
"paths": {
"/api/oauth/mcp/callback": {
"get": {
"description": "Handles the OAuth authorization callback, exchanges code for tokens",
"tags": [
"mcp"
],
"summary": "OAuth callback handler",
"parameters": [
{
"type": "string",
"description": "Authorization code",
"name": "code",
"in": "query",
"required": true
},
{
"type": "string",
"description": "State parameter",
"name": "state",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "HTML page that closes the popup",
"schema": {
"type": "string"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/auth/login": {
"post": {
"description": "Validate user credentials and issue a JWT",
@@ -2315,6 +2354,215 @@
}
}
},
"/bots/{bot_id}/mcp/{id}/oauth/authorize": {
"post": {
"description": "Generate PKCE and return authorization URL for the user to authorize",
"tags": [
"mcp"
],
"summary": "Start OAuth authorization flow",
"parameters": [
{
"type": "string",
"description": "MCP connection ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Optional client_id",
"name": "payload",
"in": "body",
"schema": {
"$ref": "#/definitions/handlers.oauthAuthorizeRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/mcp.AuthorizeResult"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/bots/{bot_id}/mcp/{id}/oauth/discover": {
"post": {
"description": "Probe MCP server URL for OAuth requirements and discover authorization server metadata",
"tags": [
"mcp"
],
"summary": "Discover OAuth configuration for MCP server",
"parameters": [
{
"type": "string",
"description": "MCP connection ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Optional URL override",
"name": "payload",
"in": "body",
"schema": {
"$ref": "#/definitions/handlers.oauthDiscoverRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/mcp.DiscoveryResult"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/bots/{bot_id}/mcp/{id}/oauth/status": {
"get": {
"description": "Returns the current OAuth status including whether tokens are available",
"tags": [
"mcp"
],
"summary": "Get OAuth status for MCP connection",
"parameters": [
{
"type": "string",
"description": "MCP connection ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/mcp.OAuthStatus"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/bots/{bot_id}/mcp/{id}/oauth/token": {
"delete": {
"description": "Clears stored OAuth tokens",
"tags": [
"mcp"
],
"summary": "Revoke OAuth tokens for MCP connection",
"parameters": [
{
"type": "string",
"description": "MCP connection ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"description": "No Content"
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/bots/{bot_id}/mcp/{id}/probe": {
"post": {
"description": "Probe a MCP connection to discover tools and verify connectivity",
"tags": [
"mcp"
],
"summary": "Probe MCP connection",
"parameters": [
{
"type": "string",
"description": "MCP connection ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handlers.ProbeResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"403": {
"description": "Forbidden",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/bots/{bot_id}/memory": {
"get": {
"description": "List all memories in the bot-shared namespace",
@@ -7919,6 +8167,9 @@
"github_com_memohai_memoh_internal_mcp.Connection": {
"type": "object",
"properties": {
"auth_type": {
"type": "string"
},
"bot_id": {
"type": "string"
},
@@ -7935,9 +8186,24 @@
"is_active": {
"type": "boolean"
},
"last_probed_at": {
"type": "string"
},
"name": {
"type": "string"
},
"status": {
"type": "string"
},
"status_message": {
"type": "string"
},
"tools_cache": {
"type": "array",
"items": {
"$ref": "#/definitions/mcp.ToolDescriptor"
}
},
"type": {
"type": "string"
},
@@ -8341,6 +8607,26 @@
}
}
},
"handlers.ProbeResponse": {
"type": "object",
"properties": {
"auth_required": {
"type": "boolean"
},
"error": {
"type": "string"
},
"status": {
"type": "string"
},
"tools": {
"type": "array",
"items": {
"$ref": "#/definitions/mcp.ToolDescriptor"
}
}
}
},
"handlers.RefreshResponse": {
"type": "object",
"properties": {
@@ -8589,6 +8875,22 @@
}
}
},
"handlers.oauthAuthorizeRequest": {
"type": "object",
"properties": {
"client_id": {
"type": "string"
}
}
},
"handlers.oauthDiscoverRequest": {
"type": "object",
"properties": {
"url": {
"type": "string"
}
}
},
"handlers.skillsOpResponse": {
"type": "object",
"properties": {
@@ -8733,6 +9035,43 @@
}
}
},
"mcp.AuthorizeResult": {
"type": "object",
"properties": {
"authorization_url": {
"type": "string"
}
}
},
"mcp.DiscoveryResult": {
"type": "object",
"properties": {
"authorization_endpoint": {
"type": "string"
},
"authorization_server_url": {
"type": "string"
},
"registration_endpoint": {
"type": "string"
},
"resource_metadata_url": {
"type": "string"
},
"resource_uri": {
"type": "string"
},
"scopes_supported": {
"type": "array",
"items": {
"type": "string"
}
},
"token_endpoint": {
"type": "string"
}
}
},
"mcp.ExportResponse": {
"type": "object",
"properties": {
@@ -8801,6 +9140,44 @@
}
}
},
"mcp.OAuthStatus": {
"type": "object",
"properties": {
"auth_server": {
"type": "string"
},
"configured": {
"type": "boolean"
},
"expired": {
"type": "boolean"
},
"expires_at": {
"type": "string"
},
"has_token": {
"type": "boolean"
},
"scopes": {
"type": "string"
}
}
},
"mcp.ToolDescriptor": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"inputSchema": {
"type": "object",
"additionalProperties": {}
},
"name": {
"type": "string"
}
}
},
"mcp.UpsertRequest": {
"type": "object",
"properties": {
@@ -8810,6 +9187,9 @@
"type": "string"
}
},
"auth_type": {
"type": "string"
},
"command": {
"type": "string"
},
+250
View File
@@ -707,6 +707,8 @@ definitions:
type: object
github_com_memohai_memoh_internal_mcp.Connection:
properties:
auth_type:
type: string
bot_id:
type: string
config:
@@ -718,8 +720,18 @@ definitions:
type: string
is_active:
type: boolean
last_probed_at:
type: string
name:
type: string
status:
type: string
status_message:
type: string
tools_cache:
items:
$ref: '#/definitions/mcp.ToolDescriptor'
type: array
type:
type: string
updated_at:
@@ -980,6 +992,19 @@ definitions:
status:
type: string
type: object
handlers.ProbeResponse:
properties:
auth_required:
type: boolean
error:
type: string
status:
type: string
tools:
items:
$ref: '#/definitions/mcp.ToolDescriptor'
type: array
type: object
handlers.RefreshResponse:
properties:
access_token:
@@ -1142,6 +1167,16 @@ definitions:
type: string
type: array
type: object
handlers.oauthAuthorizeRequest:
properties:
client_id:
type: string
type: object
handlers.oauthDiscoverRequest:
properties:
url:
type: string
type: object
handlers.skillsOpResponse:
properties:
ok:
@@ -1237,6 +1272,30 @@ definitions:
source:
type: string
type: object
mcp.AuthorizeResult:
properties:
authorization_url:
type: string
type: object
mcp.DiscoveryResult:
properties:
authorization_endpoint:
type: string
authorization_server_url:
type: string
registration_endpoint:
type: string
resource_metadata_url:
type: string
resource_uri:
type: string
scopes_supported:
items:
type: string
type: array
token_endpoint:
type: string
type: object
mcp.ExportResponse:
properties:
mcpServers:
@@ -1281,12 +1340,39 @@ definitions:
url:
type: string
type: object
mcp.OAuthStatus:
properties:
auth_server:
type: string
configured:
type: boolean
expired:
type: boolean
expires_at:
type: string
has_token:
type: boolean
scopes:
type: string
type: object
mcp.ToolDescriptor:
properties:
description:
type: string
inputSchema:
additionalProperties: {}
type: object
name:
type: string
type: object
mcp.UpsertRequest:
properties:
args:
items:
type: string
type: array
auth_type:
type: string
command:
type: string
cwd:
@@ -2077,6 +2163,32 @@ info:
title: Memoh API
version: 1.0.0
paths:
/api/oauth/mcp/callback:
get:
description: Handles the OAuth authorization callback, exchanges code for tokens
parameters:
- description: Authorization code
in: query
name: code
required: true
type: string
- description: State parameter
in: query
name: state
required: true
type: string
responses:
"200":
description: HTML page that closes the popup
schema:
type: string
"400":
description: Bad Request
schema:
$ref: '#/definitions/handlers.ErrorResponse'
summary: OAuth callback handler
tags:
- mcp
/auth/login:
post:
description: Validate user credentials and issue a JWT
@@ -3553,6 +3665,144 @@ paths:
summary: Update MCP connection
tags:
- mcp
/bots/{bot_id}/mcp/{id}/oauth/authorize:
post:
description: Generate PKCE and return authorization URL for the user to authorize
parameters:
- description: MCP connection ID
in: path
name: id
required: true
type: string
- description: Optional client_id
in: body
name: payload
schema:
$ref: '#/definitions/handlers.oauthAuthorizeRequest'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/mcp.AuthorizeResult'
"400":
description: Bad Request
schema:
$ref: '#/definitions/handlers.ErrorResponse'
"404":
description: Not Found
schema:
$ref: '#/definitions/handlers.ErrorResponse'
summary: Start OAuth authorization flow
tags:
- mcp
/bots/{bot_id}/mcp/{id}/oauth/discover:
post:
description: Probe MCP server URL for OAuth requirements and discover authorization
server metadata
parameters:
- description: MCP connection ID
in: path
name: id
required: true
type: string
- description: Optional URL override
in: body
name: payload
schema:
$ref: '#/definitions/handlers.oauthDiscoverRequest'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/mcp.DiscoveryResult'
"400":
description: Bad Request
schema:
$ref: '#/definitions/handlers.ErrorResponse'
"404":
description: Not Found
schema:
$ref: '#/definitions/handlers.ErrorResponse'
summary: Discover OAuth configuration for MCP server
tags:
- mcp
/bots/{bot_id}/mcp/{id}/oauth/status:
get:
description: Returns the current OAuth status including whether tokens are available
parameters:
- description: MCP connection ID
in: path
name: id
required: true
type: string
responses:
"200":
description: OK
schema:
$ref: '#/definitions/mcp.OAuthStatus'
"400":
description: Bad Request
schema:
$ref: '#/definitions/handlers.ErrorResponse'
"404":
description: Not Found
schema:
$ref: '#/definitions/handlers.ErrorResponse'
summary: Get OAuth status for MCP connection
tags:
- mcp
/bots/{bot_id}/mcp/{id}/oauth/token:
delete:
description: Clears stored OAuth tokens
parameters:
- description: MCP connection ID
in: path
name: id
required: true
type: string
responses:
"204":
description: No Content
"400":
description: Bad Request
schema:
$ref: '#/definitions/handlers.ErrorResponse'
summary: Revoke OAuth tokens for MCP connection
tags:
- mcp
/bots/{bot_id}/mcp/{id}/probe:
post:
description: Probe a MCP connection to discover tools and verify connectivity
parameters:
- description: MCP connection ID
in: path
name: id
required: true
type: string
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handlers.ProbeResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/handlers.ErrorResponse'
"403":
description: Forbidden
schema:
$ref: '#/definitions/handlers.ErrorResponse'
"404":
description: Not Found
schema:
$ref: '#/definitions/handlers.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/handlers.ErrorResponse'
summary: Probe MCP connection
tags:
- mcp
/bots/{bot_id}/mcp/export:
get:
description: Export all MCP connections for a bot in standard mcpServers format.