Skip to content

Commit a2cee99

Browse files
authored
Added SearchMCPServerPage (#7)
1 parent 031fb9a commit a2cee99

2 files changed

Lines changed: 176 additions & 1 deletion

File tree

src/NuGetPackageSearchCmdPalExtension/NuGetPackageSearchCmdPalExtensionCommandsProvider.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ public NuGetPackageSearchCmdPalExtensionCommandsProvider()
1919
new CommandItem(new Pages.SearchNuGetPackagesPage()) { Title = "Search NuGet Packages" },
2020
new CommandItem(new Pages.SearchDotnetTemplatesPage()) {Title = "Search Dotnet Templates"},
2121
new CommandItem(new Pages.UpdateDotnetTemplatesPage()) {Title = "Update Dotnet Templates"},
22-
new CommandItem(new Pages.SearchDotnetToolsPage()){Title = "Search Dotnet Tools"}
22+
new CommandItem(new Pages.SearchDotnetToolsPage()){Title = "Search Dotnet Tools"},
23+
new CommandItem(new Pages.SearchMCPServerPage()){Title = "Search MCP Servers"}
2324
];
2425
}
2526

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
using Microsoft.CommandPalette.Extensions;
2+
using Microsoft.CommandPalette.Extensions.Toolkit;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Diagnostics;
6+
using System.Net.Http;
7+
using System.Text;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using System.Threading.Tasks.Dataflow;
11+
using Windows.ApplicationModel;
12+
13+
namespace NuGetPackageSearchCmdPalExtension.Pages
14+
{
15+
internal sealed partial class SearchMCPServerPage : DynamicListPage, IDisposable
16+
{
17+
private bool _isError;
18+
private readonly CancellationTokenSource _cancellationTokenSource = new();
19+
private readonly BufferBlock<string> _searchTextBuffer = new();
20+
private IReadOnlyList<ListItem> _results = [];
21+
22+
public SearchMCPServerPage()
23+
{
24+
// Retrieve the app version
25+
var version = Package.Current.Id.Version;
26+
var appVersion = $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
27+
28+
Icon = new IconInfo("\uE773");
29+
Title = $"NuGet Package Search Extension - v{appVersion}";
30+
Name = "Search";
31+
32+
// Configure the search processing pipeline
33+
Task.Run(async () =>
34+
{
35+
while (await _searchTextBuffer.OutputAvailableAsync(_cancellationTokenSource.Token))
36+
{
37+
var searchText = await _searchTextBuffer.ReceiveAsync(_cancellationTokenSource.Token);
38+
IsLoading = true;
39+
try
40+
{
41+
_results = await ProcessSearchAsync(searchText, _cancellationTokenSource.Token);
42+
_isError = false;
43+
}
44+
catch
45+
{
46+
_isError = true;
47+
_results = [];
48+
}
49+
finally
50+
{
51+
IsLoading = false;
52+
RaiseItemsChanged(_results!.Count);
53+
}
54+
}
55+
}, _cancellationTokenSource.Token);
56+
}
57+
58+
public override IListItem[] GetItems()
59+
{
60+
return [.. _results];
61+
}
62+
63+
public override void UpdateSearchText(string oldSearch, string newSearch)
64+
{
65+
if (newSearch == oldSearch) return;
66+
_searchTextBuffer.Post(newSearch);
67+
}
68+
69+
public void Dispose()
70+
{
71+
_cancellationTokenSource.Cancel();
72+
_cancellationTokenSource.Dispose();
73+
}
74+
75+
public override ICommandItem? EmptyContent
76+
{
77+
get
78+
{
79+
if (_isError)
80+
{
81+
return new CommandItem
82+
{
83+
Title = "Error loading dotnet MCP Servers",
84+
Icon = new IconInfo("\uea39"),
85+
Subtitle = "An error occurred while fetching dotnet MCP Servers.",
86+
Command = new NoOpCommand()
87+
};
88+
}
89+
90+
return new CommandItem
91+
{
92+
Title = "Search for a dotnet MCP Servers",
93+
Icon = new IconInfo("\uea39"),
94+
Subtitle = "Search for a dotnet MCP Servers",
95+
Command = new NoOpCommand()
96+
}; ;
97+
}
98+
}
99+
100+
private static async Task<IReadOnlyList<ListItem>> ProcessSearchAsync(string searchText, CancellationToken cancellationToken)
101+
{
102+
await Task.Yield(); // Simulate asynchronous behavior
103+
104+
var url = $"https://azuresearch-usnc.nuget.org/query?take=20&packageType=mcpserver&q={Uri.EscapeDataString(searchText)}";
105+
using var httpClient = new HttpClient();
106+
using var response = await httpClient.GetAsync(url, cancellationToken);
107+
response.EnsureSuccessStatusCode();
108+
109+
var json = await response.Content.ReadAsStringAsync(cancellationToken);
110+
111+
using var doc = System.Text.Json.JsonDocument.Parse(json);
112+
var results = new List<ListItem>();
113+
114+
if (!doc.RootElement.TryGetProperty("data", out var dataElement) ||
115+
dataElement.ValueKind != System.Text.Json.JsonValueKind.Array) return results;
116+
117+
foreach (var package in dataElement.EnumerateArray())
118+
{
119+
var id = package.GetProperty("id").GetString();
120+
var version = package.GetProperty("version").GetString();
121+
string? iconUrl = null;
122+
if (package.TryGetProperty("iconUrl", out var iconUrlProp) && iconUrlProp.ValueKind == System.Text.Json.JsonValueKind.String)
123+
{
124+
iconUrl = iconUrlProp.GetString();
125+
}
126+
// Fallback to a glyph if iconUrl is missing
127+
var icon = !string.IsNullOrEmpty(iconUrl) ? new IconInfo(iconUrl) : new IconInfo("\uE7B8");
128+
results.Add(new ListItem
129+
{
130+
Title = id!,
131+
Subtitle = version!,
132+
Icon = icon,
133+
Command = new CopyTextCommand($"dotnet tool install --global {id} --version {version}") { Name = "Copy global install Command" },
134+
MoreCommands =
135+
[
136+
new CommandContextItem(new AnonymousCommand(() =>
137+
{
138+
var startInfo = new ProcessStartInfo
139+
{
140+
FileName = "cmd.exe",
141+
Arguments = $"/k dotnet tool install --global {id} --version {version}", // Use /k to keep the shell open
142+
UseShellExecute = true,
143+
CreateNoWindow = false
144+
};
145+
146+
using var process = new Process();
147+
process.StartInfo = startInfo;
148+
process.Start();
149+
process.WaitForExit();
150+
}){Icon = new IconInfo("\uE896"), Name = "Install tool globally"}),
151+
new CommandContextItem(new CopyTextCommand($"dotnet tool install --local {id} --version {version}"){Name = "Copy local install command"}),
152+
new CommandContextItem(new CopyTextCommand($"#tool dotnet:?package={id}&version={version}"){Name = "Copy cake tool"}),
153+
new CommandContextItem(new CopyTextCommand($"nuke :add-package {id} --version {version}"){Name = "Copy NUKE"}),
154+
new CommandContextItem(new CopyTextCommand($$"""
155+
{
156+
servers:'{
157+
{{id}}:'{
158+
type:"stdio",
159+
command:"dnx",
160+
args:["{{id}}@{{version}}", "--yes"]
161+
}
162+
}
163+
}
164+
""")
165+
{Name = "Copy MCP Server"}
166+
)
167+
]
168+
});
169+
}
170+
171+
return results;
172+
}
173+
}
174+
}

0 commit comments

Comments
 (0)