Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,13 @@ sequenceDiagram
participant BackendConn as BackendConnection
participant Downstream as MCP Backend

Client->>Router: POST /message?sessionId=1 (search_tools)
Router->>RoutingMgr: SearchToolsAsync("restart container")
Client->>Router: POST /message?sessionId=1 (search_tools: query, mode, dense_weight)
Router->>RoutingMgr: SearchToolsDetailedAsync("restart container", mode, denseWeight)
RoutingMgr->>RoutingMgr: Compute Lexical Keyword Scoring (name, desc, tags, params)
RoutingMgr->>VectorStore: SearchSimilarAsync(queryEmbedding) via .NET 10 SIMD
VectorStore-->>RoutingMgr: Return Ranked Vector Similarities
RoutingMgr->>RoutingMgr: Fuse Ranks via Reciprocal Rank Fusion (RRF, k=60)
RoutingMgr-->>Router: Return Top Ranked Tools
RoutingMgr->>RoutingMgr: Calibrated Fusion: Score = alpha * DenseScore + (1 - alpha) * SparseScore
RoutingMgr-->>Router: Return Top Ranked Tools with Decomposed Scores
Router-->>Client: Return namespaced search result JSON

Client->>Router: POST /message?sessionId=1 (execute_tool: docker/restart_container)
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ For summary details and quick references, see [README.md](README.md).

| Version | Release Date | Summary of Key Changes |
| :--- | :--- | :--- |
| **`v5.21.0`** | 2026-10-02 | feat(routing): calibrated hybrid semantic search scoring, explicit search modes, and test bench simulator overhaul |
| **`v5.20.1`** | 2026-09-30 | refactor(testbench): Remove Pre-fill Example Button and Streamline Dynamic Argument Handling. Removed the legacy Pre-fill Example button and unused bulk argument modification plumbing from the Test Bench tool tester card, eliminating brittle and unhelpful parameter mocks while preserving interactive form validation and tool execution coverage. |
| **`v5.20.0`** | 2026-09-30 | feat(auth,ui,docs,ci): AppKey & OAuth Client Last-Used Tracking, Safe Secret Rotation Workflow, Multi-Client Config Generator, VitePress Documentation Architecture Overhaul, and 30-Day Dependabot Stability Cooldown. Implemented `LastUsedAt` audit tracking for AppKeys and OAuth clients across SQLite, MySQL, and MSSQL with an in-memory 60-second sliding debounce `ActivityTracker`; added atomic credential rotation endpoints (`POST /api/appkeys/{id}/rotate`, `POST /api/clients/{id}/rotate-secret`) returning plaintext keys once with SHA-256 hashing at rest; built two-stage `AppKeyRotateModal` with backdrop click-away protection and multi-client configuration generator (Claude Desktop, Cursor, Windsurf, generic JSON); overhauled VitePress documentation architecture with Diátaxis decomposition, path-scoped multi-sidebars, purged legacy ASCII art to responsive Mermaid diagrams, modernized typography rhythm, and link integrity CI quality gates; updated GitHub Actions to v7/v5; and configured a 30-day stability cooldown across all Dependabot ecosystems (`AUTH-118` through `AUTH-123`, `UI-135` through `UI-137`). |
| **`v5.19.1`** | 2026-09-29 | docs(slack): Purge Deprecated Wrapper References, Renumber Documentation Sections, and Refresh Wiki. Streamlined documentation for Slack Native Direct MCP (`https://mcp.slack.com/mcp`) and the Hybrid Egress Delegation Model; eliminated legacy self-hosted Node.js wrapper references across repository and wiki; updated release assets; and pinned container image tags. |
Expand Down
23 changes: 21 additions & 2 deletions Components/Capabilities/CapabilityEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -504,8 +504,27 @@ FROM AuditLogs
}
}

var scoredResults = await SemanticSearchService.SearchToolsSemanticAsync(model.Query, allTools, embeddingService, logger);
return Results.Ok(scoredResults);
var effectiveProvider = embeddingService != null ? new ModelContextGateway.Core.VectorSearch.EmbeddingServiceAdapter(embeddingService) : null;
var effectiveStore = httpContext.RequestServices.GetService<ModelContextGateway.Core.VectorSearch.IToolVectorStore>();
var routingManager = new ToolRoutingManager(effectiveProvider, effectiveStore);
var scoredResults = await routingManager.SearchToolsDetailedAsync(
model.Query ?? "",
allTools,
effectiveProvider,
effectiveStore,
logger,
model.Limit ?? 15,
searchMode: model.Mode ?? "hybrid",
denseWeight: model.DenseWeight ?? 0.5,
cancellationToken: httpContext.RequestAborted);

return Results.Ok(new
{
query = model.Query ?? "",
mode = model.Mode ?? "hybrid",
denseWeight = model.DenseWeight ?? 0.5,
results = scoredResults
});
});

// 2b. Test Prompts List API
Expand Down
3 changes: 3 additions & 0 deletions Components/Capabilities/CapabilityModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,8 @@ public class TestResourceReadModel
public class SearchModel
{
public string Query { get; set; } = string.Empty;
public string? Mode { get; set; } = "hybrid";
public double? DenseWeight { get; set; } = 0.5;
public int? Limit { get; set; } = 15;
}
}
38 changes: 32 additions & 6 deletions Core/Routing/ToolRoutingManager.Execution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,27 @@ private async Task<object> CallToolInternalAsync(
var root = doc.RootElement;

string query = "";
string mode = "hybrid";
double denseWeight = 0.5;

if (root.TryGetProperty("params", out var paramsProp) &&
paramsProp.TryGetProperty("arguments", out var argsProp) &&
argsProp.TryGetProperty("query", out var queryProp))
paramsProp.TryGetProperty("arguments", out var argsProp))
{
query = queryProp.GetString() ?? "";
if (argsProp.TryGetProperty("query", out var queryProp))
{
query = queryProp.GetString() ?? "";
}
if (argsProp.TryGetProperty("mode", out var modeProp) && modeProp.ValueKind == JsonValueKind.String)
{
mode = modeProp.GetString() ?? "hybrid";
}
if (argsProp.TryGetProperty("dense_weight", out var dwProp) || argsProp.TryGetProperty("denseWeight", out dwProp))
{
if (dwProp.ValueKind == JsonValueKind.Number && dwProp.TryGetDouble(out var dwVal))
{
denseWeight = dwVal;
}
}
}

var tools = new List<object>();
Expand Down Expand Up @@ -192,9 +208,19 @@ private async Task<object> CallToolInternalAsync(
}

var effectiveProvider = _embeddingProvider ?? (embeddingService != null ? new ModelContextGateway.Core.VectorSearch.EmbeddingServiceAdapter(embeddingService) : null);
var effectiveStore = _vectorStore ?? new ModelContextGateway.Core.VectorSearch.InMemorySimdToolVectorStore();
var results = await SearchToolsAsync(query, tools, effectiveProvider, effectiveStore, logger, 15, cancellationToken);
var serialized = JsonSerializer.Serialize(results, new JsonSerializerOptions { WriteIndented = true });
var effectiveStore = _vectorStore;
var detailedResults = await SearchToolsDetailedAsync(
query,
tools,
effectiveProvider,
effectiveStore,
logger,
15,
searchMode: mode,
denseWeight: denseWeight,
cancellationToken: cancellationToken);
var toolSchemas = detailedResults.Select(r => r.Tool).ToList();
var serialized = JsonSerializer.Serialize(toolSchemas, new JsonSerializerOptions { WriteIndented = true });
return new
{
resultType = "complete",
Expand Down
Loading
Loading