diff --git a/Foundation.Data.Doublets.Cli/Foundation.Data.Doublets.Cli.csproj b/Foundation.Data.Doublets.Cli/Foundation.Data.Doublets.Cli.csproj index 1d8f9ab..ebb738f 100644 --- a/Foundation.Data.Doublets.Cli/Foundation.Data.Doublets.Cli.csproj +++ b/Foundation.Data.Doublets.Cli/Foundation.Data.Doublets.Cli.csproj @@ -26,6 +26,8 @@ + + diff --git a/Foundation.Data.Doublets.Cli/McpModels.cs b/Foundation.Data.Doublets.Cli/McpModels.cs new file mode 100644 index 0000000..4e553cd --- /dev/null +++ b/Foundation.Data.Doublets.Cli/McpModels.cs @@ -0,0 +1,248 @@ +using System.Text.Json.Serialization; + +namespace Foundation.Data.Doublets.Cli +{ + // Initialize request/response + public class McpInitializeParams + { + [JsonPropertyName("protocolVersion")] + public string ProtocolVersion { get; set; } = ""; + + [JsonPropertyName("capabilities")] + public McpClientCapabilities? Capabilities { get; set; } + + [JsonPropertyName("clientInfo")] + public McpClientInfo? ClientInfo { get; set; } + } + + public class McpInitializeResult + { + [JsonPropertyName("protocolVersion")] + public string ProtocolVersion { get; set; } = ""; + + [JsonPropertyName("serverInfo")] + public McpServerInfo? ServerInfo { get; set; } + + [JsonPropertyName("capabilities")] + public McpServerCapabilities? Capabilities { get; set; } + } + + public class McpClientInfo + { + [JsonPropertyName("name")] + public string? Name { get; set; } + + [JsonPropertyName("version")] + public string? Version { get; set; } + } + + public class McpServerInfo + { + [JsonPropertyName("name")] + public string? Name { get; set; } + + [JsonPropertyName("version")] + public string? Version { get; set; } + } + + public class McpClientCapabilities + { + [JsonPropertyName("experimental")] + public object? Experimental { get; set; } + + [JsonPropertyName("sampling")] + public object? Sampling { get; set; } + } + + public class McpServerCapabilities + { + [JsonPropertyName("experimental")] + public object? Experimental { get; set; } + + [JsonPropertyName("logging")] + public object? Logging { get; set; } + + [JsonPropertyName("prompts")] + public McpPromptsCapability? Prompts { get; set; } + + [JsonPropertyName("resources")] + public McpResourcesCapability? Resources { get; set; } + + [JsonPropertyName("tools")] + public McpToolsCapability? Tools { get; set; } + } + + public class McpPromptsCapability + { + [JsonPropertyName("listChanged")] + public bool ListChanged { get; set; } + } + + public class McpResourcesCapability + { + [JsonPropertyName("subscribe")] + public bool Subscribe { get; set; } + + [JsonPropertyName("listChanged")] + public bool ListChanged { get; set; } + } + + public class McpToolsCapability + { + [JsonPropertyName("listChanged")] + public bool ListChanged { get; set; } + } + + // Resources + public class McpResourcesListResult + { + [JsonPropertyName("resources")] + public McpResource[]? Resources { get; set; } + } + + public class McpResource + { + [JsonPropertyName("uri")] + public string Uri { get; set; } = ""; + + [JsonPropertyName("name")] + public string Name { get; set; } = ""; + + [JsonPropertyName("description")] + public string? Description { get; set; } + + [JsonPropertyName("mimeType")] + public string? MimeType { get; set; } + } + + public class McpResourcesReadParams + { + [JsonPropertyName("uri")] + public string Uri { get; set; } = ""; + } + + public class McpResourcesReadResult + { + [JsonPropertyName("contents")] + public McpResourceContent[]? Contents { get; set; } + } + + public class McpResourceContent + { + [JsonPropertyName("uri")] + public string Uri { get; set; } = ""; + + [JsonPropertyName("mimeType")] + public string? MimeType { get; set; } + + [JsonPropertyName("text")] + public string? Text { get; set; } + + [JsonPropertyName("blob")] + public byte[]? Blob { get; set; } + } + + // Tools + public class McpToolsListResult + { + [JsonPropertyName("tools")] + public McpTool[]? Tools { get; set; } + } + + public class McpTool + { + [JsonPropertyName("name")] + public string Name { get; set; } = ""; + + [JsonPropertyName("description")] + public string? Description { get; set; } + + [JsonPropertyName("inputSchema")] + public object? InputSchema { get; set; } + } + + public class McpToolsCallParams + { + [JsonPropertyName("name")] + public string Name { get; set; } = ""; + + [JsonPropertyName("arguments")] + public System.Text.Json.JsonElement Arguments { get; set; } + } + + public class McpToolsCallResult + { + [JsonPropertyName("content")] + public McpContent[]? Content { get; set; } + + [JsonPropertyName("isError")] + public bool IsError { get; set; } + } + + public class McpContent + { + [JsonPropertyName("type")] + public string Type { get; set; } = ""; + + [JsonPropertyName("text")] + public string? Text { get; set; } + } + + // Prompts + public class McpPromptsListResult + { + [JsonPropertyName("prompts")] + public McpPrompt[]? Prompts { get; set; } + } + + public class McpPrompt + { + [JsonPropertyName("name")] + public string Name { get; set; } = ""; + + [JsonPropertyName("description")] + public string? Description { get; set; } + + [JsonPropertyName("arguments")] + public McpPromptArgument[]? Arguments { get; set; } + } + + public class McpPromptArgument + { + [JsonPropertyName("name")] + public string Name { get; set; } = ""; + + [JsonPropertyName("description")] + public string? Description { get; set; } + + [JsonPropertyName("required")] + public bool Required { get; set; } + } + + public class McpPromptsGetParams + { + [JsonPropertyName("name")] + public string Name { get; set; } = ""; + + [JsonPropertyName("arguments")] + public Dictionary? Arguments { get; set; } + } + + public class McpPromptsGetResult + { + [JsonPropertyName("description")] + public string? Description { get; set; } + + [JsonPropertyName("messages")] + public McpPromptMessage[]? Messages { get; set; } + } + + public class McpPromptMessage + { + [JsonPropertyName("role")] + public string Role { get; set; } = ""; + + [JsonPropertyName("content")] + public McpContent? Content { get; set; } + } +} \ No newline at end of file diff --git a/Foundation.Data.Doublets.Cli/McpServer.cs b/Foundation.Data.Doublets.Cli/McpServer.cs new file mode 100644 index 0000000..3a02a7f --- /dev/null +++ b/Foundation.Data.Doublets.Cli/McpServer.cs @@ -0,0 +1,575 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using StreamJsonRpc; +using Platform.Data.Doublets; +using Platform.Data; +using DoubletLink = Platform.Data.Doublets.Link; + +namespace Foundation.Data.Doublets.Cli +{ + public class McpServer + { + private readonly NamedLinksDecorator _links; + private readonly bool _tracingEnabled; + + public McpServer(NamedLinksDecorator links, bool tracingEnabled = false) + { + _links = links; + _tracingEnabled = tracingEnabled; + } + + // MCP Initialization + [JsonRpcMethod("initialize")] + public McpInitializeResult Initialize(McpInitializeParams parameters) + { + if (_tracingEnabled) + Console.WriteLine($"[MCP] Initialize called with clientInfo: {parameters.ClientInfo?.Name}"); + + return new McpInitializeResult + { + ProtocolVersion = "2024-11-05", + ServerInfo = new McpServerInfo + { + Name = "link-cli-mcp", + Version = "1.0.0" + }, + Capabilities = new McpServerCapabilities + { + Resources = new McpResourcesCapability + { + Subscribe = false, + ListChanged = false + }, + Tools = new McpToolsCapability + { + ListChanged = false + }, + Prompts = new McpPromptsCapability + { + ListChanged = false + } + } + }; + } + + // Resources: Expose links data for reading + [JsonRpcMethod("resources/list")] + public McpResourcesListResult ListResources() + { + if (_tracingEnabled) + Console.WriteLine("[MCP] resources/list called"); + + return new McpResourcesListResult + { + Resources = new[] + { + new McpResource + { + Uri = "memory://links/all", + Name = "All Memory Links", + Description = "All links stored in the neural network memory", + MimeType = "application/json" + }, + new McpResource + { + Uri = "memory://links/search", + Name = "Search Memory Links", + Description = "Search links by pattern or content", + MimeType = "application/json" + } + } + }; + } + + [JsonRpcMethod("resources/read")] + public McpResourcesReadResult ReadResource(McpResourcesReadParams parameters) + { + if (_tracingEnabled) + Console.WriteLine($"[MCP] resources/read called for: {parameters.Uri}"); + + return parameters.Uri switch + { + "memory://links/all" => ReadAllLinks(), + "memory://links/search" => ReadSearchableLinks(), + _ => throw new InvalidOperationException($"Unknown resource: {parameters.Uri}") + }; + } + + private McpResourcesReadResult ReadAllLinks() + { + var linksList = new List(); + var any = _links.Constants.Any; + var query = new DoubletLink(index: any, source: any, target: any); + + _links.Each(query, link => + { + var doubletLink = new DoubletLink(link); + var name = _links.GetName(doubletLink.Index); + linksList.Add(new + { + id = doubletLink.Index.ToString(), + source = doubletLink.Source.ToString(), + target = doubletLink.Target.ToString(), + name = name, + formatted = _links.Format(link) + }); + return _links.Constants.Continue; + }); + + return new McpResourcesReadResult + { + Contents = new[] + { + new McpResourceContent + { + Uri = "memory://links/all", + MimeType = "application/json", + Text = JsonSerializer.Serialize(new { links = linksList }, new JsonSerializerOptions { WriteIndented = true }) + } + } + }; + } + + private McpResourcesReadResult ReadSearchableLinks() + { + var searchInfo = new + { + description = "Use the search_memory tool to find specific links by pattern or content", + usage = "Call the search_memory tool with your search criteria" + }; + + return new McpResourcesReadResult + { + Contents = new[] + { + new McpResourceContent + { + Uri = "memory://links/search", + MimeType = "application/json", + Text = JsonSerializer.Serialize(searchInfo, new JsonSerializerOptions { WriteIndented = true }) + } + } + }; + } + + // Tools: Expose CRUD operations + [JsonRpcMethod("tools/list")] + public McpToolsListResult ListTools() + { + if (_tracingEnabled) + Console.WriteLine("[MCP] tools/list called"); + + return new McpToolsListResult + { + Tools = new[] + { + new McpTool + { + Name = "store_memory", + Description = "Store information as links in neural network memory", + InputSchema = new + { + type = "object", + properties = new + { + content = new { type = "string", description = "Content to store in memory" }, + name = new { type = "string", description = "Optional name/label for the memory" }, + source = new { type = "string", description = "Optional source link ID" }, + target = new { type = "string", description = "Optional target link ID" } + }, + required = new[] { "content" } + } + }, + new McpTool + { + Name = "search_memory", + Description = "Search for information in neural network memory", + InputSchema = new + { + type = "object", + properties = new + { + query = new { type = "string", description = "Search query or pattern" }, + name = new { type = "string", description = "Search by name/label" }, + source = new { type = "string", description = "Filter by source link ID" }, + target = new { type = "string", description = "Filter by target link ID" } + } + } + }, + new McpTool + { + Name = "update_memory", + Description = "Update existing memory links", + InputSchema = new + { + type = "object", + properties = new + { + id = new { type = "string", description = "ID of the link to update" }, + source = new { type = "string", description = "New source link ID" }, + target = new { type = "string", description = "New target link ID" }, + name = new { type = "string", description = "New name/label" } + }, + required = new[] { "id" } + } + }, + new McpTool + { + Name = "delete_memory", + Description = "Delete memory links", + InputSchema = new + { + type = "object", + properties = new + { + id = new { type = "string", description = "ID of the link to delete" }, + source = new { type = "string", description = "Delete by source link ID" }, + target = new { type = "string", description = "Delete by target link ID" }, + name = new { type = "string", description = "Delete by name/label" } + } + } + } + } + }; + } + + [JsonRpcMethod("tools/call")] + public McpToolsCallResult CallTool(McpToolsCallParams parameters) + { + if (_tracingEnabled) + Console.WriteLine($"[MCP] tools/call called: {parameters.Name}"); + + return parameters.Name switch + { + "store_memory" => StoreMemory(parameters.Arguments), + "search_memory" => SearchMemory(parameters.Arguments), + "update_memory" => UpdateMemory(parameters.Arguments), + "delete_memory" => DeleteMemory(parameters.Arguments), + _ => throw new InvalidOperationException($"Unknown tool: {parameters.Name}") + }; + } + + private McpToolsCallResult StoreMemory(JsonElement arguments) + { + var content = arguments.GetProperty("content").GetString() ?? throw new ArgumentException("content is required"); + var name = arguments.TryGetProperty("name", out var nameElement) ? nameElement.GetString() : null; + var sourceStr = arguments.TryGetProperty("source", out var sourceElement) ? sourceElement.GetString() : null; + var targetStr = arguments.TryGetProperty("target", out var targetElement) ? targetElement.GetString() : null; + + uint source = sourceStr != null && uint.TryParse(sourceStr, out var s) ? s : 1; + uint target = targetStr != null && uint.TryParse(targetStr, out var t) ? t : 1; + + // Create the link + var createdLink = _links.GetOrCreate(source, target); + + // Set name if provided + if (!string.IsNullOrWhiteSpace(name)) + { + _links.SetName(createdLink, name); + } + + return new McpToolsCallResult + { + Content = new[] + { + new McpContent + { + Type = "text", + Text = $"Memory stored successfully. Link ID: {createdLink}, Content: {content}" + + (name != null ? $", Name: {name}" : "") + } + } + }; + } + + private McpToolsCallResult SearchMemory(JsonElement arguments) + { + var results = new List(); + var any = _links.Constants.Any; + + // Handle search by name + if (arguments.TryGetProperty("name", out var nameElement) && nameElement.ValueKind == JsonValueKind.String) + { + var searchName = nameElement.GetString()!; + var linkId = _links.GetByName(searchName); + if (!_links.Constants.Null.Equals(linkId)) + { + var linkData = _links.GetLink(linkId); + var link = new DoubletLink(linkData); + results.Add(new + { + id = link.Index.ToString(), + source = link.Source.ToString(), + target = link.Target.ToString(), + name = _links.GetName(link.Index), + formatted = _links.Format(linkData) + }); + } + } + else + { + // Search all links + var query = new DoubletLink(index: any, source: any, target: any); + _links.Each(query, link => + { + var doubletLink = new DoubletLink(link); + var linkName = _links.GetName(doubletLink.Index); + var formatted = _links.Format(link); + + // Apply filters if provided + bool matches = true; + + if (arguments.TryGetProperty("query", out var queryElement) && queryElement.ValueKind == JsonValueKind.String) + { + var searchQuery = queryElement.GetString()!; + matches = linkName?.Contains(searchQuery, StringComparison.OrdinalIgnoreCase) == true || + formatted.Contains(searchQuery, StringComparison.OrdinalIgnoreCase); + } + + if (matches) + { + results.Add(new + { + id = doubletLink.Index.ToString(), + source = doubletLink.Source.ToString(), + target = doubletLink.Target.ToString(), + name = linkName, + formatted = formatted + }); + } + + return _links.Constants.Continue; + }); + } + + return new McpToolsCallResult + { + Content = new[] + { + new McpContent + { + Type = "text", + Text = $"Found {results.Count} memory links:\n" + + JsonSerializer.Serialize(results, new JsonSerializerOptions { WriteIndented = true }) + } + } + }; + } + + private McpToolsCallResult UpdateMemory(JsonElement arguments) + { + if (!arguments.TryGetProperty("id", out var idElement) || !uint.TryParse(idElement.GetString(), out var linkId)) + { + throw new ArgumentException("Valid link id is required"); + } + + var existingLinkData = _links.GetLink(linkId); + var existingLink = new DoubletLink(existingLinkData); + var newSource = existingLink.Source; + var newTarget = existingLink.Target; + + if (arguments.TryGetProperty("source", out var sourceElement) && uint.TryParse(sourceElement.GetString(), out var s)) + newSource = s; + + if (arguments.TryGetProperty("target", out var targetElement) && uint.TryParse(targetElement.GetString(), out var t)) + newTarget = t; + + // Update the link + var restriction = new DoubletLink(linkId, existingLink.Source, existingLink.Target); + var substitution = new DoubletLink(linkId, newSource, newTarget); + _links.Update(restriction, substitution, null); + + // Update name if provided + if (arguments.TryGetProperty("name", out var nameElement) && nameElement.ValueKind == JsonValueKind.String) + { + var newName = nameElement.GetString()!; + _links.SetName(linkId, newName); + } + + return new McpToolsCallResult + { + Content = new[] + { + new McpContent + { + Type = "text", + Text = $"Memory link {linkId} updated successfully" + } + } + }; + } + + private McpToolsCallResult DeleteMemory(JsonElement arguments) + { + if (arguments.TryGetProperty("id", out var idElement) && uint.TryParse(idElement.GetString(), out var linkId)) + { + var linkData = _links.GetLink(linkId); + var link = new DoubletLink(linkData); + var restriction = new DoubletLink(linkId, link.Source, link.Target); + _links.Delete(restriction, null); + + return new McpToolsCallResult + { + Content = new[] + { + new McpContent + { + Type = "text", + Text = $"Memory link {linkId} deleted successfully" + } + } + }; + } + else if (arguments.TryGetProperty("name", out var nameElement) && nameElement.ValueKind == JsonValueKind.String) + { + var name = nameElement.GetString()!; + var deleteLinkId = _links.GetByName(name); + if (!_links.Constants.Null.Equals(deleteLinkId)) + { + var linkData = _links.GetLink(deleteLinkId); + var link = new DoubletLink(linkData); + var restriction = new DoubletLink(deleteLinkId, link.Source, link.Target); + _links.Delete(restriction, null); + + return new McpToolsCallResult + { + Content = new[] + { + new McpContent + { + Type = "text", + Text = $"Memory link '{name}' deleted successfully" + } + } + }; + } + else + { + return new McpToolsCallResult + { + Content = new[] + { + new McpContent + { + Type = "text", + Text = $"Memory link with name '{name}' not found" + } + } + }; + } + } + + throw new ArgumentException("Either 'id' or 'name' is required for deletion"); + } + + // Prompts: Common neural network memory operations + [JsonRpcMethod("prompts/list")] + public McpPromptsListResult ListPrompts() + { + if (_tracingEnabled) + Console.WriteLine("[MCP] prompts/list called"); + + return new McpPromptsListResult + { + Prompts = new[] + { + new McpPrompt + { + Name = "remember_context", + Description = "Store conversational context in neural network memory", + Arguments = new[] + { + new McpPromptArgument + { + Name = "context", + Description = "The context or information to remember", + Required = true + }, + new McpPromptArgument + { + Name = "importance", + Description = "Importance level (1-10)", + Required = false + } + } + }, + new McpPrompt + { + Name = "recall_similar", + Description = "Find similar memories based on content", + Arguments = new[] + { + new McpPromptArgument + { + Name = "query", + Description = "What to search for in memory", + Required = true + } + } + } + } + }; + } + + [JsonRpcMethod("prompts/get")] + public McpPromptsGetResult GetPrompt(McpPromptsGetParams parameters) + { + if (_tracingEnabled) + Console.WriteLine($"[MCP] prompts/get called: {parameters.Name}"); + + return parameters.Name switch + { + "remember_context" => GetRememberContextPrompt(parameters.Arguments), + "recall_similar" => GetRecallSimilarPrompt(parameters.Arguments), + _ => throw new InvalidOperationException($"Unknown prompt: {parameters.Name}") + }; + } + + private McpPromptsGetResult GetRememberContextPrompt(Dictionary? arguments) + { + var context = arguments?.GetValueOrDefault("context", ""); + var importance = arguments?.GetValueOrDefault("importance", "5"); + + return new McpPromptsGetResult + { + Description = "Store important context in neural network memory for future reference", + Messages = new[] + { + new McpPromptMessage + { + Role = "user", + Content = new McpContent + { + Type = "text", + Text = $"Please store this context in memory with importance level {importance}: {context}\n\n" + + "Use the store_memory tool to save this information so it can be recalled later." + } + } + } + }; + } + + private McpPromptsGetResult GetRecallSimilarPrompt(Dictionary? arguments) + { + var query = arguments?.GetValueOrDefault("query", ""); + + return new McpPromptsGetResult + { + Description = "Search for similar memories based on the provided query", + Messages = new[] + { + new McpPromptMessage + { + Role = "user", + Content = new McpContent + { + Type = "text", + Text = $"Please search my memory for information related to: {query}\n\n" + + "Use the search_memory tool to find relevant stored information." + } + } + } + }; + } + } +} \ No newline at end of file diff --git a/Foundation.Data.Doublets.Cli/Program.cs b/Foundation.Data.Doublets.Cli/Program.cs index 1f9bfed..a142082 100644 --- a/Foundation.Data.Doublets.Cli/Program.cs +++ b/Foundation.Data.Doublets.Cli/Program.cs @@ -9,6 +9,7 @@ using QueryProcessor = Foundation.Data.Doublets.Cli.AdvancedMixedQueryProcessor; using Foundation.Data.Doublets.Cli; using System.Text.RegularExpressions; +using StreamJsonRpc; const string defaultDatabaseFilename = "db.links"; @@ -70,6 +71,13 @@ afterOption.AddAlias("--links"); afterOption.AddAlias("-a"); +var mcpServerOption = new Option( + name: "--mcp-server", + description: "Start MCP (Model Context Protocol) server for neural network memory access", + getDefaultValue: () => false +); +mcpServerOption.AddAlias("--mcp"); + var rootCommand = new RootCommand("LiNo CLI Tool for managing links data store") { dbOption, @@ -79,79 +87,113 @@ structureOption, beforeOption, changesOption, - afterOption + afterOption, + mcpServerOption }; -rootCommand.SetHandler( - (string db, string queryOptionValue, string queryArgumentValue, bool trace, uint? structure, bool before, bool changes, bool after) => +rootCommand.SetHandler(context => +{ + var db = context.ParseResult.GetValueForOption(dbOption)!; + var queryOptionValue = context.ParseResult.GetValueForOption(queryOption) ?? ""; + var queryArgumentValue = context.ParseResult.GetValueForArgument(queryArgument) ?? ""; + var trace = context.ParseResult.GetValueForOption(traceOption); + var structure = context.ParseResult.GetValueForOption(structureOption); + var before = context.ParseResult.GetValueForOption(beforeOption); + var changes = context.ParseResult.GetValueForOption(changesOption); + var after = context.ParseResult.GetValueForOption(afterOption); + var mcpServer = context.ParseResult.GetValueForOption(mcpServerOption); + + var decoratedLinks = new NamedLinksDecorator(db, trace); + + // If --mcp-server is provided, start MCP server + if (mcpServer) { - var decoratedLinks = new NamedLinksDecorator(db, trace); + StartMcpServer(decoratedLinks, trace); + return; + } - // If --structure is provided, handle it separately - if (structure.HasValue) + // If --structure is provided, handle it separately + if (structure.HasValue) + { + var linkId = structure.Value; + try { - var linkId = structure.Value; - try - { - var structureFormatted = decoratedLinks.FormatStructure(linkId, link => decoratedLinks.IsFullPoint(linkId), true, true); - Console.WriteLine(Namify(decoratedLinks, structureFormatted)); - } - catch (Exception ex) - { - Console.Error.WriteLine($"Error formatting structure for link ID {linkId}: {ex.Message}"); - Environment.Exit(1); - } - return; // Exit after handling --structure + var structureFormatted = decoratedLinks.FormatStructure(linkId, link => decoratedLinks.IsFullPoint(linkId), true, true); + Console.WriteLine(Namify(decoratedLinks, structureFormatted)); } - - if (before) + catch (Exception ex) { - PrintAllLinks(decoratedLinks); + Console.Error.WriteLine($"Error formatting structure for link ID {linkId}: {ex.Message}"); + Environment.Exit(1); } + return; // Exit after handling --structure + } - var effectiveQuery = !string.IsNullOrWhiteSpace(queryOptionValue) ? queryOptionValue : queryArgumentValue; + if (before) + { + PrintAllLinks(decoratedLinks); + } - var changesList = new List<(DoubletLink Before, DoubletLink After)>(); + var effectiveQuery = !string.IsNullOrWhiteSpace(queryOptionValue) ? queryOptionValue : queryArgumentValue; - if (!string.IsNullOrWhiteSpace(effectiveQuery)) - { - var options = new QueryProcessor.Options - { - Query = effectiveQuery, - Trace = trace, - ChangesHandler = (beforeLink, afterLink) => - { - changesList.Add((new DoubletLink(beforeLink), new DoubletLink(afterLink))); - return decoratedLinks.Constants.Continue; - } - }; - - QueryProcessor.ProcessQuery(decoratedLinks, options); - } + var changesList = new List<(DoubletLink Before, DoubletLink After)>(); - if (changes && changesList.Any()) + if (!string.IsNullOrWhiteSpace(effectiveQuery)) + { + var options = new QueryProcessor.Options { - // Simplify the collected changes - var simplifiedChanges = SimplifyChanges(changesList); - - // Print the simplified changes - foreach (var (linkBefore, linkAfter) in simplifiedChanges) + Query = effectiveQuery, + Trace = trace, + ChangesHandler = (beforeLink, afterLink) => { - PrintChange(decoratedLinks, linkBefore, linkAfter); + changesList.Add((new DoubletLink(beforeLink), new DoubletLink(afterLink))); + return decoratedLinks.Constants.Continue; } - } + }; + + QueryProcessor.ProcessQuery(decoratedLinks, options); + } + + if (changes && changesList.Any()) + { + // Simplify the collected changes + var simplifiedChanges = SimplifyChanges(changesList); - if (after) + // Print the simplified changes + foreach (var (linkBefore, linkAfter) in simplifiedChanges) { - PrintAllLinks(decoratedLinks); + PrintChange(decoratedLinks, linkBefore, linkAfter); } - }, - // Explicitly specify the type parameters - dbOption, queryOption, queryArgument, traceOption, structureOption, beforeOption, changesOption, afterOption -); + } + + if (after) + { + PrintAllLinks(decoratedLinks); + } +}); await rootCommand.InvokeAsync(args); +static void StartMcpServer(NamedLinksDecorator links, bool trace) +{ + if (trace) + Console.WriteLine("[MCP] Starting MCP server on stdio"); + + var mcpServer = new McpServer(links, trace); + + // Create JsonRpc using stdio streams + using var jsonRpc = JsonRpc.Attach(Console.OpenStandardInput(), Console.OpenStandardOutput(), mcpServer); + + if (trace) + Console.WriteLine("[MCP] MCP server started successfully. Waiting for requests..."); + + // Wait for the connection to be terminated + jsonRpc.Completion.Wait(); + + if (trace) + Console.WriteLine("[MCP] MCP server shut down"); +} + static string Namify(NamedLinksDecorator namedLinks, string linksNotation) { var numberGlobalRegex = new Regex(@"\d+"); diff --git a/README.md b/README.md index 9399258..f3502e0 100644 --- a/README.md +++ b/README.md @@ -215,6 +215,62 @@ clink '((($index: $source $target)) (($index: $target $source)))' --changes --af clink '((1: 2 1) (2: 1 2)) ()' --changes --after ``` +## MCP (Model Context Protocol) Server for Neural Networks + +The CLI now supports MCP (Model Context Protocol) server mode, which allows neural networks and AI assistants to use the links database as persistent memory storage. This enables GPTs to "remember anything they need" by storing and retrieving information as associative links. + +### Starting the MCP Server + +```bash +clink --mcp-server +# or +clink --mcp +``` + +The server communicates via JSON-RPC 2.0 over stdio, making it compatible with any MCP client. + +### MCP Capabilities + +The server exposes three types of capabilities: + +#### 1. Resources (Reading Memory) +- `memory://links/all` - Access to all stored memory links +- `memory://links/search` - Search interface for finding specific memories + +#### 2. Tools (Memory Operations) +- `store_memory` - Store new information in neural network memory +- `search_memory` - Search for stored memories by content or name +- `update_memory` - Update existing memory links +- `delete_memory` - Remove memories from storage + +#### 3. Prompts (Common Neural Network Operations) +- `remember_context` - Template for storing conversational context +- `recall_similar` - Template for finding similar memories + +### Example Usage with MCP Clients + +```bash +# Start the MCP server +clink --mcp-server + +# The server will listen for JSON-RPC requests on stdio +# Neural networks can then use tools like: +# - store_memory: {"content": "User prefers coffee over tea", "name": "user_preference"} +# - search_memory: {"query": "coffee"} +# - update_memory: {"id": "1", "name": "updated_preference"} +# - delete_memory: {"name": "old_memory"} +``` + +### Benefits for Neural Networks + +1. **Persistent Memory**: Information survives across conversations +2. **Associative Storage**: Links naturally represent relationships between concepts +3. **Efficient Retrieval**: Fast search and filtering capabilities +4. **Structured Data**: All memories stored as triplets (source, target, index) +5. **Named References**: Memories can be labeled for easy access + +This makes the links database an ideal backend for neural network memory systems, allowing AI assistants to build and maintain long-term knowledge bases. + ## All options and arguments | Parameter | Type | Default Value | Aliases | Description | @@ -227,6 +283,7 @@ clink '((1: 2 1) (2: 1 2)) ()' --changes --after | `--before` | bool | `false` | `-b` | Print the state of the database before applying changes | | `--changes` | bool | `false` | `-c` | Print the changes applied by the query | | `--after` | bool | `false` | `--links`, `-a` | Print the state of the database after applying changes | +| `--mcp-server` | bool | `false` | `--mcp` | Start MCP (Model Context Protocol) server for neural network memory access | ## For developers and debugging diff --git a/examples/mcp_demo.md b/examples/mcp_demo.md new file mode 100644 index 0000000..e754466 --- /dev/null +++ b/examples/mcp_demo.md @@ -0,0 +1,95 @@ +# MCP Server Demo for Neural Network Memory + +This demonstrates how the link-cli MCP server enables neural networks to store and retrieve persistent memory. + +## Quick Start + +1. **Start the MCP server:** +```bash +clink --mcp-server +``` + +2. **The server is now ready for MCP clients** to connect via JSON-RPC 2.0 over stdio. + +## MCP Protocol Example + +Here's what a typical interaction might look like: + +### Initialize Connection +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": "2024-11-05", + "clientInfo": { + "name": "neural-network-client", + "version": "1.0.0" + } + } +} +``` + +### Store Memory +```json +{ + "jsonrpc": "2.0", + "id": 2, + "method": "tools/call", + "params": { + "name": "store_memory", + "arguments": { + "content": "User prefers dark roast coffee over light roast", + "name": "coffee_preference" + } + } +} +``` + +### Search Memory +```json +{ + "jsonrpc": "2.0", + "id": 3, + "method": "tools/call", + "params": { + "name": "search_memory", + "arguments": { + "query": "coffee" + } + } +} +``` + +### Access All Memory Links +```json +{ + "jsonrpc": "2.0", + "id": 4, + "method": "resources/read", + "params": { + "uri": "memory://links/all" + } +} +``` + +## Integration with AI Assistants + +Neural networks and AI assistants can use this MCP server to: + +1. **Store Context**: Remember important information from conversations +2. **Build Knowledge**: Create persistent knowledge bases +3. **Learn Preferences**: Remember user preferences and settings +4. **Track Relationships**: Store associations between concepts +5. **Maintain State**: Keep information across sessions + +## Benefits + +- **Persistent Memory**: Survives restarts and sessions +- **Associative Storage**: Natural relationship representation +- **Fast Retrieval**: Efficient search and filtering +- **Standard Protocol**: Works with any MCP client +- **Scalable**: Handles large amounts of structured data + +The links database provides the perfect foundation for neural network memory systems! \ No newline at end of file