- 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? ??)
266 lines
10 KiB
C#
266 lines
10 KiB
C#
namespace AxCopilot.Services.Agent;
|
|
|
|
/// <summary>
|
|
/// 사용 가능한 에이전트 도구/스킬을 관리하는 레지스트리.
|
|
/// 도구 목록을 LLM function calling에 전달하고, 이름으로 도구를 찾습니다.
|
|
/// </summary>
|
|
public class ToolRegistry : IDisposable
|
|
{
|
|
private readonly Dictionary<string, IAgentTool> _tools = new(StringComparer.OrdinalIgnoreCase);
|
|
private readonly List<IDisposable> _ownedResources = new();
|
|
private readonly Dictionary<string, McpClientService> _mcpClients = new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
/// <summary>등록된 모든 도구 목록.</summary>
|
|
public IReadOnlyCollection<IAgentTool> All => _tools.Values;
|
|
|
|
/// <summary>도구를 이름으로 찾습니다.</summary>
|
|
public IAgentTool? Get(string name) =>
|
|
_tools.TryGetValue(AgentToolCatalog.Canonicalize(name), out var tool) ? tool : null;
|
|
|
|
/// <summary>도구를 등록합니다.</summary>
|
|
public void Register(IAgentTool tool) => _tools[tool.Name] = tool;
|
|
|
|
public IReadOnlyCollection<McpClientService> GetMcpClients() => _mcpClients.Values.ToList().AsReadOnly();
|
|
|
|
public async Task<int> RegisterMcpToolsAsync(IEnumerable<Models.McpServerEntry>? servers, CancellationToken ct = default)
|
|
{
|
|
if (servers == null) return 0;
|
|
|
|
var registered = 0;
|
|
foreach (var server in servers)
|
|
{
|
|
if (server == null || !server.Enabled) continue;
|
|
|
|
if (!string.Equals(server.Transport, "stdio", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
LogService.Warn($"MCP '{server.Name}': unsupported transport '{server.Transport}'.");
|
|
continue;
|
|
}
|
|
|
|
var client = new McpClientService(server);
|
|
if (!await client.ConnectAsync(ct).ConfigureAwait(false))
|
|
{
|
|
client.Dispose();
|
|
continue;
|
|
}
|
|
|
|
_ownedResources.Add(client);
|
|
_mcpClients[server.Name] = client;
|
|
foreach (var def in client.Tools)
|
|
{
|
|
Register(new McpTool(client, def));
|
|
registered++;
|
|
}
|
|
}
|
|
|
|
McpSkillCatalog.RefreshFromClients(_mcpClients.Values);
|
|
return registered;
|
|
}
|
|
|
|
/// <summary>비활성 도구를 제외한 활성 도구 목록을 반환합니다.</summary>
|
|
public IReadOnlyCollection<IAgentTool> GetActiveTools(IEnumerable<string>? disabledNames = null)
|
|
{
|
|
if (disabledNames == null) return All;
|
|
var disabled = new HashSet<string>(AgentToolCatalog.CanonicalizeMany(disabledNames), StringComparer.OrdinalIgnoreCase);
|
|
if (disabled.Count == 0) return All;
|
|
return OrderToolsForExposure(_tools.Values.Where(t => !disabled.Contains(t.Name)))
|
|
.ToList()
|
|
.AsReadOnly();
|
|
}
|
|
|
|
/// <summary>비활성 도구를 제외하고 현재 탭에 해당하는 도구만 반환합니다.</summary>
|
|
public IReadOnlyCollection<IAgentTool> GetActiveToolsForTab(string activeTab, IEnumerable<string>? disabledNames = null)
|
|
{
|
|
var disabled = disabledNames != null
|
|
? new HashSet<string>(AgentToolCatalog.CanonicalizeMany(disabledNames), StringComparer.OrdinalIgnoreCase)
|
|
: null;
|
|
|
|
return OrderToolsForExposure(_tools.Values.Where(t =>
|
|
{
|
|
if (disabled != null && disabled.Contains(t.Name)) return false;
|
|
return IsToolAvailableForTab(t, activeTab);
|
|
})).ToList().AsReadOnly();
|
|
}
|
|
|
|
private static IEnumerable<IAgentTool> OrderToolsForExposure(IEnumerable<IAgentTool> tools)
|
|
{
|
|
return tools
|
|
.OrderBy(GetToolExposureBucket)
|
|
.ThenBy(t => t.Name, StringComparer.OrdinalIgnoreCase);
|
|
}
|
|
|
|
private static int GetToolExposureBucket(IAgentTool tool)
|
|
{
|
|
return AgentToolCatalog.GetExposureBucket(tool.Name);
|
|
}
|
|
|
|
/// <summary>도구가 해당 탭에서 사용 가능한지 확인합니다.</summary>
|
|
private static bool IsToolAvailableForTab(IAgentTool tool, string activeTab)
|
|
{
|
|
var category = ResolveTabCategory(tool);
|
|
if (string.IsNullOrEmpty(category)) return true; // null = 모든 탭
|
|
// 쉼표 구분 복수 탭: "Cowork,Code"
|
|
foreach (var part in category.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
|
{
|
|
if (string.Equals(part, activeTab, StringComparison.OrdinalIgnoreCase))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 도구별 탭 카테고리 오버라이드.
|
|
/// IAgentTool.TabCategory가 null인 도구는 이 맵을 참조합니다.
|
|
/// 키: 도구 이름, 값: 허용 탭 (쉼표 구분). 맵에 없으면 = 모든 탭.
|
|
/// </summary>
|
|
/// <summary>도구의 실질 탭 카테고리를 결정합니다 (IAgentTool.TabCategory → 오버라이드 맵 순).</summary>
|
|
private static string? ResolveTabCategory(IAgentTool tool)
|
|
{
|
|
// 도구 자체에 TabCategory가 명시되어 있으면 우선
|
|
if (!string.IsNullOrEmpty(tool.TabCategory))
|
|
return tool.TabCategory;
|
|
|
|
return AgentToolCatalog.GetTabCategory(tool.Name);
|
|
}
|
|
|
|
/// <summary>IDisposable 도구를 모두 해제합니다.</summary>
|
|
public void Dispose()
|
|
{
|
|
foreach (var tool in _tools.Values)
|
|
{
|
|
if (tool is IDisposable disposable)
|
|
disposable.Dispose();
|
|
}
|
|
foreach (var resource in _ownedResources)
|
|
resource.Dispose();
|
|
_ownedResources.Clear();
|
|
_mcpClients.Clear();
|
|
_tools.Clear();
|
|
}
|
|
|
|
/// <summary>기본 도구 + 내장 스킬을 모두 등록한 레지스트리를 생성합니다.</summary>
|
|
public static ToolRegistry CreateDefault()
|
|
{
|
|
var registry = new ToolRegistry();
|
|
|
|
// 기본 도구 (파일/검색/프로세스)
|
|
registry.Register(new FileReadTool());
|
|
registry.Register(new FileWriteTool());
|
|
registry.Register(new FileEditTool());
|
|
registry.Register(new GlobTool());
|
|
registry.Register(new GrepTool());
|
|
registry.Register(new ProcessTool());
|
|
registry.Register(new FolderMapTool());
|
|
registry.Register(new DocumentReaderTool());
|
|
|
|
// 내장 스킬 (문서 생성)
|
|
registry.Register(new ExcelSkill());
|
|
registry.Register(new DocxSkill());
|
|
registry.Register(new CsvSkill());
|
|
registry.Register(new MarkdownSkill());
|
|
registry.Register(new HtmlSkill());
|
|
registry.Register(new ChartSkill());
|
|
registry.Register(new BatchSkill());
|
|
registry.Register(new PptxSkill());
|
|
|
|
// 멀티패스 문서 엔진
|
|
registry.Register(new DocumentPlannerTool());
|
|
registry.Register(new DocumentAssemblerTool());
|
|
|
|
// 문서 품질 검증 & 포맷 변환
|
|
registry.Register(new DocumentReviewTool());
|
|
registry.Register(new FormatConvertTool());
|
|
|
|
// Code 탭: 개발 환경 감지 & 빌드/테스트 & Git
|
|
registry.Register(new DevEnvDetectTool());
|
|
registry.Register(new BuildRunTool());
|
|
registry.Register(new GitTool());
|
|
registry.Register(new LspTool());
|
|
registry.Register(new SubAgentTool());
|
|
registry.Register(new SpawnAgentsTool());
|
|
registry.Register(new WaitAgentsTool());
|
|
registry.Register(new CodeSearchTool());
|
|
registry.Register(new TestLoopTool());
|
|
registry.Register(new ToolSearchTool(() => registry.All));
|
|
|
|
// 코드 리뷰 + 프로젝트 규칙
|
|
registry.Register(new CodeReviewTool());
|
|
registry.Register(new ProjectRuleTool());
|
|
|
|
// 스킬 시스템
|
|
registry.Register(new SkillManagerTool());
|
|
|
|
// 에이전트 메모리
|
|
registry.Register(new MemoryTool());
|
|
|
|
// 데이터 처리 + 시스템 유틸리티
|
|
registry.Register(new JsonTool());
|
|
registry.Register(new RegexTool());
|
|
registry.Register(new DiffTool());
|
|
registry.Register(new ClipboardTool());
|
|
registry.Register(new NotifyTool());
|
|
registry.Register(new EnvTool());
|
|
registry.Register(new ZipTool());
|
|
registry.Register(new HttpTool());
|
|
registry.Register(new SqlTool());
|
|
registry.Register(new Base64Tool());
|
|
registry.Register(new HashTool());
|
|
registry.Register(new DateTimeTool());
|
|
|
|
// 코드 품질
|
|
registry.Register(new SnippetRunnerTool());
|
|
|
|
// 데이터 분석 + 문서 자동화
|
|
registry.Register(new DataPivotTool());
|
|
registry.Register(new TemplateRenderTool());
|
|
registry.Register(new TextSummarizeTool());
|
|
|
|
// 파일 모니터링 + 이미지 분석
|
|
registry.Register(new FileWatchTool());
|
|
registry.Register(new ImageAnalyzeTool());
|
|
|
|
// 파일 관리 + 메타데이터 + 멀티리드
|
|
registry.Register(new FileManageTool());
|
|
registry.Register(new FileInfoTool());
|
|
registry.Register(new MultiReadTool());
|
|
|
|
// 사용자 질문
|
|
registry.Register(new UserAskTool());
|
|
|
|
// MCP 리소스
|
|
registry.Register(new McpListResourcesTool(() => registry.GetMcpClients()));
|
|
registry.Register(new McpReadResourceTool(() => registry.GetMcpClients()));
|
|
|
|
// 외부 열기 + 수학 + XML + 인코딩
|
|
registry.Register(new OpenExternalTool());
|
|
registry.Register(new MathTool());
|
|
registry.Register(new XmlTool());
|
|
registry.Register(new EncodingTool());
|
|
|
|
// 태스크 추적
|
|
registry.Register(new TaskTrackerTool());
|
|
registry.Register(new TodoWriteTool());
|
|
registry.Register(new TaskCreateTool());
|
|
registry.Register(new TaskGetTool());
|
|
registry.Register(new TaskListTool());
|
|
registry.Register(new TaskUpdateTool());
|
|
registry.Register(new TaskStopTool());
|
|
registry.Register(new TaskOutputTool());
|
|
registry.Register(new EnterWorktreeTool());
|
|
registry.Register(new ExitWorktreeTool());
|
|
registry.Register(new TeamCreateTool());
|
|
registry.Register(new TeamDeleteTool());
|
|
registry.Register(new CronCreateTool());
|
|
registry.Register(new CronDeleteTool());
|
|
registry.Register(new CronListTool());
|
|
|
|
// 워크플로우 도구
|
|
registry.Register(new SuggestActionsTool());
|
|
registry.Register(new DiffPreviewTool());
|
|
registry.Register(new CheckpointTool());
|
|
registry.Register(new PlaybookTool());
|
|
|
|
return registry;
|
|
}
|
|
}
|