Retrieval-Augmented Generation, embeddings, and vector search · Folder: src/NexaFlowDemo.Core/RAG/
Claude was trained on public data up to its knowledge cutoff. It knows nothing about your company's internal procedures, your SLAs, your product catalogue, or any private document. Even if you could give it all those documents in the prompt, the context window has limits and you would be paying to re-read the same documents on every query.
RAG (Retrieval-Augmented Generation) solves this: instead of stuffing all documents into the prompt, you store them in a searchable database, find only the most relevant ones for each query, and inject just those into the prompt.
WHERE ... LIKE '%keyword%' and only load the matching rows. RAG does the same, but uses semantic similarity instead of keyword matching.
document_search in NexaFlowDemo.McpServer. When Claude calls it, the McpServer's DocumentSearchTools.SearchDocumentsAsync(query, top_k) runs a vector similarity search over the in-memory knowledge base and returns the top matching documents as JSON. Claude then synthesises an answer from those documents. The knowledge base is seeded at McpServer startup via KnowledgeBaseSeeder.Seed().
Steps 1 and 2 happen at McpServer startup (seeding). Steps 3–5 happen on every document_search tool call.
An embedding is a list of numbers (a vector) that represents the meaning of a piece of text. Similar meanings produce vectors that are close together in space; different meanings produce vectors far apart.
In this project, SimulatedDenseEmbeddingService is used — it generates random-but-consistent vectors for each text (seeded by a hash of the text) — good enough for demonstration without an API key for a separate embedding service. It is registered in McpServer/Program.cs:
// McpServer/Program.cs — registered so DocumentSearchTools can inject IDocumentStore
builder.Services.AddSingleton<IEmbeddingService>(_ => new SimulatedDenseEmbeddingService(384));
builder.Services.AddSingleton<IVectorStore, InMemoryVectorStore>();
builder.Services.AddSingleton<IDocumentStore>(sp => {
var vec = sp.GetRequiredService<IVectorStore>();
var emb = sp.GetRequiredService<IEmbeddingService>();
return new VectorDocumentStore(vec, emb);
});
A vector store stores (document, embedding) pairs and answers: "Which stored embeddings are closest to this query embedding?" The measure of closeness is cosine similarity: two vectors pointing in the same direction score 1.0 (identical meaning); perpendicular vectors score 0.0 (unrelated).
LINQ .OrderByDescending(d => CosineSimilarity(d.Embedding, queryEmbedding)).Take(k). The vector store does exactly that.
IDocumentStore wraps IVectorStore and IEmbeddingService together. You hand it a Document object with a Title and Content string. It calls the embedding service, converts the text to a vector, stores both together, and on search converts your query string to a vector for you.
public interface IDocumentStore
{
Task AddAsync(Document document, CancellationToken ct = default);
Task<IReadOnlyList<SearchResult>> SearchAsync(string query, int topK = 5, ...);
}
In this project, the RAG pipeline is exposed to Claude via an MCP tool rather than via a direct RagPipeline.QueryAsync() call. DocumentSearchTools is an instance class (injected with IDocumentStore via DI) that the McpServer hosts as an MCP tool. When Claude calls document_search, the McpServer invokes this class, which runs the vector search and returns matching snippets as JSON:
[McpServerToolType]
public class DocumentSearchTools
{
private readonly IDocumentStore _store;
public DocumentSearchTools(IDocumentStore store) => _store = store;
[McpServerTool(Name = "document_search", ReadOnly = true)]
[Description("Search the supply chain knowledge base for relevant documents.")]
public async Task<string> SearchDocumentsAsync(
[Description("Search query.")] string query,
[Description("Number of results to return (1–5).")] int top_k = 3)
{
var results = await _store.SearchAsync(query, top_k);
// serialise results to JSON and return
}
}
At McpServer startup, KnowledgeBaseSeeder.Seed() adds supply chain documents to the document store. Each document is embedded and indexed at that point:
// McpServer/Program.cs — runs after the host is built
var store = host.Services.GetRequiredService<IDocumentStore>();
var logger = host.Services.GetRequiredService<ILogger<Program>>();
KnowledgeBaseSeeder.Seed(store, logger);
To add your own documents to the knowledge base, add them in KnowledgeBaseSeeder.Seed() using store.AddAsync(new Document { ... }). They will be available to Claude via the document_search tool on the next McpServer startup.
Document, it handles embedding + storing + searching.document_search MCP tool hosted in NexaFlowDemo.McpServer.KnowledgeBaseSeeder.Seed().