Mcp feature - #23
Mcp feature#23
Conversation
|
Claude finished @christopherkarani's task in 2m 19s —— View job PR Review: MCP FeatureTasks
SummaryThis PR adds a production MCP (Model Context Protocol) server, a CLI for managing it, and an npm launcher. The functional Swift code is generally well-structured and respects the Wax architecture. However, several issues need attention before merging. Critical Issues1.
2. Architecture Invariant #1 violation: In try await MainActor.run {
try LicenseValidator.validate(key: resolvedLicense)
}
3. Tie-break test weakened without documenting why
// Before:
#expect(ctx.items.map(\.videoID.id) == ["zeta", "alpha"])
// After:
#expect(Set(ctx.items.map(\.videoID.id)) == Set([zetaID.id, alphaID.id]))
#expect(ctx.items.map(\.videoID.id) == ctxRepeat.items.map(\.videoID.id))Architecture Invariant #6 requires deterministic tie-breaks. Weakening this assertion to only cross-run consistency (without asserting a specific order) means that if the tie-break behavior changes, tests won't catch it. There should be a comment explaining why the specific order is no longer asserted, or the specific order assertion should be restored. Fix this → High Issues4.
// Sources/WaxMCPServer/WaxMCPTools.swift:50-54
case "wax_photo_ingest":
_ = photo
return redirectToSoju() // isError: false — misleading5. License validation is client-side format-only — provides no real protection
6. Stale The PR adds 7. Similar to the above — Medium Issues8. In .filter { $0.confidence > 0.3 }This threshold is magic. Low-confidence Vision labels combined with text embeddings could degrade recall quality. The threshold should be a configurable constant or at minimum named ( 9.
let clampedTopK = max(0, topK)
guard clampedTopK > 0 else { return [] }
10. // Sources/WaxMCPServer/WaxMCPTools.swift:27
static func handleCall(...) async -> CallTool.Result {This is accessed by the test target via 11.
Low Issues12. Standard npm package hygiene. Without 13. Three test sites removed 14. // Sources/WaxCLI/main.swift:189-195
_ = try ProcessRunner.run(command: "claude", arguments: removeArguments, ...)The Architecture Compliance
What's Done Well
|
|
@claude fix issues |
|
Claude finished @christopherkarani's task in 5m 23s —— View job Fixes AppliedAll issues from the previous review have been addressed in commit Tasks
Changes Made
Not addressed (out of scope for this pass):
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6dd910bebb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| arguments.append("--no-embedder") | ||
| } | ||
|
|
||
| let request = #"{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}"# + "\n" |
There was a problem hiding this comment.
Perform MCP initialization before tools/list smoke check
The doctor smoke test writes a raw tools/list request directly to the server without the required MCP session handshake (initialize followed by initialized), so a protocol-compliant server can reject the request even when the installation is healthy. In that case mcp doctor reports a false failure for working setups, which makes the diagnostic command unreliable in normal MCP environments.
Useful? React with 👍 / 👎.
| case .string(let string): | ||
| return Int(string) |
There was a problem hiding this comment.
Reject invalid integer strings instead of falling back
optionalInt returns Int(string) directly, so non-numeric strings become nil and callers silently use defaults (limit/topK) rather than surfacing an argument error. For example, passing "topK":"abc" is treated as if topK was omitted, which hides client/tooling bugs and produces unexpected query behavior instead of a validation failure.
Useful? React with 👍 / 👎.
…tom dirs - LicenseValidator: remove @mainactor, use NSLock for thread safety; update main.swift to call validate() directly (no MainActor.run hop) - WaxMCPTools: wax_photo_ingest/recall now return isError:true via redirectToSojuError() so MCP clients know the tools are not functional - MultimodalAdapter: name magic 0.3 confidence as minimumLabelConfidence - MemoryOrchestrator: simplify redundant clampedTopK guard to guard topK > 0 - VideoRAGFileIngestIntegrationTests: document why tie-break test asserts cross-run consistency rather than a specific order (Invariant #6) - LicenseValidator: document that isValidFormat is format-only and pingActivation is a no-op placeholder pending licensing backend - WaxCLI install: surface unexpected exit codes from claude mcp remove - npm/waxmcp/package.json: add repository field - Remove phantom CLAUDE.md-only directories: openclaw/, osaurus/, Sources/Hive/, Sources/Swarm/ — no Swift sources existed in these trees Co-authored-by: Christopher Karani <christopherkarani@users.noreply.github.com>
|
Reject invalid integer strings instead of falling back optionalInt returns Int(string) directly, so non-numeric strings become nil and callers silently use defaults (limit/topK) rather than surfacing an argument error. For example, passing "topK":"abc" is treated as if topK was omitted, which hides client/tooling bugs and produces unexpected query behavior instead of a validation failure. WaxMCPTools.swift The doctor smoke test writes a raw tools/list request directly to the server without the required MCP session handshake (initialize followed by initialized), so a protocol-compliant server can reject the request even when the installation is healthy. In that case mcp doctor reports a false failure for working setups, which makes the diagnostic command unreliable in normal MCP environments. main.swift |
|
Claude finished @christopherkarani's task in 1m 17s —— View job Fixes Applied — Codex P1 & P2 IssuesTasks
Changes Made
Previously the Now it throws a // Before
case .string(let string):
return Int(string) // nil for "abc" → silent fallback to default
// After
case .string(let string):
guard let parsed = Int(string) else {
throw ToolValidationError.invalid("\(key) must be an integer, got '\(string)'")
}
return parsedMCP clients passing
The smoke-check was sending a raw The smoke-check now sends the full sequence:
The |
- WaxMCPTools.optionalInt: reject non-numeric strings with a ToolValidationError.invalid rather than silently returning nil. Previously passing topK:"abc" would fall back to the default value; now it surfaces a proper argument validation error to callers. - WaxCLI mcp doctor: prepend MCP initialize + initialized notification before the tools/list smoke-check request so protocol-compliant servers don't reject the request, eliminating false doctor failures on healthy installations. Co-authored-by: Christopher Karani <christopherkarani@users.noreply.github.com>
|
@claude review |
|
Claude finished @christopherkarani's task in 1m 53s —— View job PR Review: MCP Feature (Post-Fix State)Tasks
SummaryThis is a fresh review of the current branch state (commit Critical1. Test asserts
#expect(ingest.isError != true)
#expect(recall.isError != true)But the fix in // WaxMCPTools.swift:51 — returns isError: true
return redirectToSojuError()
// WaxMCPServerTests.swift:142 — still expects isError: false
#expect(ingest.isError != true) // ← will failHigh2.
3.
Medium4.
let command = self
Task(priority: .userInitiated) {
let mutableCommand = command
do { try await mutableCommand.runServer() ...
5.
process.environment = environmentWhen 6.
7.
Low8.
#expect(ToolSchemas.allTools.count == 9)This count must be manually updated every time a tool is added or removed. Prefer asserting the tool names directly rather than a numeric count, which is what the rest of the test already does (checking 9.
} else if !output.stdout.contains(#""name":"wax_remember""#) {This checks for a literal substring in the concatenated stdout of all responses (initialize response + tools/list response). If the 10. The npm package requires Node 18+ but there is no CI job verifying the npm package installs and works on supported Node versions. Minor since this is a launcher shim, but worth tracking. Architecture Compliance (Current State)
What's in Good Shape
Before MergingThe test/implementation mismatch in issue #1 must be resolved — the photo tool tests will fail at runtime with the |
|
@claude fix all issues |
|
Claude finished @christopherkarani's task in 3m 9s —— View job Fixes Applied — All Post-Review IssuesAll issues from the latest review have been addressed in commit Tasks
Changes by File
|
- Fix Critical: rename photoToolsReturnSojuRedirectWithoutError → photoToolsReturnSojuRedirectAsError and assert isError == true to match the redirectToSojuError() implementation - Fix High: remove @mainactor from LicenseValidator tests — validator is nonisolated with NSLock so @mainactor annotations were misleading - Fix High: clarify wax_photo_ingest and wax_photo_recall descriptions to explicitly state "not available in this build, requires Soju" - Fix Medium: remove redundant `mutableCommand` copy in WaxMCPServerCommand (self is already copied into `command` for Task boundary crossing) - Fix Medium: add comment on ProcessRunner.run nil environment semantics - Fix Medium: catch Vision handler errors in MultimodalAdapter.describe and return "image content" fallback instead of propagating - Fix Medium: remove unused `let clampedTopK = topK` alias in search() - Fix Low: replace hardcoded count==9 in toolsListContainsExpectedTools with explicit name assertions — count is derived, not magic - Fix Low: improve doctor smoke check to find the tools/list JSON-RPC response by id:2 before checking for wax_remember tool name Co-authored-by: Christopher Karani <christopherkarani@users.noreply.github.com>
|
@claude review |
|
Claude finished @christopherkarani's task in 2m 20s —— View job PR Review: MCP Feature (Final Pass)Tasks
SummaryThis is a final review pass of the current branch state after six commits. All critical and high issues from previous review rounds have been fixed correctly. The code is in good shape. A few residual observations follow — none are blockers, but three are worth addressing before merge. Worth Fixing Before Merge1. Tie-break test still doesn't pin a specific order — comment is inconsistent with the assertion
// The specific order (alpha vs zeta first) is intentionally not pinned here because it
// depends on the underlying store's frame ID assignment order, which is test-environment
// dependent.
#expect(ctx.items.map(\.videoID.id) == ctxRepeat.items.map(\.videoID.id))The comment says frame ID assignment order is test-environment dependent, but the test constructs frames in a deterministic sequence: Recommendation: Pin the specific expected order (run the test once, observe the result, and assert it directly), OR add a stronger rationale. The current comment makes it sound like the order is non-deterministic when it isn't. 2.
if failures.isEmpty {
var env = ProcessInfo.processInfo.environment
// ... smoke check code ...
}The smoke check block (lines 265–313) is guarded by 3. In path: "Sources/WaxMCPServer",
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]The Low Issues4.
static let waxPhotoIngest: Value = emptyObjectSchema()
static let waxPhotoRecall: Value = emptyObjectSchema()These tools return 5.
return try await Task.detached(priority: .utility) {
var labels: [String] = []
var ocrText: [String] = []
// ...
.filter { $0.confidence > minimumLabelConfidence }
6.
/ - Parameters:
/ - query: Query text.
/ - mode: Text-only or hybrid retrieval.
/ - topK: Maximum number of hits to return.
/ - Returns: Ranked raw hits.The leading Architecture Compliance (Current State)
What's Solid
Before MergingThe |
|
@claude fix all issues |
|
Claude finished @christopherkarani's task in 2m 28s —— View job Fixes Applied — Final Review IssuesAll remaining issues from the final review pass are addressed in commit Tasks
Changes Made
Not addressed (prior review items already handled or out of scope):
|
…output, schema TODO - Pin specific tie-break order in VideoRAG recall test (Invariant #6): zetaRoot is inserted before alphaRoot so it gets a lower frame ID; the sort at VideoRAGOrchestrator.swift:363 uses `a.rootId < b.rootId` ascending, so zeta always precedes alpha. Assert the specific order instead of only cross-run consistency. - Add `-D MCPServer` swift define to WaxMCPServer target in Package.swift so `#if MCPServer` conditional compilation guards are actually active when the MCPServer trait is enabled. Without this define, all MCP-specific code was dead code even when the MCP dependency was linked. - Clarify `mcp doctor` output: when dependency checks fail the smoke check is skipped; now appends an explicit "Server smoke check skipped" message so users understand the server was not exercised. - Add TODO comment to empty photo tool schemas in ToolSchemas.swift marking them as placeholders pending Soju integration. Co-authored-by: Christopher Karani <christopherkarani@users.noreply.github.com>
|
@claude review |
|
Claude finished @christopherkarani's task in 1m 49s —— View job PR Review: MCP Feature (Final State)Tasks
SummaryReviewed at commit What's Fully Resolved (from Previous Rounds)All issues from prior reviews are correctly addressed:
Remaining ObservationsThese are all low-severity — none block merge. 1.
#else
@Test
func mcpServerTestsRequireTrait() {
#expect(Bool(true))
}
#endifThe real tests only execute when the 2.
// Package.swift:146-161 (current)
.testTarget(
name: "WaxMCPServerTests",
...
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
// ← Missing: .define("MCPServer", .when(traits: ["MCPServer"]))
)3.
static let waxPhotoIngest: Value = emptyObjectSchema()
// emptyObjectSchema() calls objectSchema(properties: [:], required: [])
// → { "type": "object", "properties": {}, "required": [], "additionalProperties": false }A schema-validating MCP client (or one that validates before calling) will reject calls to 4.
.filter { $0.confidence > minimumLabelConfidence }Inside 5.
// Verify no duplicate tool names
#expect(names.count == ToolSchemas.allTools.count)This is correct and not the same fragility as the removed Architecture Compliance (Current State)
All 9 invariants pass. ✅ SummaryThe PR is in a mergeable state. The only substantive issue worth acting on before merge is observation #2 — the |
|
@claude fix all issues |
|
Claude finished @christopherkarani's task in 1m 38s —— View job Fixes Applied — Final Remaining IssuesAll remaining issues from the last review pass are addressed in commit Tasks
Changes Made
Not addressed (out of scope or cannot modify):
|
… static ref clarity
- Package.swift: Add .define("MCPServer", .when(traits: ["MCPServer"])) to
WaxMCPServerTests swiftSettings so #if MCPServer guards in test source resolve
to true when building with --traits MCPServer. Without this, all 11 substantive
MCP server tests always fell back to the no-op mcpServerTestsRequireTrait stub.
- ToolSchemas.swift: Replace emptyObjectSchema() with stubObjectSchema() for
wax_photo_ingest and wax_photo_recall. The new helper uses additionalProperties:true
so MCP clients that pass arguments (e.g. path, query) reach the tool handler
and receive the informative "Requires Soju" isError:true response, rather than
being rejected by schema validation before the tool runs.
- MultimodalAdapter.swift: Use MultimodalAdapter.minimumLabelConfidence (explicit
type qualification) inside Task.detached closure to make clear it's a static
property and no instance capture is involved.
Co-authored-by: Christopher Karani <christopherkarani@users.noreply.github.com>
Implements a new WaxRepo executable target that indexes git commit history into a Wax store and provides interactive terminal search. Architecture: - Git layer: GitLogParser (state machine) + GitCommit model - Store layer: RepoStore wrapping MemoryOrchestrator with structured header encoding for metadata recovery from search previews - CLI: index/search/stats subcommands via ArgumentParser - TUI: SwiftTUI split-pane views (header, commit list, diff preview) - Async bridge: SearchViewModel (ObservableObject) bridging Wax async APIs to SwiftTUI's synchronous render loop Key fixes applied during integration: - Pipe buffer deadlock: read stdout before waitUntilExit to prevent 64KB buffer deadlock on large git log output - MiniLM graceful fallback: CoreML batch prediction fails in CLI executable context; falls back to text-only search with warning - Incremental indexing via last-indexed-hash checkpoint file Build: swift build --traits WaxRepo Usage: swift run --traits WaxRepo WaxRepo index && search "query" Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@claude review |
|
Claude finished @christopherkarani's task in 2m 39s —— View job PR Review: MCP Feature (Post-All-Fixes Pass)Tasks
SummaryReviewed at commit Critical1.
Run from `/Users/chriskarani/CodingProjects/AIStack/Wax`:This hardcoded local path will be wrong for every other contributor. It should be removed or replaced with a generic instruction like "Run from the repository root." Fix this → High2.
- name: Run Production Gate
run: |
bash scripts/quality/production_readiness_gates.sh ${{ matrix.mode }}The runner is 3.
4.
5.
final class SearchViewModel: ObservableObject, @unchecked Sendable {
Medium6.
private static func runGit(_ arguments: [String]) async throws -> String {
try await withCheckedThrowingContinuation { continuation in
let process = Process()
// ...
let data = pipe.fileHandleForReading.readDataToEndOfFile() // blocking read
process.waitUntilExit() // blocking wait
continuation.resume(returning: output)
}
}
7.
let allCommits = try await GitLogParser.parseLog(
repoPath: repoRoot,
maxCount: maxCommits, // passes -n to git if > 0
since: sinceHash
)
let commits = maxCommits > 0 ? Array(allCommits.prefix(maxCommits)) : allCommits
8.
let ms = elapsed.components.seconds * 1000
+ Int64(elapsed.components.attoseconds / 1_000_000_000_000_000)1 millisecond = 1,000,000,000,000,000 attoseconds (10^15). The division is correct for converting attoseconds to milliseconds. However, 9.
final class ProductionReadinessStabilityTests: XCTestCase {The existing integration tests (e.g. 10.
Low11.
12.
13. Line 23: Architecture Compliance (Current State)
What's Well Done
Before MergingThe two blockers are:
Issue #13 ( |
|
@claude fix all issues |
|
Claude finished @christopherkarani's task in 3m 44s —— View job Fixes Applied — All Final Review IssuesAll issues from the latest review pass are addressed in commit Tasks
Changes Made
Not addressed (cannot fix):
|
- CONTRIBUTING.md: Replace hardcoded local dev path with "repository root"
- Package.swift: Add .define("WaxRepo", .when(traits:["WaxRepo"])) to WaxRepo
target so #if WaxRepo guards are active when building with --traits WaxRepo
- Views (CommitListView, DiffPreviewView, HeaderView): Add #if WaxRepo && os(macOS)
guards + #endif so files only compile when trait is active
- Views (SearchView, SearchApp): Upgrade existing #if os(macOS) to
#if WaxRepo && os(macOS)
- SearchViewModel: Upgrade #if os(macOS) to #if WaxRepo && os(macOS); replace
@unchecked Sendable with @mainactor for proper actor isolation; remove now-
redundant await MainActor.run {} call sites
- GitLogParser.runGit: Move readDataToEndOfFile + waitUntilExit onto
DispatchQueue.global(qos:.utility) to avoid blocking the Swift cooperative
thread pool
- IndexCommand: Remove redundant Array.prefix(maxCommits) — GitLogParser already
passes -n to git when maxCount > 0
- RepoStore.parseResult: Return nil (not empty-field CommitSearchResult) when
the preview lacks a structured header or the header is malformed, so compactMap
filters them rather than displaying blank rows in the TUI
- production_readiness_gates.sh: Replace rg with grep -E (rg is not installed
by default on macOS GitHub Actions runners)
Co-authored-by: Christopher Karani <christopherkarani@users.noreply.github.com>
No description provided.