| 1 | using System.Collections.Frozen; |
| 2 | using System.Diagnostics; |
| 3 | using System.Text.Json; |
| 4 | using AgentNotes.Core; |
| 5 | using AgentNotesMcp.Status; |
| 6 | using ModelContextProtocol.Protocol; |
| 7 | using ModelContextProtocol.Server; |
| 8 | |
| 9 | if (AgentNotesBootstrap.IsStatusOnly(args)) |
| 10 | return await AgentNotesStatusOnlyRunner.RunAsync(args); |
| 11 | |
| 12 | var startupCode = AgentNotesBootstrap.TryLoadSettings(args, out var localSettings, out var startupError); |
| 13 | if (startupCode != 0) |
| 14 | { |
| 15 | Console.Error.WriteLine(startupError); |
| 16 | return startupCode; |
| 17 | } |
| 18 | |
| 19 | AgentNotesRuntime.Initialize(localSettings!, AgentNotesBootstrap.LoadedConfigPath); |
| 20 | |
| 21 | var storage = new NotesStorage(); |
| 22 | var handlers = new ToolHandlers(storage); |
| 23 | var tools = ToolCatalog.Build(); |
| 24 | |
| 25 | var startedAt = DateTimeOffset.UtcNow; |
| 26 | AgentNotesStatusHost? statusHost = null; |
| 27 | if (AgentNotesStatusHost.TryStartBackground(storage, startedAt, out statusHost, out var statusUrl, out var statusError) |
| 28 | && statusUrl is not null) |
| 29 | { |
| 30 | Console.Error.WriteLine($"AgentNotesStatus: {statusUrl}"); |
| 31 | if (localSettings!.Status.PreviewWorkspace is { } preview) |
| 32 | AgentNotesStatusRuntimeFile.TryWrite(preview, statusUrl, AgentNotesBootstrap.LoadedConfigPath ?? ""); |
| 33 | } |
| 34 | else if (localSettings!.Status.Enabled && statusError is not null) |
| 35 | { |
| 36 | Console.Error.WriteLine($"AgentNotesStatus: failed to start ({statusError}). MCP continues without HTTP status."); |
| 37 | } |
| 38 | |
| 39 | var mcpVersion = typeof(Program).Assembly.GetName().Version?.ToString(3) ?? "2.1.0"; |
| 40 | var options = new McpServerOptions |
| 41 | { |
| 42 | ServerInfo = new Implementation { Name = "AgentNotesMcp", Version = mcpVersion }, |
| 43 | ProtocolVersion = "2024-11-05", |
| 44 | Capabilities = new ServerCapabilities |
| 45 | { |
| 46 | Tools = new ToolsCapability { ListChanged = false } |
| 47 | }, |
| 48 | Handlers = new McpServerHandlers |
| 49 | { |
| 50 | ListToolsHandler = (_, _) => ValueTask.FromResult(new ListToolsResult { Tools = tools }), |
| 51 | CallToolHandler = (request, _) => |
| 52 | { |
| 53 | var name = request.Params?.Name ?? ""; |
| 54 | var args = request.Params?.Arguments is IReadOnlyDictionary<string, JsonElement> providedArgs |
| 55 | ? providedArgs |
| 56 | : FrozenDictionary<string, JsonElement>.Empty; |
| 57 | |
| 58 | var sw = Stopwatch.StartNew(); |
| 59 | try |
| 60 | { |
| 61 | var text = handlers.Handle(name, args); |
| 62 | var isError = handlers.IsWriteLikeTool(name) && text != "OK" && !text.StartsWith("NO_CHANGES", StringComparison.Ordinal); |
| 63 | AgentNotesToolCallRingBuffer.Record(name, args, text, isError, sw.ElapsedMilliseconds); |
| 64 | |
| 65 | return ValueTask.FromResult(new CallToolResult |
| 66 | { |
| 67 | Content = [new TextContentBlock { Text = text }], |
| 68 | IsError = isError |
| 69 | }); |
| 70 | } |
| 71 | catch (ArgumentException ex) |
| 72 | { |
| 73 | var message = $"Error: {ex.Message}"; |
| 74 | AgentNotesToolCallRingBuffer.Record(name, args, message, isError: true, sw.ElapsedMilliseconds); |
| 75 | return ValueTask.FromResult(new CallToolResult |
| 76 | { |
| 77 | Content = [new TextContentBlock { Text = message }], |
| 78 | IsError = true |
| 79 | }); |
| 80 | } |
| 81 | catch (Exception ex) |
| 82 | { |
| 83 | var message = "Error: " + ex.Message; |
| 84 | AgentNotesToolCallRingBuffer.Record(name, args, message, isError: true, sw.ElapsedMilliseconds); |
| 85 | return ValueTask.FromResult(new CallToolResult |
| 86 | { |
| 87 | Content = [new TextContentBlock { Text = message }], |
| 88 | IsError = true |
| 89 | }); |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 | }; |
| 94 | |
| 95 | try |
| 96 | { |
| 97 | var transport = new StdioServerTransport("AgentNotesMcp"); |
| 98 | await using var server = McpServer.Create(transport, options); |
| 99 | await server.RunAsync(); |
| 100 | return 0; |
| 101 | } |
| 102 | finally |
| 103 | { |
| 104 | if (statusHost is not null) |
| 105 | await statusHost.StopAsync(); |
| 106 | } |
| 107 | |