Browse Source

Merge pull request #1461 from colinin/ai

feat(ai): The MCP tool has added support for OAuth.
dev
yx lin 20 hours ago
committed by GitHub
parent
commit
7f3c7e4ab2
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 152
      apps/vben5/packages/@abp/ai-management/src/components/tools/AIToolDefinitionModal.vue
  2. 84
      apps/vben5/packages/@abp/ai-management/src/components/tools/AIToolProperty.vue
  3. 2
      apps/vben5/packages/@abp/ai-management/src/types/tools.ts
  4. 31
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/DefaultMcpAuthorizationCodeProvider.cs
  5. 7
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/IMcpAuthorizationCodeProvider.cs
  6. 10
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/Localization/Resources/en.json
  7. 10
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/Localization/Resources/zh-Hans.json
  8. 87
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolDefinitionExtenssions.cs
  9. 66
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolProvider.cs
  10. 26
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAuthorizationCodeHandleContext.cs
  11. 9
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/Microsoft/Extensions/DependencyInjection/HttpClientMcpAIToolExtenssions.cs
  12. 13
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolPropertyDescriptor.cs
  13. 1
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/Dtos/AIToolPropertyDescriptorDto.cs
  14. 1
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application/LINGYUN/Abp/AIManagement/Tools/AIToolDefinitionAppService.cs

152
apps/vben5/packages/@abp/ai-management/src/components/tools/AIToolDefinitionModal.vue

@ -1,6 +1,4 @@
<script setup lang="ts">
import type { PropertyInfo } from '@abp/ui';
import type { CheckboxChangeEvent } from 'ant-design-vue/es/checkbox/interface';
import type { FormInstance } from 'ant-design-vue/lib/form';
import type {
DefaultOptionType,
@ -10,25 +8,25 @@ import type {
import type {
AIToolDefinitionRecordDto,
AIToolPropertyDescriptorDto,
AIToolProviderDto,
} from '../../types/tools';
import { computed, nextTick, ref, useTemplateRef, watch } from 'vue';
import {
computed,
defineAsyncComponent,
nextTick,
ref,
useTemplateRef,
watch,
} from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { useAuthorization } from '@abp/core';
import { LocalizableInput, PropertyTable, useMessage } from '@abp/ui';
import {
Checkbox,
Form,
FormItemRest,
Input,
InputNumber,
Select,
Tabs,
} from 'ant-design-vue';
import { LocalizableInput, useMessage } from '@abp/ui';
import { Checkbox, Form, Input, Select, Tabs } from 'ant-design-vue';
import { useAIToolsApi } from '../../api/useAIToolsApi';
import { AIToolDefinitionPermissions } from '../../constants/permissions';
@ -36,6 +34,11 @@ import { AIToolDefinitionPermissions } from '../../constants/permissions';
const emit = defineEmits<{
(event: 'change', data: AIToolDefinitionVto): void;
}>();
const AIToolProperty = defineAsyncComponent(
() => import('./AIToolProperty.vue'),
);
const FormItem = Form.Item;
const TabPane = Tabs.TabPane;
@ -84,6 +87,31 @@ const getIsAllowUpdate = computed<boolean>(() => {
}
return isGranted(AIToolDefinitionPermissions.Create);
});
const getParentProperties = computed<AIToolPropertyDescriptorDto[]>(() => {
if (!currentAIToolProvider.value) {
return [];
}
return currentAIToolProvider.value.properties.filter(
(p) => p.dependencies.length === 0,
);
});
const getDependencyProperties = computed<AIToolPropertyDescriptorDto[]>(() => {
if (!currentAIToolProvider.value || !formModel.value.extraProperties) {
return [];
}
const properties = currentAIToolProvider.value.properties.filter(
(p) => p.dependencies.length,
);
return properties.filter((p) => {
for (let index = 0; index < p.dependencies.length; index++) {
const depend = p.dependencies[index]!;
if (formModel.value.extraProperties[depend.name] === depend.value) {
return true;
}
}
return false;
});
});
watch(
() => getIsAllowUpdate.value,
@ -146,33 +174,28 @@ function onProviderChange(_: SelectValue, option: DefaultOptionType) {
if (provider) {
const propertites: Record<string, any> = {};
provider.properties.forEach((prop) => {
propertites[prop.name] = '';
switch (prop.valueType) {
case 'Boolean': {
propertites[prop.name] = false;
break;
}
case 'Dictionary': {
propertites[prop.name] = {};
break;
}
case 'Number':
case 'Select':
case 'String': {
propertites[prop.name] = undefined;
break;
}
}
});
formModel.value.extraProperties ??= {};
formModel.value.extraProperties = propertites;
}
}
function onBoolPropChange(key: string, e: CheckboxChangeEvent) {
const propertites = formModel.value.extraProperties ?? {};
propertites[key] = {};
propertites[key] = e.target.checked;
formModel.value.extraProperties = propertites;
}
function onPropChange(key: string, prop: PropertyInfo) {
const propertites = formModel.value.extraProperties ?? {};
propertites[key] = {};
propertites[key][prop.key] = prop.value;
formModel.value.extraProperties = propertites;
}
function onPropDelete(key: string, prop: PropertyInfo) {
const propertites = formModel.value.extraProperties ?? {};
propertites[key] = {};
delete propertites[key][prop.key];
formModel.value.extraProperties = propertites;
}
/** 提交表单 */
async function onSubmit() {
await form.value?.validate();
@ -251,74 +274,31 @@ async function onSubmit() {
force-render
>
<div class="h-[500px] overflow-y-auto rounded bg-gray-50 p-4">
<template
v-for="prop in currentAIToolProvider.properties"
:key="prop.name"
>
<template v-for="prop in getParentProperties" :key="prop.name">
<FormItem
v-if="prop.valueType === 'String'"
:extra="prop.description"
:label="prop.displayName"
:name="['extraProperties', prop.name]"
:required="prop.required"
>
<Input v-model:value="formModel.extraProperties[prop.name]" />
</FormItem>
<FormItem
v-if="prop.valueType === 'Number'"
:extra="prop.description"
:label="prop.displayName"
:name="['extraProperties', prop.name]"
:required="prop.required"
>
<InputNumber
class="w-full"
:min="0"
v-model:value="formModel.extraProperties[prop.name]"
<AIToolProperty
:property="prop"
v-model:model="formModel.extraProperties"
/>
</FormItem>
</template>
<template v-for="prop in getDependencyProperties" :key="prop.name">
<FormItem
v-else-if="prop.valueType === 'Boolean'"
:extra="prop.description"
:label="prop.displayName"
:name="['extraProperties', prop.name]"
:required="prop.required"
>
<Checkbox
:checked="formModel.extraProperties[prop.name] === true"
@change="onBoolPropChange(prop.name, $event)"
>
{{ prop.displayName }}
</Checkbox>
</FormItem>
<FormItem
v-else-if="prop.valueType === 'Select'"
:extra="prop.description"
:label="prop.displayName"
:name="['extraProperties', prop.name]"
:required="prop.required"
>
<Select
class="w-full"
:options="prop.options"
:field-names="{ label: 'name', value: 'value' }"
v-model:value="formModel.extraProperties[prop.name]"
<AIToolProperty
:property="prop"
v-model:model="formModel.extraProperties"
/>
</FormItem>
<FormItem
v-else-if="prop.valueType === 'Dictionary'"
:extra="prop.description"
:label="prop.displayName"
:name="['extraProperties', prop.name]"
>
<FormItemRest>
<PropertyTable
:data="formModel.extraProperties[prop.name]"
@change="onPropChange(prop.name, $event)"
@delete="onPropDelete(prop.name, $event)"
/>
</FormItemRest>
</FormItem>
</template>
</div>
</TabPane>

84
apps/vben5/packages/@abp/ai-management/src/components/tools/AIToolProperty.vue

@ -0,0 +1,84 @@
<script setup lang="ts">
import type { PropertyInfo } from '@abp/ui';
import type { AIToolPropertyDescriptorDto } from '../../types/tools';
import { PropertyTable } from '@abp/ui';
import {
Checkbox,
FormItemRest,
Input,
InputNumber,
Select,
} from 'ant-design-vue';
const props = defineProps<{
model: Record<string, any>;
property: AIToolPropertyDescriptorDto;
}>();
const emit = defineEmits<{
(event: 'change', data: Record<string, any>): void;
(event: 'update:value', data: Record<string, any>): void;
}>();
const onValueChange = (value?: any) => {
const prop = props.model;
prop[props.property.name] = value;
emit('change', prop);
emit('update:value', prop);
};
const onPropChange = (propertyInfo: PropertyInfo) => {
const prop = props.model;
prop[props.property.name] ??= {};
prop[props.property.name][propertyInfo.key] = propertyInfo.value;
emit('change', prop);
emit('update:value', prop);
};
const onPropDelete = (propertyInfo: PropertyInfo) => {
const prop = props.model;
prop[props.property.name] ??= {};
delete prop[props.property.name][propertyInfo.key];
emit('change', prop);
emit('update:value', prop);
};
</script>
<template>
<InputNumber
v-if="property.valueType === 'Number'"
class="w-full"
:min="0"
:value="model[property.name]"
@change="onValueChange($event)"
/>
<Input
v-if="property.valueType === 'String'"
class="w-full"
:min="0"
:value="model[property.name]"
@change="onValueChange($event.target.value)"
/>
<Checkbox
v-else-if="property.valueType === 'Boolean'"
:checked="model[property.name] === true"
@change="onValueChange($event.target.checked)"
>
{{ property.displayName }}
</Checkbox>
<Select
v-else-if="property.valueType === 'Select'"
class="w-full"
:options="property.options"
:field-names="{ label: 'name', value: 'value' }"
:value="model[property.name]"
@change="onValueChange($event)"
/>
<FormItemRest v-else-if="property.valueType === 'Dictionary'">
<PropertyTable
:data="model[property.name]"
@change="onPropChange"
@delete="onPropDelete"
/>
</FormItemRest>
</template>
<style scoped></style>

2
apps/vben5/packages/@abp/ai-management/src/types/tools.ts

@ -39,6 +39,7 @@ interface AIToolDefinitionRecordGetPagedListInput extends PagedAndSortedResultRe
}
interface AIToolPropertyDescriptorDto {
dependencies: NameValue<any>[];
description?: string;
displayName: string;
name: string;
@ -57,5 +58,6 @@ export type {
AIToolDefinitionRecordDto,
AIToolDefinitionRecordGetPagedListInput,
AIToolDefinitionRecordUpdateDto,
AIToolPropertyDescriptorDto,
AIToolProviderDto,
};

31
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<string?> 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;
}
}

7
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<string?> HandleAsync(McpAuthorizationCodeHandleContext context);
}

10
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"
}
}

10
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": "附加参数"
}
}

87
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<string, string>();
}
#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<string, string> parameters)
{
oAuthOptions.AdditionalAuthorizationParameters = parameters;
}
}
return oAuthOptions;
}
return null;
}
#endregion
}

66
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<AbpAIResource>("McpAITool:MaxReconnectionAttemptsDesc")),
AIToolPropertyDescriptor.CreateBoolProperty(
McpAIToolDefinitionExtenssions.CurrentAccessToken,
LocalizableString.Create<AbpAIResource>("McpAITool:CurrentAccessToken"))];
LocalizableString.Create<AbpAIResource>("McpAITool:CurrentAccessToken")),
AIToolPropertyDescriptor.CreateBoolProperty(
McpAIToolDefinitionExtenssions.UseOAuth,
LocalizableString.Create<AbpAIResource>("McpAITool:UseOAuth")),
AIToolPropertyDescriptor.CreateStringProperty(
McpAIToolDefinitionExtenssions.RedirectUri,
LocalizableString.Create<AbpAIResource>("McpAITool:RedirectUri"),
required: true)
.DependsOn(McpAIToolDefinitionExtenssions.UseOAuth, true),
AIToolPropertyDescriptor.CreateStringProperty(
McpAIToolDefinitionExtenssions.ClientId,
LocalizableString.Create<AbpAIResource>("McpAITool:ClientId"))
.DependsOn(McpAIToolDefinitionExtenssions.UseOAuth, true),
AIToolPropertyDescriptor.CreateStringProperty(
McpAIToolDefinitionExtenssions.ClientSecret,
LocalizableString.Create<AbpAIResource>("McpAITool:ClientSecret"))
.DependsOn(McpAIToolDefinitionExtenssions.UseOAuth, true),
AIToolPropertyDescriptor.CreateStringProperty(
McpAIToolDefinitionExtenssions.ClientMetadataDocumentUri,
LocalizableString.Create<AbpAIResource>("McpAITool:ClientMetadataDocumentUri"))
.DependsOn(McpAIToolDefinitionExtenssions.UseOAuth, true),
AIToolPropertyDescriptor.CreateStringProperty(
McpAIToolDefinitionExtenssions.Scopes,
LocalizableString.Create<AbpAIResource>("McpAITool:Scopes"),
LocalizableString.Create<AbpAIResource>("McpAITool:ScopesDesc"))
.DependsOn(McpAIToolDefinitionExtenssions.UseOAuth, true),
AIToolPropertyDescriptor.CreateDictionaryProperty(
McpAIToolDefinitionExtenssions.AuthorizationParameters,
LocalizableString.Create<AbpAIResource>("McpAITool:AdditionalParameters"))
.DependsOn(McpAIToolDefinitionExtenssions.UseOAuth, true)];
}
public async virtual Task<AITool[]> 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<IMcpAuthorizationCodeProvider>();
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<IAbpAccessTokenProvider>();
var token = await accessTokenProvider.GetTokenAsync();
if (!token.IsNullOrWhiteSpace())
{
// TODO: 使用OAuth配置?
httpClientTransportOptions.AdditionalHeaders.TryAdd("Authorization", $"Bearer {token}");
}
}

26
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;
}
}

9
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;
}

13
aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolPropertyDescriptor.cs

@ -21,6 +21,7 @@ public class AIToolPropertyDescriptor
public List<NameValue<object>> Options { get; }
public ILocalizableString DisplayName { get; }
public ILocalizableString? Description { get; }
public List<NameValue<object>> Dependencies { get; }
private AIToolPropertyDescriptor(
string name,
PropertyValueType valueType,
@ -35,6 +36,7 @@ public class AIToolPropertyDescriptor
Required = required;
Options = new List<NameValue<object>>();
Dependencies = new List<NameValue<object>>();
}
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<object>(name, value));
return this;
}
public AIToolPropertyDescriptor DependsOn(string name, object value)
{
Dependencies.Add(new NameValue<object>(name, value));
return this;
}
}

1
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Tools/Dtos/AIToolPropertyDescriptorDto.cs

@ -10,4 +10,5 @@ public class AIToolPropertyDescriptorDto
public List<NameValue<object>> Options { get; set; }
public string DisplayName { get; set; }
public string? Description { get; set; }
public List<NameValue<object>> Dependencies { get; set; }
}

1
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application/LINGYUN/Abp/AIManagement/Tools/AIToolDefinitionAppService.cs

@ -66,6 +66,7 @@ public class AIToolDefinitionAppService :
Required = prop.Required,
ValueType = prop.ValueType.ToString(),
DisplayName = prop.DisplayName.Localize(StringLocalizerFactory),
Dependencies = prop.Dependencies,
};
if (prop.Description != null)
{

Loading…
Cancel
Save