Browse Source

feat(ai): The MCP tool has added support for OAuth.

pull/1461/head
colin 1 day ago
parent
commit
548be22c0e
  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