diff --git a/apps/vben5/packages/@abp/ai-management/src/components/tools/AIToolDefinitionModal.vue b/apps/vben5/packages/@abp/ai-management/src/components/tools/AIToolDefinitionModal.vue
index 3e885c2ff..cde5d0c58 100644
--- a/apps/vben5/packages/@abp/ai-management/src/components/tools/AIToolDefinitionModal.vue
+++ b/apps/vben5/packages/@abp/ai-management/src/components/tools/AIToolDefinitionModal.vue
@@ -1,6 +1,4 @@
+
+
+
+
+
+ {{ property.displayName }}
+
+
+
+
+
+
+
+
diff --git a/apps/vben5/packages/@abp/ai-management/src/types/tools.ts b/apps/vben5/packages/@abp/ai-management/src/types/tools.ts
index b7504e566..9ddcf5761 100644
--- a/apps/vben5/packages/@abp/ai-management/src/types/tools.ts
+++ b/apps/vben5/packages/@abp/ai-management/src/types/tools.ts
@@ -39,6 +39,7 @@ interface AIToolDefinitionRecordGetPagedListInput extends PagedAndSortedResultRe
}
interface AIToolPropertyDescriptorDto {
+ dependencies: NameValue[];
description?: string;
displayName: string;
name: string;
@@ -57,5 +58,6 @@ export type {
AIToolDefinitionRecordDto,
AIToolDefinitionRecordGetPagedListInput,
AIToolDefinitionRecordUpdateDto,
+ AIToolPropertyDescriptorDto,
AIToolProviderDto,
};
diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/DefaultMcpAuthorizationCodeProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/DefaultMcpAuthorizationCodeProvider.cs
new file mode 100644
index 000000000..102377f15
--- /dev/null
+++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/DefaultMcpAuthorizationCodeProvider.cs
@@ -0,0 +1,31 @@
+using System.Threading.Tasks;
+using System.Web;
+using Volo.Abp.DependencyInjection;
+
+namespace LINGYUN.Abp.AI.Tools.Mcp;
+
+[Dependency(TryRegister = true)]
+public class DefaultMcpAuthorizationCodeProvider : IMcpAuthorizationCodeProvider, ISingletonDependency
+{
+ public async virtual Task HandleAsync(McpAuthorizationCodeHandleContext context)
+ {
+ using var redirectResponse = await context.HttpClient.GetAsync(context.AuthorizationUrl, context.CancellationToken);
+ var location = redirectResponse.Headers.Location;
+
+ if (location is not null && !string.IsNullOrEmpty(location.Query))
+ {
+ // Parse query string to extract "code" parameter
+ var query = location.Query.TrimStart('?');
+ foreach (var pair in query.Split('&'))
+ {
+ var parts = pair.Split('=', 2);
+ if (parts.Length == 2 && parts[0] == "code")
+ {
+ return HttpUtility.UrlDecode(parts[1]);
+ }
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/IMcpAuthorizationCodeProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/IMcpAuthorizationCodeProvider.cs
new file mode 100644
index 000000000..132e71fb3
--- /dev/null
+++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/IMcpAuthorizationCodeProvider.cs
@@ -0,0 +1,7 @@
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.AI.Tools.Mcp;
+public interface IMcpAuthorizationCodeProvider
+{
+ Task HandleAsync(McpAuthorizationCodeHandleContext context);
+}
diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/Localization/Resources/en.json b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/Localization/Resources/en.json
index 668fef84b..82df26809 100644
--- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/Localization/Resources/en.json
+++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/Localization/Resources/en.json
@@ -9,6 +9,14 @@
"McpAITool:ConnectionTimeoutDesc": "The timeout used to establish the initial connection to the SSE server. The default is 30 seconds.",
"McpAITool:MaxReconnectionAttempts": "Max Reconnection Attempts",
"McpAITool:MaxReconnectionAttemptsDesc": "The maximum number of reconnection attempts. The default is 5.",
- "McpAITool:CurrentAccessToken": "Use the current user token"
+ "McpAITool:CurrentAccessToken": "Use the current user token",
+ "McpAITool:UseOAuth": "OAuth Authorization",
+ "McpAITool:RedirectUri": "Redirect Uri",
+ "McpAITool:ClientId": "Client Id",
+ "McpAITool:ClientSecret": "Client Secret",
+ "McpAITool:ClientMetadataDocumentUri": "Client Metadata Uri",
+ "McpAITool:Scopes": "Scopes",
+ "McpAITool:ScopesDesc": "Multiple authorization scopes are separated by spaces.",
+ "McpAITool:AdditionalParameters": "Additional Parameters"
}
}
\ No newline at end of file
diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/Localization/Resources/zh-Hans.json b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/Localization/Resources/zh-Hans.json
index 401f0ad7b..4d5ceee7a 100644
--- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/Localization/Resources/zh-Hans.json
+++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/Localization/Resources/zh-Hans.json
@@ -9,6 +9,14 @@
"McpAITool:ConnectionTimeoutDesc": "用于建立与 SSE 服务器初始连接的超时时间, 默认: 30秒.",
"McpAITool:MaxReconnectionAttempts": "最大重连次数",
"McpAITool:MaxReconnectionAttemptsDesc": "最大重连次数, 默认: 5次.",
- "McpAITool:CurrentAccessToken": "使用当前用户Token"
+ "McpAITool:CurrentAccessToken": "使用当前用户Token",
+ "McpAITool:UseOAuth": "使用OAuth认证",
+ "McpAITool:RedirectUri": "重定向Uri",
+ "McpAITool:ClientId": "客户端Id",
+ "McpAITool:ClientSecret": "客户端密钥",
+ "McpAITool:ClientMetadataDocumentUri": "发现文档Uri",
+ "McpAITool:Scopes": "授权范围",
+ "McpAITool:ScopesDesc": "多个授权范围以空格分隔.",
+ "McpAITool:AdditionalParameters": "附加参数"
}
}
\ No newline at end of file
diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolDefinitionExtenssions.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolDefinitionExtenssions.cs
index 9914af69c..ee1cc5af2 100644
--- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolDefinitionExtenssions.cs
+++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolDefinitionExtenssions.cs
@@ -1,4 +1,5 @@
-using ModelContextProtocol.Client;
+using ModelContextProtocol.Authentication;
+using ModelContextProtocol.Client;
using System;
using System.Collections.Generic;
using Volo.Abp;
@@ -147,4 +148,88 @@ public static class McpAIToolDefinitionExtenssions
return new Dictionary();
}
+
+ #region OAuth
+
+ public const string UseOAuth = "McpUseOAuth";
+ public const string RedirectUri = "McpRedirectUri";
+ public const string ClientId = "McpClientId";
+ public const string ClientSecret = "McpClientSecret";
+ public const string ClientMetadataDocumentUri = "McpClientMetadataDocumentUri";
+ public const string Scopes = "McpScopes";
+ public const string AuthorizationParameters = "McpAuthorizationParameters";
+
+ public static AIToolDefinition WithMcpOAuth(this AIToolDefinition definition, ClientOAuthOptions oAuthOptions)
+ {
+ Check.NotNull(oAuthOptions, nameof(oAuthOptions));
+
+ definition.WithProperty(UseOAuth, true);
+ definition.WithProperty(RedirectUri, oAuthOptions.RedirectUri.ToString());
+ if (!oAuthOptions.ClientId.IsNullOrWhiteSpace())
+ {
+ definition.WithProperty(ClientId, oAuthOptions.ClientId);
+ }
+ if (!oAuthOptions.ClientSecret.IsNullOrWhiteSpace())
+ {
+ definition.WithProperty(ClientSecret, oAuthOptions.ClientSecret);
+ }
+ if (oAuthOptions.ClientMetadataDocumentUri != null)
+ {
+ definition.WithProperty(ClientMetadataDocumentUri, oAuthOptions.ClientMetadataDocumentUri.ToString());
+ }
+ if (oAuthOptions.Scopes != null)
+ {
+ definition.WithProperty(Scopes, oAuthOptions.Scopes.JoinAsString(" "));
+ }
+ if (oAuthOptions.AdditionalAuthorizationParameters != null)
+ {
+ definition.WithProperty(AuthorizationParameters, oAuthOptions.AdditionalAuthorizationParameters);
+ }
+
+ return definition;
+ }
+
+ public static ClientOAuthOptions? GetMcpOAuth(this AIToolDefinition definition)
+ {
+ if (definition.Properties.TryGetValue(UseOAuth, out var useOAuthObj) && useOAuthObj is true &&
+ definition.Properties.TryGetValue(RedirectUri, out var redirectUriObj) && redirectUriObj != null)
+ {
+ var oAuthOptions = new ClientOAuthOptions
+ {
+ RedirectUri = new Uri(redirectUriObj.ToString()!)
+ };
+ if (definition.Properties.TryGetValue(ClientId, out var clientIdObj) && clientIdObj != null)
+ {
+ oAuthOptions.ClientId = clientIdObj.ToString();
+ }
+ if (definition.Properties.TryGetValue(ClientSecret, out var clientSecretObj) && clientSecretObj != null)
+ {
+ oAuthOptions.ClientSecret = clientSecretObj.ToString();
+ }
+ if (definition.Properties.TryGetValue(ClientMetadataDocumentUri, out var clientMetadataDocumentUriObj) && clientMetadataDocumentUriObj != null)
+ {
+ var clientMetadataDocumentUri = clientMetadataDocumentUriObj.ToString();
+ if (!clientMetadataDocumentUri.IsNullOrWhiteSpace())
+ {
+ oAuthOptions.ClientMetadataDocumentUri = new Uri(clientMetadataDocumentUri);
+ }
+ }
+ if (definition.Properties.TryGetValue(Scopes, out var scopesObj) && scopesObj != null)
+ {
+ oAuthOptions.Scopes = scopesObj.ToString()?.Split(" ");
+ }
+ if (definition.Properties.TryGetValue(AuthorizationParameters, out var parametersObj) && parametersObj != null)
+ {
+ if (parametersObj is IDictionary parameters)
+ {
+ oAuthOptions.AdditionalAuthorizationParameters = parameters;
+ }
+ }
+ return oAuthOptions;
+ }
+
+ return null;
+ }
+
+ #endregion
}
diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolProvider.cs
index e9ba2bd10..b78a9ab87 100644
--- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolProvider.cs
+++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolProvider.cs
@@ -2,12 +2,15 @@
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
+using ModelContextProtocol.Authentication;
using ModelContextProtocol.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
+using System.Threading;
using System.Threading.Tasks;
+using System.Web;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client.Authentication;
using Volo.Abp.Localization;
@@ -57,7 +60,37 @@ public class McpAIToolProvider : IAIToolProvider, ITransientDependency
LocalizableString.Create("McpAITool:MaxReconnectionAttemptsDesc")),
AIToolPropertyDescriptor.CreateBoolProperty(
McpAIToolDefinitionExtenssions.CurrentAccessToken,
- LocalizableString.Create("McpAITool:CurrentAccessToken"))];
+ LocalizableString.Create("McpAITool:CurrentAccessToken")),
+
+ AIToolPropertyDescriptor.CreateBoolProperty(
+ McpAIToolDefinitionExtenssions.UseOAuth,
+ LocalizableString.Create("McpAITool:UseOAuth")),
+ AIToolPropertyDescriptor.CreateStringProperty(
+ McpAIToolDefinitionExtenssions.RedirectUri,
+ LocalizableString.Create("McpAITool:RedirectUri"),
+ required: true)
+ .DependsOn(McpAIToolDefinitionExtenssions.UseOAuth, true),
+ AIToolPropertyDescriptor.CreateStringProperty(
+ McpAIToolDefinitionExtenssions.ClientId,
+ LocalizableString.Create("McpAITool:ClientId"))
+ .DependsOn(McpAIToolDefinitionExtenssions.UseOAuth, true),
+ AIToolPropertyDescriptor.CreateStringProperty(
+ McpAIToolDefinitionExtenssions.ClientSecret,
+ LocalizableString.Create("McpAITool:ClientSecret"))
+ .DependsOn(McpAIToolDefinitionExtenssions.UseOAuth, true),
+ AIToolPropertyDescriptor.CreateStringProperty(
+ McpAIToolDefinitionExtenssions.ClientMetadataDocumentUri,
+ LocalizableString.Create("McpAITool:ClientMetadataDocumentUri"))
+ .DependsOn(McpAIToolDefinitionExtenssions.UseOAuth, true),
+ AIToolPropertyDescriptor.CreateStringProperty(
+ McpAIToolDefinitionExtenssions.Scopes,
+ LocalizableString.Create("McpAITool:Scopes"),
+ LocalizableString.Create("McpAITool:ScopesDesc"))
+ .DependsOn(McpAIToolDefinitionExtenssions.UseOAuth, true),
+ AIToolPropertyDescriptor.CreateDictionaryProperty(
+ McpAIToolDefinitionExtenssions.AuthorizationParameters,
+ LocalizableString.Create("McpAITool:AdditionalParameters"))
+ .DependsOn(McpAIToolDefinitionExtenssions.UseOAuth, true)];
}
public async virtual Task CreateToolsAsync(AIToolDefinition definition)
@@ -80,14 +113,41 @@ public class McpAIToolProvider : IAIToolProvider, ITransientDependency
httpClientTransportOptions.AdditionalHeaders.TryAdd(header.Key, header.Value);
}
- if (definition.IsUseMcpCurrentAccessToken())
+ var oAuthOptions = definition.GetMcpOAuth();
+ if (oAuthOptions != null)
+ {
+ httpClientTransportOptions.OAuth = new ClientOAuthOptions
+ {
+ RedirectUri = oAuthOptions.RedirectUri,
+ ClientId = oAuthOptions.ClientId,
+ ClientSecret = oAuthOptions.ClientSecret,
+ ClientMetadataDocumentUri = oAuthOptions.ClientMetadataDocumentUri,
+ Scopes = oAuthOptions.Scopes,
+ AdditionalAuthorizationParameters = oAuthOptions.AdditionalAuthorizationParameters,
+ };
+ var authCodeProvider = ServiceProvider.GetService();
+ if (authCodeProvider != null)
+ {
+ httpClientTransportOptions.OAuth.AuthorizationRedirectDelegate = async (Uri authorizationUrl, Uri redirectUri, CancellationToken cancellationToken) =>
+ {
+ var authCodeHandleContext = new McpAuthorizationCodeHandleContext(
+ ServiceProvider,
+ httpClient,
+ authorizationUrl,
+ redirectUri,
+ cancellationToken);
+
+ return await authCodeProvider.HandleAsync(authCodeHandleContext);
+ };
+ }
+ }
+ else if (definition.IsUseMcpCurrentAccessToken())
{
var accessTokenProvider = ServiceProvider.GetRequiredService();
var token = await accessTokenProvider.GetTokenAsync();
if (!token.IsNullOrWhiteSpace())
{
- // TODO: 使用OAuth配置?
httpClientTransportOptions.AdditionalHeaders.TryAdd("Authorization", $"Bearer {token}");
}
}
diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAuthorizationCodeHandleContext.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAuthorizationCodeHandleContext.cs
new file mode 100644
index 000000000..4dee44868
--- /dev/null
+++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAuthorizationCodeHandleContext.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Net.Http;
+using System.Threading;
+
+namespace LINGYUN.Abp.AI.Tools.Mcp;
+public class McpAuthorizationCodeHandleContext
+{
+ public IServiceProvider ServiceProvider { get; }
+ public HttpClient HttpClient { get; }
+ public Uri AuthorizationUrl { get; }
+ public Uri RedirectUri { get; }
+ public CancellationToken CancellationToken { get; }
+ public McpAuthorizationCodeHandleContext(
+ IServiceProvider serviceProvider,
+ HttpClient httpClient,
+ Uri authorizationUrl,
+ Uri redirectUri,
+ CancellationToken cancellationToken)
+ {
+ ServiceProvider = serviceProvider;
+ HttpClient = httpClient;
+ AuthorizationUrl = authorizationUrl;
+ RedirectUri = redirectUri;
+ CancellationToken = cancellationToken;
+ }
+}
diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/Microsoft/Extensions/DependencyInjection/HttpClientMcpAIToolExtenssions.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/Microsoft/Extensions/DependencyInjection/HttpClientMcpAIToolExtenssions.cs
index 98d5bc1c8..b28581d44 100644
--- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/Microsoft/Extensions/DependencyInjection/HttpClientMcpAIToolExtenssions.cs
+++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/Microsoft/Extensions/DependencyInjection/HttpClientMcpAIToolExtenssions.cs
@@ -6,7 +6,14 @@ internal static class HttpClientMcpAIToolExtenssions
private const string McpAIToolClient = "__AbpAIMcpToolClient";
public static IServiceCollection AddMcpAIToolClient(this IServiceCollection services)
{
- services.AddHttpClient(McpAIToolClient);
+ services.AddHttpClient(McpAIToolClient)
+ .ConfigurePrimaryHttpMessageHandler(() =>
+ {
+ return new HttpClientHandler()
+ {
+ AllowAutoRedirect = false,
+ };
+ });
return services;
}
diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolPropertyDescriptor.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolPropertyDescriptor.cs
index 9d0194509..0ddc9ec51 100644
--- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolPropertyDescriptor.cs
+++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolPropertyDescriptor.cs
@@ -21,6 +21,7 @@ public class AIToolPropertyDescriptor
public List> Options { get; }
public ILocalizableString DisplayName { get; }
public ILocalizableString? Description { get; }
+ public List> Dependencies { get; }
private AIToolPropertyDescriptor(
string name,
PropertyValueType valueType,
@@ -35,6 +36,7 @@ public class AIToolPropertyDescriptor
Required = required;
Options = new List>();
+ Dependencies = new List>();
}
public static AIToolPropertyDescriptor CreateStringProperty(
@@ -113,8 +115,17 @@ public class AIToolPropertyDescriptor
return propertyDescriptor;
}
- public void WithOption(string name, object value)
+ public AIToolPropertyDescriptor WithOption(string name, object value)
{
Options.Add(new NameValue