Skip to content

Commit c1c6710

Browse files
olaservoclaude
andcommitted
docs: revise v2 specs based on PR modelcontextprotocol#945 feedback
Address reviewer feedback from cliffhall, pcarleton, KKonstantinov, mattzcarey: Spec changes (v2_ux.md): - Add Server Settings Modal for per-server config (headers, metadata, timeouts, OAuth) - Add Clone button to Server Card Actions - Add OAuth Debugger section for debugging auth flows - Change Resources Screen from resizable panes to accordion pattern - Update Logging Screen to all 8 RFC 5424 levels with distinct colors - Clarify Browse button as local file picker - Expand JSON-RPC Tester to support all methods with custom headers - Add multi-select support for anyOf/oneOf enums in Form Generation Tech stack changes (v2_tech_stack.md): - Select Hono over Express based on community consensus - Add Hono Rationale section with comparison table Prototype changes (shadcn branch): - Resources.tsx: Implement accordion pattern with collapsible sections - Logs.tsx: Add all 8 RFC 5424 levels with distinct color styles 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 55ea357 commit c1c6710

File tree

5 files changed

+612
-167
lines changed

5 files changed

+612
-167
lines changed

claude-progress.md

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,51 @@
11
# Inspector V2 Progress Log
22

3-
## Current Session: 2025-11-30 (Mantine Client Feature Modal Port)
3+
## Current Session: 2025-12-07 (PR #945 Feedback Revisions)
4+
5+
### Context
6+
PR #945 (v2_ux.md) received feedback from reviewers (cliffhall, pcarleton, KKonstantinov, mattzcarey).
7+
This session addresses all feedback and updates both spec documents and prototype code.
8+
9+
### Spec Changes (v2_ux.md)
10+
1. **Server Settings Modal** - Added new section for per-server config:
11+
- Connection Mode (Direct vs Via Proxy)
12+
- Custom Headers key-value editor
13+
- Request Metadata per-server
14+
- Timeouts (Connection/Request)
15+
- OAuth Settings (Client ID, Secret, Scopes)
16+
2. **Clone button** - Added to Server Card Actions
17+
3. **OAuth Debugger** - New section for debugging OAuth flows with step-by-step visualization
18+
4. **Resources Screen** - Changed from resizable panes to accordion pattern
19+
5. **Logging Screen** - Updated to all 8 RFC 5424 levels with distinct colors
20+
6. **Browse button** - Clarified as local file picker (not registry browser)
21+
7. **Advanced JSON-RPC Tester** - Expanded from experimental-only to all methods, added custom headers
22+
8. **Form Generation** - Added multi-select support for anyOf/oneOf enums
23+
24+
### Spec Changes (v2_tech_stack.md)
25+
- Changed framework selection from Express to Hono (consensus from PR discussion)
26+
- Added Hono Rationale section with comparison table
27+
28+
### Prototype Changes (v2/prototype/shadcn)
29+
- **Resources.tsx** - Implemented accordion pattern with collapsible sections
30+
- **Logs.tsx** - Added all 8 log levels to visible level checkboxes, distinct colors
31+
32+
### Files Modified
33+
- `specification/v2_ux.md` - 8 major additions/revisions
34+
- `specification/v2_tech_stack.md` - Hono selection and rationale
35+
- `client/src/pages/Resources.tsx` - Accordion pattern implementation
36+
- `client/src/pages/Logs.tsx` - 8 log levels with RFC 5424 colors
37+
38+
### Build Status
39+
- Client build verified passing
40+
41+
### Next Steps
42+
- Apply same prototype changes to v2/prototype/mantine branch
43+
- Create ServerSettingsModal.tsx component
44+
- Add OAuth debugger modal
45+
46+
---
47+
48+
## Previous Session: 2025-11-30 (Mantine Client Feature Modal Port)
449

550
### Completed
651
- Ported 3 client feature modals from shadcn to Mantine for feature parity:

client/src/pages/Logs.tsx

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,16 @@ const mockLogs = [
2424

2525
const logLevels = ['debug', 'info', 'notice', 'warning', 'error', 'critical', 'alert', 'emergency'];
2626

27+
// RFC 5424 log levels with distinct colors per v2_ux.md spec
2728
const levelColors: Record<string, string> = {
28-
debug: 'text-gray-400',
29-
info: 'text-blue-400',
30-
notice: 'text-blue-300',
31-
warning: 'text-yellow-400',
32-
error: 'text-red-400',
33-
critical: 'text-red-500',
34-
alert: 'text-red-600',
35-
emergency: 'text-red-700',
29+
debug: 'text-gray-400', // Gray - Normal
30+
info: 'text-blue-400', // Blue - Normal
31+
notice: 'text-cyan-400', // Cyan - Normal
32+
warning: 'text-yellow-400', // Yellow - Normal
33+
error: 'text-red-400', // Red - Normal
34+
critical: 'text-red-500 font-bold', // Red - Bold
35+
alert: 'text-fuchsia-500 font-bold', // Magenta - Bold
36+
emergency: 'bg-red-600 text-white px-1 rounded', // White on Red - Background
3637
};
3738

3839
const levelVariants: Record<string, 'default' | 'secondary' | 'warning' | 'error'> = {
@@ -50,11 +51,16 @@ export function Logs() {
5051
const [logLevel, setLogLevel] = useState('debug');
5152
const [filter, setFilter] = useState('');
5253
const [autoScroll, setAutoScroll] = useState(true);
54+
// All 8 RFC 5424 log levels
5355
const [visibleLevels, setVisibleLevels] = useState({
5456
debug: true,
5557
info: true,
58+
notice: true,
5659
warning: true,
5760
error: true,
61+
critical: true,
62+
alert: true,
63+
emergency: true,
5864
});
5965

6066
const toggleLevel = (level: string) => {

client/src/pages/Resources.tsx

Lines changed: 136 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Button } from '@/components/ui/button';
44
import { Input } from '@/components/ui/input';
55
import { Badge } from '@/components/ui/badge';
66
import { ListChangedIndicator } from '@/components/ListChangedIndicator';
7+
import { ChevronDown, ChevronRight } from 'lucide-react';
78

89
// Resource interface with annotations per MCP spec
910
interface Resource {
@@ -59,13 +60,57 @@ function getPriorityLabel(priority: number): { label: string; variant: 'default'
5960
return { label: 'low', variant: 'default' };
6061
}
6162

63+
// Collapsible section component for accordion pattern
64+
function AccordionSection({
65+
title,
66+
count,
67+
isOpen,
68+
onToggle,
69+
children,
70+
}: {
71+
title: string;
72+
count: number;
73+
isOpen: boolean;
74+
onToggle: () => void;
75+
children: React.ReactNode;
76+
}) {
77+
return (
78+
<div className="border rounded-md">
79+
<button
80+
className="w-full flex items-center gap-2 p-2 text-sm font-medium hover:bg-muted/50 transition-colors"
81+
onClick={onToggle}
82+
>
83+
{isOpen ? (
84+
<ChevronDown className="h-4 w-4" />
85+
) : (
86+
<ChevronRight className="h-4 w-4" />
87+
)}
88+
<span>{title}</span>
89+
<span className="text-muted-foreground">({count})</span>
90+
</button>
91+
{isOpen && <div className="p-2 pt-0 border-t">{children}</div>}
92+
</div>
93+
);
94+
}
95+
6296
export function Resources() {
6397
const [hasResourcesChanged, setHasResourcesChanged] = useState(true);
6498
const [selectedResource, setSelectedResource] = useState<Resource>(mockResources[0]);
6599
const [searchFilter, setSearchFilter] = useState('');
66100
const [templateInputs, setTemplateInputs] = useState<Record<string, string>>({});
67101
const [subscriptions, setSubscriptions] = useState<Subscription[]>(mockSubscriptions);
68102

103+
// Accordion state - Resources expanded by default, others collapsed
104+
const [expandedSections, setExpandedSections] = useState({
105+
resources: true,
106+
templates: false,
107+
subscriptions: false,
108+
});
109+
110+
const toggleSection = (section: keyof typeof expandedSections) => {
111+
setExpandedSections((prev) => ({ ...prev, [section]: !prev[section] }));
112+
};
113+
69114
const handleRefresh = () => {
70115
setHasResourcesChanged(false);
71116
};
@@ -125,86 +170,99 @@ export function Resources() {
125170
onChange={(e) => setSearchFilter(e.target.value)}
126171
/>
127172

128-
<div className="space-y-3">
173+
{/* Accordion Sections */}
174+
<div className="space-y-2">
129175
{/* Resources Section */}
130-
<p className="text-xs font-semibold text-muted-foreground uppercase">
131-
Resources
132-
</p>
133-
<div className="space-y-1">
134-
{filteredResources.map((resource) => (
135-
<div key={resource.uri} className="space-y-1">
136-
<Button
137-
variant={selectedResource.uri === resource.uri ? 'default' : 'ghost'}
138-
className="w-full justify-start text-sm"
139-
size="sm"
140-
onClick={() => setSelectedResource(resource)}
141-
>
142-
{resource.uri.split('/').pop()}
143-
</Button>
144-
{/* Annotation badges */}
145-
{resource.annotations && Object.keys(resource.annotations).length > 0 && (
146-
<div className="flex flex-wrap gap-1 pl-3 pb-1">
147-
{resource.annotations.audience && (
148-
<Badge variant="secondary" className="text-xs">
149-
{resource.annotations.audience}
150-
</Badge>
151-
)}
152-
{resource.annotations.priority !== undefined && (
153-
<Badge
154-
variant={getPriorityLabel(resource.annotations.priority).variant}
155-
className="text-xs"
156-
>
157-
priority: {getPriorityLabel(resource.annotations.priority).label}
158-
</Badge>
159-
)}
160-
</div>
161-
)}
162-
</div>
163-
))}
164-
</div>
176+
<AccordionSection
177+
title="Resources"
178+
count={filteredResources.length}
179+
isOpen={expandedSections.resources}
180+
onToggle={() => toggleSection('resources')}
181+
>
182+
<div className="space-y-1 pt-2">
183+
{filteredResources.map((resource) => (
184+
<div key={resource.uri} className="space-y-1">
185+
<Button
186+
variant={selectedResource.uri === resource.uri ? 'default' : 'ghost'}
187+
className="w-full justify-start text-sm"
188+
size="sm"
189+
onClick={() => setSelectedResource(resource)}
190+
>
191+
{resource.uri.split('/').pop()}
192+
</Button>
193+
{/* Annotation badges */}
194+
{resource.annotations && Object.keys(resource.annotations).length > 0 && (
195+
<div className="flex flex-wrap gap-1 pl-3 pb-1">
196+
{resource.annotations.audience && (
197+
<Badge variant="secondary" className="text-xs">
198+
{resource.annotations.audience}
199+
</Badge>
200+
)}
201+
{resource.annotations.priority !== undefined && (
202+
<Badge
203+
variant={getPriorityLabel(resource.annotations.priority).variant}
204+
className="text-xs"
205+
>
206+
priority: {getPriorityLabel(resource.annotations.priority).label}
207+
</Badge>
208+
)}
209+
</div>
210+
)}
211+
</div>
212+
))}
213+
</div>
214+
</AccordionSection>
165215

166216
{/* Templates Section */}
167-
<p className="text-xs font-semibold text-muted-foreground uppercase pt-2">
168-
Templates
169-
</p>
170-
<div className="space-y-2">
171-
{mockTemplates.map((template) => {
172-
const varMatch = template.uriTemplate.match(/\{(\w+)\}/);
173-
const varName = varMatch ? varMatch[1] : '';
174-
return (
175-
<div key={template.uriTemplate} className="space-y-1">
176-
<p className="text-sm text-muted-foreground">{template.uriTemplate}</p>
177-
<div className="flex gap-1">
178-
<Input
179-
placeholder={varName}
180-
className="h-7 text-xs"
181-
value={templateInputs[template.uriTemplate] || ''}
182-
onChange={(e) =>
183-
handleTemplateInputChange(template.uriTemplate, e.target.value)
184-
}
185-
/>
186-
<Button
187-
size="sm"
188-
variant="outline"
189-
className="h-7 px-2"
190-
onClick={() => handleTemplateGo(template)}
191-
>
192-
Go
193-
</Button>
217+
<AccordionSection
218+
title="Templates"
219+
count={mockTemplates.length}
220+
isOpen={expandedSections.templates}
221+
onToggle={() => toggleSection('templates')}
222+
>
223+
<div className="space-y-2 pt-2">
224+
{mockTemplates.map((template) => {
225+
const varMatch = template.uriTemplate.match(/\{(\w+)\}/);
226+
const varName = varMatch ? varMatch[1] : '';
227+
return (
228+
<div key={template.uriTemplate} className="space-y-1">
229+
<p className="text-sm text-muted-foreground">{template.uriTemplate}</p>
230+
<div className="flex gap-1">
231+
<Input
232+
placeholder={varName}
233+
className="h-7 text-xs"
234+
value={templateInputs[template.uriTemplate] || ''}
235+
onChange={(e) =>
236+
handleTemplateInputChange(template.uriTemplate, e.target.value)
237+
}
238+
/>
239+
<Button
240+
size="sm"
241+
variant="outline"
242+
className="h-7 px-2"
243+
onClick={() => handleTemplateGo(template)}
244+
>
245+
Go
246+
</Button>
247+
</div>
194248
</div>
195-
</div>
196-
);
197-
})}
198-
</div>
249+
);
250+
})}
251+
</div>
252+
</AccordionSection>
199253

200254
{/* Subscriptions Section */}
201-
{subscriptions.length > 0 && (
202-
<>
203-
<p className="text-xs font-semibold text-muted-foreground uppercase pt-2">
204-
Subscriptions
205-
</p>
206-
<div className="space-y-1">
207-
{subscriptions.map((sub) => (
255+
<AccordionSection
256+
title="Subscriptions"
257+
count={subscriptions.length}
258+
isOpen={expandedSections.subscriptions}
259+
onToggle={() => toggleSection('subscriptions')}
260+
>
261+
<div className="space-y-1 pt-2">
262+
{subscriptions.length === 0 ? (
263+
<p className="text-sm text-muted-foreground">No active subscriptions</p>
264+
) : (
265+
subscriptions.map((sub) => (
208266
<div
209267
key={sub.uri}
210268
className="flex items-center justify-between text-sm py-1"
@@ -222,10 +280,10 @@ export function Resources() {
222280
Unsub
223281
</Button>
224282
</div>
225-
))}
226-
</div>
227-
</>
228-
)}
283+
))
284+
)}
285+
</div>
286+
</AccordionSection>
229287
</div>
230288
</CardContent>
231289
</Card>

specification/v2_tech_stack.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,34 @@ Let's choose a feature-rich component set with easy theming control
2929

3030
### Transport Operation
3131
Let's consider how to operate the server transports.
32-
* -[x] [Express](https://expressjs.com/)
32+
* -[x] [Hono](https://hono.dev/)
33+
* -[ ] [Express](https://expressjs.com/)
3334
* -[ ] Node:[http](https://nodejs.org/docs/v22.18.0/api/http.html)
3435

36+
#### Hono Rationale
37+
38+
Hono is selected based on community consensus (PR #945 discussion) for alignment with modern web standards and the TypeScript SDK v2 direction.
39+
40+
**Why Hono over Express:**
41+
42+
| Requirement | Hono | Express |
43+
|-------------|------|---------|
44+
| Bundle size | 12kb | ~1mb |
45+
| Web Standards (Request/Response) | Yes - Native | No - Shimmed |
46+
| TypeScript native | Yes | No - @types package |
47+
| Tree-shakable | Yes - Fully | No |
48+
| HTTP/2 support | Yes | No (SPDY dropped) |
49+
| Built-in middleware | Yes - Body parsing, auth, etc. | No - Requires plugins |
50+
| Edge/serverless deployment | Yes - Native | Partial - Requires adapters |
51+
52+
**Benefits:**
53+
54+
1. **Web Standards Alignment** - Uses native `Request`/`Response` objects, enabling deployment across Node, Deno, Bun, serverless, and edge environments
55+
2. **TypeScript Native** - Full type safety without external type packages, no monkey-patching of request objects
56+
3. **Future-proofing** - HTTP/2 support enables potential gRPC transport; aligns with TypeScript SDK v2 plans
57+
4. **Developer Experience** - Simpler API, type-safe context, smaller learning curve
58+
5. **Bundle Efficiency** - Dramatically smaller footprint benefits both development and deployment
59+
3560
### Logging
3661
Let's step up our logging capability with an advanced logger:
3762
* -[ ] [Winston](https://github.com/winstonjs/winston?tab=readme-ov-file#winston)

0 commit comments

Comments
 (0)