Add server inspection diff engine and change detection UI#1731
Add server inspection diff engine and change detection UI#1731chelojimenez wants to merge 2 commits intomainfrom
Conversation
Ship a local-first "Changes since last successful connect" feature for the MCPJam Inspector. Shows semantic diffs for tools/list and initialize data (protocolVersion, transport, serverVersion, instructions, serverCapabilities) between consecutive successful connections. Key components: - Pure diff engine with deterministic normalization (sorted keys, merged tool metadata via _meta ?? toolsMetadata pattern) - Zustand store persisted to localStorage (atomic records per server) - App-level inspection coordinator that watches all workspace servers for connection transitions, with workspace scoping guards - Rich toast with "View changes" action using TTL-based localStorage handoff to open the server detail modal Overview tab - ServerChangesPanel UI with color-coded badges and collapsible before/after sections for init and tool changes Safety: incomplete pagination skips diff, inspection errors never overwrite previous baselines, workspace switches cancel in-flight work. 81 new tests across 6 test files (diff engine, summary, store, coordinator, UI panel, detail request helper). https://claude.ai/code/session_01KBjUEbjapGUfujDhE4gYqA
|
🚅 Deployed to the inspector-pr-1731 environment in triumphant-alignment
|
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
WalkthroughThis pull request introduces a server inspection system that tracks MCP server initialization state and tool catalogs across reconnections. It adds snapshot capture, diffing logic to detect changes, persistent storage via localStorage, and UI components to display server changes. The implementation includes the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
mcpjam-inspector/client/src/stores/inspection-store.ts (1)
32-37: Consider potential for unbounded growth.The
recordsobject accumulates entries indefinitely. As users connect to many servers across workspaces, localStorage usage grows without cleanup. Consider adding aclearForWorkspace(workspaceId)method or periodic pruning of stale entries.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@mcpjam-inspector/client/src/stores/inspection-store.ts` around lines 32 - 37, The records map in inspection-store.ts (used with inspectionStoreKey) can grow unbounded because entries are never removed; add a clearForWorkspace(workspaceId: string) method that scans stored keys (matching `${workspaceId}::`) and removes them from the in-memory records object and localStorage, and call it where workspace teardown/disconnect logic occurs; alternatively implement periodic pruning by adding a timestamp field to each record and a pruneStaleRecords(maxAgeMs) helper that deletes entries older than the threshold (update load/save routines such as loadRecords/saveRecords to persist the timestamp and run pruneStaleRecords on init).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@mcpjam-inspector/client/src/App.tsx`:
- Around line 913-920: The toast callback only sends serverName, losing the
originating workspace identity; update handleInspectionViewChanges to include
the current workspace id when calling writeInspectionDetailRequest (e.g., pass {
workspaceId, serverName }) and keep applyNavigation as-is, and then update the
consumer in ServersTab (or wherever writeInspectionDetailRequest is read) to
validate that the incoming request's workspaceId matches the active workspace
before resolving/opening the diff; if it doesn't match, ignore or surface a
user-facing message.
In `@mcpjam-inspector/client/src/components/connection/ServerInfoContent.tsx`:
- Around line 29-31: The selector is using the live activeWorkspaceId
(useSharedAppState) which can change under you; instead use the workspace id
captured when the modal opened (the modal's snapshot/prop) rather than
activeWorkspaceId so the inspectionStoreKey stays stable. Replace usage of
activeWorkspaceId in the construction of storeKey
(inspectionStoreKey(activeWorkspaceId, server.name)) with the captured workspace
id provided by the modal (e.g., detailModalState.workspaceId or
serverSnapshot.workspaceId) so useInspectionStore reads the record for that
captured workspace; alternatively ensure the modal is closed on workspace
switch. Ensure you update any references in ServerInfoContent (and the call
sites that pass serverSnapshot) so the captured id is available to build the
stable storeKey.
---
Nitpick comments:
In `@mcpjam-inspector/client/src/stores/inspection-store.ts`:
- Around line 32-37: The records map in inspection-store.ts (used with
inspectionStoreKey) can grow unbounded because entries are never removed; add a
clearForWorkspace(workspaceId: string) method that scans stored keys (matching
`${workspaceId}::`) and removes them from the in-memory records object and
localStorage, and call it where workspace teardown/disconnect logic occurs;
alternatively implement periodic pruning by adding a timestamp field to each
record and a pruneStaleRecords(maxAgeMs) helper that deletes entries older than
the threshold (update load/save routines such as loadRecords/saveRecords to
persist the timestamp and run pruneStaleRecords on init).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0f1650ef-af82-4e71-a83a-6fcd761a7f48
📒 Files selected for processing (16)
mcpjam-inspector/client/src/App.tsxmcpjam-inspector/client/src/components/ServersTab.tsxmcpjam-inspector/client/src/components/connection/ServerChangesPanel.tsxmcpjam-inspector/client/src/components/connection/ServerInfoContent.tsxmcpjam-inspector/client/src/components/connection/__tests__/ServerChangesPanel.test.tsxmcpjam-inspector/client/src/hooks/__tests__/use-inspection-coordinator.test.tsmcpjam-inspector/client/src/hooks/use-inspection-coordinator.tsmcpjam-inspector/client/src/lib/inspection-detail-request.tsmcpjam-inspector/client/src/lib/inspection/__tests__/diff-engine.test.tsmcpjam-inspector/client/src/lib/inspection/__tests__/diff-summary.test.tsmcpjam-inspector/client/src/lib/inspection/__tests__/inspection-detail-request.test.tsmcpjam-inspector/client/src/lib/inspection/diff-engine.tsmcpjam-inspector/client/src/lib/inspection/diff-summary.tsmcpjam-inspector/client/src/lib/inspection/types.tsmcpjam-inspector/client/src/stores/__tests__/inspection-store.test.tsmcpjam-inspector/client/src/stores/inspection-store.ts
| // Callback for inspection toast "View changes" action | ||
| const handleInspectionViewChanges = useCallback( | ||
| (serverName: string) => { | ||
| writeInspectionDetailRequest(serverName); | ||
| applyNavigation("servers", { updateHash: true }); | ||
| }, | ||
| [applyNavigation], | ||
| ); |
There was a problem hiding this comment.
Carry workspace identity through the toast callback.
Inspection records are keyed by workspaceId::serverName, but this handoff persists only serverName. If a toast created in workspace A is clicked after the user has moved to workspace B, ServersTab will resolve the request against B’s servers and can open the wrong diff—or drop the request entirely. Persist the originating workspace id alongside the server name and validate it when consuming the request.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mcpjam-inspector/client/src/App.tsx` around lines 913 - 920, The toast
callback only sends serverName, losing the originating workspace identity;
update handleInspectionViewChanges to include the current workspace id when
calling writeInspectionDetailRequest (e.g., pass { workspaceId, serverName })
and keep applyNavigation as-is, and then update the consumer in ServersTab (or
wherever writeInspectionDetailRequest is read) to validate that the incoming
request's workspaceId matches the active workspace before resolving/opening the
diff; if it doesn't match, ignore or surface a user-facing message.
| const { activeWorkspaceId } = useSharedAppState(); | ||
| const storeKey = inspectionStoreKey(activeWorkspaceId, server.name); | ||
| const inspectionRecord = useInspectionStore((s) => s.records[storeKey]); |
There was a problem hiding this comment.
Don’t key the diff panel off live workspace state.
ServersTab can keep showing detailModalState.serverSnapshot after the live server entry disappears, so this selector may outlive the workspace it came from. Once the user switches workspaces, Line 30 starts reading the new workspace’s inspection record for the same server name, which can make “changes since last connect” disappear or show the wrong diff. Pass the workspace id captured when the modal opened, or close the modal on workspace switch.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mcpjam-inspector/client/src/components/connection/ServerInfoContent.tsx`
around lines 29 - 31, The selector is using the live activeWorkspaceId
(useSharedAppState) which can change under you; instead use the workspace id
captured when the modal opened (the modal's snapshot/prop) rather than
activeWorkspaceId so the inspectionStoreKey stays stable. Replace usage of
activeWorkspaceId in the construction of storeKey
(inspectionStoreKey(activeWorkspaceId, server.name)) with the captured workspace
id provided by the modal (e.g., detailModalState.workspaceId or
serverSnapshot.workspaceId) so useInspectionStore reads the record for that
captured workspace; alternatively ensure the modal is closed on workspace
switch. Ensure you update any references in ServerInfoContent (and the call
sites that pass serverSnapshot) so the captured id is available to build the
stable storeKey.
| // Consume inspection detail request (from toast "View changes" CTA) | ||
| useEffect(() => { | ||
| if (detailModalState.isOpen) return; | ||
|
|
||
| const request = readInspectionDetailRequest(); | ||
| if (!request) return; | ||
|
|
||
| clearInspectionDetailRequest(); | ||
|
|
||
| const server = workspaceServers[request.serverName]; | ||
| if (!server) return; | ||
|
|
||
| setDetailModalState((prev) => ({ | ||
| isOpen: true, | ||
| serverName: server.name, | ||
| defaultTab: "overview", | ||
| sessionKey: prev.sessionKey + 1, | ||
| serverSnapshot: server, | ||
| })); | ||
| }, [detailModalState.isOpen, workspaceServers]); |
There was a problem hiding this comment.
This effect can both miss and prematurely delete inspection requests.
The new consumer only reruns when detailModalState.isOpen or workspaceServers changes, so a localStorage write from the toast does nothing when the user is already on the Servers tab. It also clears the request at Line 578 before Line 580 proves the server exists, which permanently drops the CTA if workspaceServers has not hydrated yet. Drive this off an explicit signal/custom event and clear the request only after the modal is actually opened.
Summary
Implements a complete server inspection system that captures MCP server snapshots on connection and detects meaningful changes between connections. Includes a diff engine, state management, coordinator hook, and UI panel to display changes to users.
Key Changes
Diff Engine (
diff-engine.ts): Pure, deterministic functions for normalizing server snapshots, computing diffs, and detecting meaningful changes. Uses stable JSON serialization to handle object key ordering consistently.Inspection Store (
inspection-store.ts): Zustand store persisted to localStorage that maintains inspection records keyed by{workspaceId}::{serverName}, storing both the latest snapshot and diff.Inspection Coordinator Hook (
use-inspection-coordinator.ts): App-level effect hook that monitors all workspace servers for connection transitions, automatically runs the inspection pipeline (snapshot + diff) after successful connects, and shows toast notifications with "View changes" CTA when meaningful changes are detected.Server Changes Panel (
ServerChangesPanel.tsx): New UI component that displays a summary of changes since last connect, with collapsible sections for initialization changes and tool changes (added/removed/changed), including side-by-side JSON diffs for complex fields.Supporting Utilities:
diff-summary.ts: Formats diffs into human-readable one-line summariesinspection-detail-request.ts: localStorage-based request pattern for opening the detail modal from toast CTAs with 5-minute TTLtypes.ts: TypeScript definitions for snapshots, diffs, and related structuresComprehensive Test Coverage: Added 1000+ lines of tests covering the diff engine, coordinator hook, UI component, store, and utilities with realistic scenarios (pagination, errors, stale requests, etc.)
Integration Points:
App.tsx: MounteduseInspectionCoordinatorhook via zero-UI bridge componentServersTab.tsx: Consumes inspection detail requests to open detail modal on Overview tabServerInfoContent.tsx: IntegratedServerChangesPanelinto the Overview tabImplementation Details
tool._meta(preferred) and externaltoolsMetadatarecord, matching existing UI behaviorhttps://claude.ai/code/session_01KBjUEbjapGUfujDhE4gYqA
Note
Medium Risk
Adds new persisted client-side state plus async inspection/diffing on server connect events; mistakes could lead to noisy toasts, stale localStorage, or incorrect server modal navigation, but it does not touch auth/payment/back-end data paths.
Overview
Adds a full client-side inspection system that snapshots MCP server initialization info + tool catalogs on successful connect, computes deterministic diffs on subsequent connects, and persists the latest snapshot/diff per
{workspaceId}::{serverName}in a new Zustandinspection-store(localStorage-backed).When meaningful changes are detected, the app now shows a toast with a “View changes” CTA; clicking it writes a short-lived
inspection-detail-request(TTL) and navigates toServersTab, which consumes the request to open the server detail modal on the Overview tab. The Overview UI is extended with a newServerChangesPanelthat summarizes added/removed/changed tools and init-field changes with expandable JSON before/after views, plus extensive new unit tests covering diffing, summary formatting, coordinator behavior, and request/store lifecycle.Reviewed by Cursor Bugbot for commit 6dbfa85. Bugbot is set up for automated code reviews on this repo. Configure here.