Files
Enterprise-Knowledge-Base/client/mcp_service.go
T
wizardchen f2686e9e39 feat(mcp): drive auth by AuthType and auto-detect OAuth on test
Select the auth header for the configured strategy only, instead of
emitting both X-API-Key and Authorization whenever the fields happened
to be set (which double-authed after switching strategies). AuthType=""
keeps the legacy infer-from-fields behavior for pre-existing rows.

Add a configurable api_key_header (non-secret, defaults to X-API-Key) so
services expecting the key in a different header work without plaintext
custom headers.

On connection test, upgrade a bare 401 that advertises RFC 9728
protected-resource metadata into an explicit OAuthRequired signal so the
UI can guide the user to switch to OAuth 2.0. Surface the server's
description from the initialize handshake in the test result.
2026-06-29 22:37:52 +08:00

215 lines
6.8 KiB
Go

package client
import (
"context"
"encoding/json"
"fmt"
"net/http"
)
// MCPTransportType represents the transport type for MCP service
type MCPTransportType string
const (
MCPTransportSSE MCPTransportType = "sse"
MCPTransportHTTPStreamable MCPTransportType = "http-streamable"
MCPTransportStdio MCPTransportType = "stdio"
)
// MCPService represents an MCP service configuration
type MCPService struct {
ID string `json:"id"`
TenantID uint64 `json:"tenant_id"`
Name string `json:"name"`
Description string `json:"description"`
Enabled bool `json:"enabled"`
TransportType MCPTransportType `json:"transport_type"`
URL *string `json:"url,omitempty"`
Headers map[string]string `json:"headers"`
AuthConfig *MCPAuthConfig `json:"auth_config"`
AdvancedConfig *MCPAdvancedConfig `json:"advanced_config"`
StdioConfig *MCPStdioConfig `json:"stdio_config,omitempty"`
EnvVars map[string]string `json:"env_vars,omitempty"`
IsBuiltin bool `json:"is_builtin"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// MCPAuthConfig represents authentication configuration for MCP service.
//
// Secret fields (APIKey, Token) are accepted on create but are never returned
// by the server. To mutate credentials on an existing service, use the
// dedicated /credentials subresource — see the MCP credentials API for the
// PUT / DELETE shape. Sending secret fields in a main PUT body is silently
// ignored server-side.
type MCPAuthConfig struct {
APIKey string `json:"api_key,omitempty"`
Token string `json:"token,omitempty"`
CustomHeaders map[string]string `json:"custom_headers,omitempty"`
}
// MCPAdvancedConfig represents advanced configuration for MCP service
type MCPAdvancedConfig struct {
Timeout int `json:"timeout"`
RetryCount int `json:"retry_count"`
RetryDelay int `json:"retry_delay"`
}
// MCPStdioConfig represents stdio transport configuration
type MCPStdioConfig struct {
Command string `json:"command"`
Args []string `json:"args"`
}
// MCPTool represents a tool exposed by an MCP service
type MCPTool struct {
Name string `json:"name"`
Description string `json:"description"`
InputSchema json.RawMessage `json:"inputSchema"`
}
// MCPResource represents a resource exposed by an MCP service
type MCPResource struct {
URI string `json:"uri"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
MimeType string `json:"mimeType,omitempty"`
}
// MCPTestResult represents the result of testing an MCP service connection
type MCPTestResult struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
Description string `json:"description,omitempty"`
Tools []*MCPTool `json:"tools,omitempty"`
Resources []*MCPResource `json:"resources,omitempty"`
}
// CreateMCPService creates a new MCP service
func (c *Client) CreateMCPService(ctx context.Context, service *MCPService) (*MCPService, error) {
resp, err := c.doRequest(ctx, http.MethodPost, "/api/v1/mcp-services", service, nil)
if err != nil {
return nil, err
}
var result struct {
Success bool `json:"success"`
Data *MCPService `json:"data"`
}
if err := parseResponse(resp, &result); err != nil {
return nil, err
}
return result.Data, nil
}
// ListMCPServices lists all MCP services for the current tenant
func (c *Client) ListMCPServices(ctx context.Context) ([]*MCPService, error) {
resp, err := c.doRequest(ctx, http.MethodGet, "/api/v1/mcp-services", nil, nil)
if err != nil {
return nil, err
}
var result struct {
Success bool `json:"success"`
Data []*MCPService `json:"data"`
}
if err := parseResponse(resp, &result); err != nil {
return nil, err
}
return result.Data, nil
}
// GetMCPService gets an MCP service by ID
func (c *Client) GetMCPService(ctx context.Context, serviceID string) (*MCPService, error) {
resp, err := c.doRequest(ctx, http.MethodGet, fmt.Sprintf("/api/v1/mcp-services/%s", serviceID), nil, nil)
if err != nil {
return nil, err
}
var result struct {
Success bool `json:"success"`
Data *MCPService `json:"data"`
}
if err := parseResponse(resp, &result); err != nil {
return nil, err
}
return result.Data, nil
}
// UpdateMCPService updates an MCP service
func (c *Client) UpdateMCPService(ctx context.Context, serviceID string, updates map[string]interface{}) (*MCPService, error) {
resp, err := c.doRequest(ctx, http.MethodPut, fmt.Sprintf("/api/v1/mcp-services/%s", serviceID), updates, nil)
if err != nil {
return nil, err
}
var result struct {
Success bool `json:"success"`
Data *MCPService `json:"data"`
}
if err := parseResponse(resp, &result); err != nil {
return nil, err
}
return result.Data, nil
}
// DeleteMCPService deletes an MCP service
func (c *Client) DeleteMCPService(ctx context.Context, serviceID string) error {
resp, err := c.doRequest(ctx, http.MethodDelete, fmt.Sprintf("/api/v1/mcp-services/%s", serviceID), nil, nil)
if err != nil {
return err
}
return parseResponse(resp, nil)
}
// TestMCPService tests an MCP service connection
func (c *Client) TestMCPService(ctx context.Context, serviceID string) (*MCPTestResult, error) {
resp, err := c.doRequest(ctx, http.MethodPost, fmt.Sprintf("/api/v1/mcp-services/%s/test", serviceID), nil, nil)
if err != nil {
return nil, err
}
var result struct {
Success bool `json:"success"`
Data *MCPTestResult `json:"data"`
}
if err := parseResponse(resp, &result); err != nil {
return nil, err
}
return result.Data, nil
}
// GetMCPServiceTools gets the tools provided by an MCP service
func (c *Client) GetMCPServiceTools(ctx context.Context, serviceID string) ([]*MCPTool, error) {
resp, err := c.doRequest(ctx, http.MethodGet, fmt.Sprintf("/api/v1/mcp-services/%s/tools", serviceID), nil, nil)
if err != nil {
return nil, err
}
var result struct {
Success bool `json:"success"`
Data []*MCPTool `json:"data"`
}
if err := parseResponse(resp, &result); err != nil {
return nil, err
}
return result.Data, nil
}
// GetMCPServiceResources gets the resources provided by an MCP service
func (c *Client) GetMCPServiceResources(ctx context.Context, serviceID string) ([]*MCPResource, error) {
resp, err := c.doRequest(ctx, http.MethodGet, fmt.Sprintf("/api/v1/mcp-services/%s/resources", serviceID), nil, nil)
if err != nil {
return nil, err
}
var result struct {
Success bool `json:"success"`
Data []*MCPResource `json:"data"`
}
if err := parseResponse(resp, &result); err != nil {
return nil, err
}
return result.Data, nil
}