AX Agent ?? ?? ??? MCP ?? ??? ???? ??? ??? ??
- MCP ?? ?????? synthetic skill? ???? McpSkillCatalog? ???? ToolRegistry ?? snapshot ?? ??? ??? - managed/user/additional/project/plugin/mcp/legacy ?? source ??, plugin-only ??, source? inline shell trust boundary? SkillService/AppSettings/Settings UI? ??? - SlashCommandCatalog? ChatWindow?? builtin command? skill? ???? ???? ??? ?? ? ????? dedupe?? MCP ???? ? synthetic skill ?? ??? SkillGallery/AgentSettings? ??? - README.md? docs/DEVELOPMENT.md? 2026-04-14 19:13 (KST) ?? ?? ??? ?? ??? ??? - ??: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_phase4\\ -p:IntermediateOutputPath=obj\\verify_phase4\\ (?? 0, ?? 0) - ??: dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "SkillServiceRuntimePolicyTests|SlashCommandCatalogTests|McpSkillCatalogTests" -p:OutputPath=bin\\verify_phase4_tests\\ -p:IntermediateOutputPath=obj\\verify_phase4_tests\\ (?? 17, ?? WorkspaceContextGeneratorTests.cs nullable ?? 1? ??)
This commit is contained in:
59
src/AxCopilot.Tests/Services/McpSkillCatalogTests.cs
Normal file
59
src/AxCopilot.Tests/Services/McpSkillCatalogTests.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using AxCopilot.Models;
|
||||
using AxCopilot.Services.Agent;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace AxCopilot.Tests.Services;
|
||||
|
||||
public class McpSkillCatalogTests
|
||||
{
|
||||
[Fact]
|
||||
public void BuildSyntheticSkills_ShouldCreateMcpScopedSkill_ForEnabledServer()
|
||||
{
|
||||
var skills = McpSkillCatalog.BuildSyntheticSkills(
|
||||
[
|
||||
new McpServerEntry
|
||||
{
|
||||
Name = "chrome-devtools",
|
||||
Enabled = true,
|
||||
Transport = "stdio",
|
||||
Command = "npx"
|
||||
},
|
||||
new McpServerEntry
|
||||
{
|
||||
Name = "disabled-server",
|
||||
Enabled = false
|
||||
}
|
||||
]);
|
||||
|
||||
skills.Should().ContainSingle();
|
||||
var skill = skills[0];
|
||||
skill.Name.Should().Be("mcp:chrome-devtools");
|
||||
skill.SourceScope.Should().Be("mcp");
|
||||
skill.Label.Should().Be("chrome-devtools MCP");
|
||||
skill.AllowedTools.Should().Contain("mcp_list_resources");
|
||||
skill.AllowedTools.Should().Contain("mcp_read_resource");
|
||||
skill.SystemPrompt.Should().Contain("Use the MCP server");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeSignature_ShouldReflectEnabledServerConfiguration()
|
||||
{
|
||||
var servers = new[]
|
||||
{
|
||||
new McpServerEntry
|
||||
{
|
||||
Name = "docs",
|
||||
Enabled = true,
|
||||
Transport = "sse",
|
||||
Url = "https://intra.example.local/mcp/sse"
|
||||
}
|
||||
};
|
||||
|
||||
var signature = McpSkillCatalog.ComputeSignature(servers);
|
||||
|
||||
signature.Should().Contain("docs");
|
||||
signature.Should().Contain("sse");
|
||||
signature.Should().Contain("https://intra.example.local/mcp/sse");
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using AxCopilot.Services.Agent;
|
||||
using AxCopilot.Models;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
using System;
|
||||
@@ -303,4 +304,60 @@ public class SkillServiceRuntimePolicyTests
|
||||
try { if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsSkillSourceEnabled_ShouldRespectSourceFlags_AndPluginOnlyMode()
|
||||
{
|
||||
var llm = new LlmSettings
|
||||
{
|
||||
EnableManagedSkillSource = true,
|
||||
EnableUserSkillSource = false,
|
||||
EnableAdditionalSkillDiscovery = false,
|
||||
EnableProjectSkillDiscovery = true,
|
||||
EnablePluginSkillDiscovery = true,
|
||||
EnableMcpSkillDiscovery = false,
|
||||
EnableLegacyCommandSkills = false,
|
||||
EnablePluginOnlySkillMode = false,
|
||||
};
|
||||
|
||||
SkillService.IsSkillSourceEnabled("managed", llm).Should().BeTrue();
|
||||
SkillService.IsSkillSourceEnabled("user", llm).Should().BeFalse();
|
||||
SkillService.IsSkillSourceEnabled("additional", llm).Should().BeFalse();
|
||||
SkillService.IsSkillSourceEnabled("project", llm).Should().BeTrue();
|
||||
SkillService.IsSkillSourceEnabled("mcp", llm).Should().BeFalse();
|
||||
SkillService.IsSkillSourceEnabled("legacy", llm).Should().BeFalse();
|
||||
|
||||
llm.EnablePluginOnlySkillMode = true;
|
||||
|
||||
SkillService.IsSkillSourceEnabled("managed", llm).Should().BeTrue();
|
||||
SkillService.IsSkillSourceEnabled("plugin", llm).Should().BeTrue();
|
||||
SkillService.IsSkillSourceEnabled("project", llm).Should().BeFalse();
|
||||
SkillService.IsSkillSourceEnabled("mcp", llm).Should().BeFalse();
|
||||
SkillService.IsSkillSourceEnabled("bundled", llm).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetInlineShellBlockedReason_ShouldRespectSourceTrustBoundaries()
|
||||
{
|
||||
var llm = new LlmSettings
|
||||
{
|
||||
EnableSkillInlineShell = true,
|
||||
AllowPluginSkillInlineShell = false,
|
||||
AllowMcpSkillInlineShell = false,
|
||||
};
|
||||
|
||||
var pluginSkill = new SkillDefinition { Name = "plugin-a", SourceScope = "plugin" };
|
||||
var mcpSkill = new SkillDefinition { Name = "mcp-a", SourceScope = "mcp" };
|
||||
var projectSkill = new SkillDefinition { Name = "project-a", SourceScope = "project" };
|
||||
|
||||
SkillService.GetInlineShellBlockedReason(pluginSkill, llm).Should().Be("[inline-shell blocked for plugin skills]");
|
||||
SkillService.GetInlineShellBlockedReason(mcpSkill, llm).Should().Be("[inline-shell blocked for mcp skills]");
|
||||
SkillService.GetInlineShellBlockedReason(projectSkill, llm).Should().BeNull();
|
||||
|
||||
llm.AllowPluginSkillInlineShell = true;
|
||||
llm.AllowMcpSkillInlineShell = true;
|
||||
|
||||
SkillService.GetInlineShellBlockedReason(pluginSkill, llm).Should().BeNull();
|
||||
SkillService.GetInlineShellBlockedReason(mcpSkill, llm).Should().BeNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,4 +28,25 @@ public class SlashCommandCatalogTests
|
||||
SlashCommandCatalog.TryGetEntry("/mcp", out var mcpEntry).Should().BeTrue();
|
||||
mcpEntry.SystemPrompt.Should().Be("__MCP__");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComposeMatches_ShouldPreferHigherPriorityEntry_WhenCommandCollides()
|
||||
{
|
||||
var matches = SlashCommandCatalog.ComposeMatches(
|
||||
[
|
||||
("/review", "Project Review Skill", true, 900),
|
||||
("/review", "Code Review", false, 2100),
|
||||
("/verify-change", "Verify Skill", true, 600)
|
||||
]);
|
||||
|
||||
matches.Should().ContainSingle(x => x.Cmd == "/review");
|
||||
matches.Should().Contain(x => x.Cmd == "/review" && x.IsSkill == false && x.Label == "Code Review");
|
||||
matches.Should().Contain(x => x.Cmd == "/verify-change" && x.IsSkill);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetBuiltInCommandPriority_ShouldGiveDevCommandsHigherPriority()
|
||||
{
|
||||
SlashCommandCatalog.GetBuiltInCommandPriority("/review").Should().BeGreaterThan(SlashCommandCatalog.GetBuiltInCommandPriority("/clear"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user