vibe-annotations-server 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -6
- package/lib/server.js +159 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,13 +46,17 @@ vibe-annotations-server logs -f
|
|
|
46
46
|
|
|
47
47
|
## AI Coding Agent Integration
|
|
48
48
|
|
|
49
|
-
After starting the server, connect it to your AI coding agent. The server supports multiple agents via
|
|
49
|
+
After starting the server, connect it to your AI coding agent. The server supports multiple agents via MCP (Model Context Protocol) using both HTTP and SSE transports.
|
|
50
50
|
|
|
51
51
|
### Claude Code
|
|
52
52
|
|
|
53
53
|
In your project directory, run:
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
|
+
# Recommended (HTTP transport - more stable)
|
|
57
|
+
claude mcp add --transport http vibe-annotations http://127.0.0.1:3846/mcp
|
|
58
|
+
|
|
59
|
+
# Alternative (SSE transport - for compatibility)
|
|
56
60
|
claude mcp add --transport sse vibe-annotations http://127.0.0.1:3846/sse
|
|
57
61
|
```
|
|
58
62
|
|
|
@@ -67,7 +71,7 @@ claude mcp add --transport sse vibe-annotations http://127.0.0.1:3846/sse
|
|
|
67
71
|
{
|
|
68
72
|
"mcpServers": {
|
|
69
73
|
"vibe-annotations": {
|
|
70
|
-
"url": "http://127.0.0.1:3846/
|
|
74
|
+
"url": "http://127.0.0.1:3846/mcp"
|
|
71
75
|
}
|
|
72
76
|
}
|
|
73
77
|
}
|
|
@@ -84,7 +88,7 @@ claude mcp add --transport sse vibe-annotations http://127.0.0.1:3846/sse
|
|
|
84
88
|
{
|
|
85
89
|
"mcpServers": {
|
|
86
90
|
"vibe-annotations": {
|
|
87
|
-
"serverUrl": "http://127.0.0.1:3846/
|
|
91
|
+
"serverUrl": "http://127.0.0.1:3846/mcp"
|
|
88
92
|
}
|
|
89
93
|
}
|
|
90
94
|
}
|
|
@@ -103,7 +107,7 @@ claude mcp add --transport sse vibe-annotations http://127.0.0.1:3846/sse
|
|
|
103
107
|
"mcpServers": {
|
|
104
108
|
"vibe-annotations": {
|
|
105
109
|
"type": "sse",
|
|
106
|
-
"url": "http://127.0.0.1:3846/
|
|
110
|
+
"url": "http://127.0.0.1:3846/mcp"
|
|
107
111
|
}
|
|
108
112
|
}
|
|
109
113
|
}
|
|
@@ -119,13 +123,13 @@ Other code editors and tools that support SSE (Server-Sent Events) can also conn
|
|
|
119
123
|
{
|
|
120
124
|
"mcpServers": {
|
|
121
125
|
"vibe-annotations": {
|
|
122
|
-
"url": "http://127.0.0.1:3846/
|
|
126
|
+
"url": "http://127.0.0.1:3846/mcp"
|
|
123
127
|
}
|
|
124
128
|
}
|
|
125
129
|
}
|
|
126
130
|
```
|
|
127
131
|
|
|
128
|
-
**Note:** The Vibe Annotations MCP server
|
|
132
|
+
**Note:** The Vibe Annotations MCP server supports both HTTP and SSE transports. HTTP transport is recommended for better stability. Use the URL: `http://127.0.0.1:3846/mcp` (HTTP) or `http://127.0.0.1:3846/sse` (SSE).
|
|
129
133
|
|
|
130
134
|
## Architecture
|
|
131
135
|
|
package/lib/server.js
CHANGED
|
@@ -348,14 +348,16 @@ class LocalAnnotationsServer {
|
|
|
348
348
|
return server;
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
+
/**
|
|
352
|
+
* Set up MCP tool handlers for this server instance
|
|
353
|
+
*/
|
|
351
354
|
setupMCPHandlersForServer(server) {
|
|
352
|
-
// List tools
|
|
353
355
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
354
356
|
return {
|
|
355
357
|
tools: [
|
|
356
358
|
{
|
|
357
359
|
name: 'read_annotations',
|
|
358
|
-
description: 'Retrieves user-created visual annotations
|
|
360
|
+
description: 'Retrieves user-created visual annotations with pagination support. Returns annotation data with has_screenshot flag instead of full screenshot data for token efficiency. Use url parameter to filter by project. MULTI-PROJECT SAFETY: This tool detects when annotations exist across multiple localhost projects and provides warnings with specific URL filtering guidance. CRITICAL WORKFLOW: (1) First call WITHOUT url parameter to see all projects, (2) Use get_project_context tool to determine current project, (3) Call again WITH url parameter (e.g., "http://localhost:3000/*") to filter for current project only. This prevents cross-project contamination where you might implement changes in wrong codebase. Use limit and offset parameters for pagination when handling large annotation sets. Use this tool when users mention: annotations, comments, feedback, suggestions, notes, marked changes, or visual issues they\'ve identified.',
|
|
359
361
|
inputSchema: {
|
|
360
362
|
type: 'object',
|
|
361
363
|
properties: {
|
|
@@ -372,6 +374,12 @@ class LocalAnnotationsServer {
|
|
|
372
374
|
maximum: 200,
|
|
373
375
|
description: 'Maximum number of annotations to return'
|
|
374
376
|
},
|
|
377
|
+
offset: {
|
|
378
|
+
type: 'number',
|
|
379
|
+
default: 0,
|
|
380
|
+
minimum: 0,
|
|
381
|
+
description: 'Number of annotations to skip for pagination'
|
|
382
|
+
},
|
|
375
383
|
url: {
|
|
376
384
|
type: 'string',
|
|
377
385
|
description: 'Filter by specific localhost URL. Supports exact match (e.g., "http://localhost:3000/dashboard") or pattern match with base URL (e.g., "http://localhost:3000/" or "http://localhost:3000/*" to get all annotations from that project)'
|
|
@@ -429,12 +437,26 @@ class LocalAnnotationsServer {
|
|
|
429
437
|
required: ['url_pattern'],
|
|
430
438
|
additionalProperties: false
|
|
431
439
|
}
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
name: 'get_annotation_screenshot',
|
|
443
|
+
description: 'Retrieves screenshot data for a specific annotation when visual context is needed to understand and implement the user\'s feedback. The read_annotations tool returns a has_screenshot flag to indicate availability. WHEN TO USE THIS TOOL: (1) Annotation mentions visual/layout/styling/positioning issues (e.g., "make it look better", "spacing is off", "layout is broken"), (2) You need to see exact element positioning, colors, or visual hierarchy, (3) The element_context text data seems insufficient to implement the fix accurately. WHEN TO SKIP: (1) Simple text content changes, (2) Clear functional bugs with sufficient text description, (3) Cases where element_context (tag, classes, styles, position) provides enough implementation detail. The screenshot includes viewport dimensions, element bounds, and visual context that complements the text-based element_context data.',
|
|
444
|
+
inputSchema: {
|
|
445
|
+
type: 'object',
|
|
446
|
+
properties: {
|
|
447
|
+
id: {
|
|
448
|
+
type: 'string',
|
|
449
|
+
description: 'Annotation ID to get screenshot for'
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
required: ['id'],
|
|
453
|
+
additionalProperties: false
|
|
454
|
+
}
|
|
432
455
|
}
|
|
433
456
|
]
|
|
434
457
|
};
|
|
435
458
|
});
|
|
436
459
|
|
|
437
|
-
// Handle tool calls
|
|
438
460
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
439
461
|
const { name, arguments: args } = request.params;
|
|
440
462
|
|
|
@@ -443,7 +465,7 @@ class LocalAnnotationsServer {
|
|
|
443
465
|
case 'read_annotations': {
|
|
444
466
|
const result = await this.readAnnotations(args || {});
|
|
445
467
|
const { annotations, projectInfo, multiProjectWarning } = result;
|
|
446
|
-
|
|
468
|
+
|
|
447
469
|
return {
|
|
448
470
|
content: [
|
|
449
471
|
{
|
|
@@ -514,6 +536,23 @@ class LocalAnnotationsServer {
|
|
|
514
536
|
};
|
|
515
537
|
}
|
|
516
538
|
|
|
539
|
+
case 'get_annotation_screenshot': {
|
|
540
|
+
const result = await this.getAnnotationScreenshot(args);
|
|
541
|
+
return {
|
|
542
|
+
content: [
|
|
543
|
+
{
|
|
544
|
+
type: 'text',
|
|
545
|
+
text: JSON.stringify({
|
|
546
|
+
tool: 'get_annotation_screenshot',
|
|
547
|
+
status: 'success',
|
|
548
|
+
data: result,
|
|
549
|
+
timestamp: new Date().toISOString()
|
|
550
|
+
}, null, 2)
|
|
551
|
+
}
|
|
552
|
+
]
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
|
|
517
556
|
default:
|
|
518
557
|
throw new Error(`Unknown tool: ${name}`);
|
|
519
558
|
}
|
|
@@ -623,6 +662,25 @@ class LocalAnnotationsServer {
|
|
|
623
662
|
}
|
|
624
663
|
}
|
|
625
664
|
|
|
665
|
+
/**
|
|
666
|
+
* Apply an annotations update using serialized read→mutate→save operations
|
|
667
|
+
* This prevents race conditions during concurrent operations by chaining
|
|
668
|
+
* all updates onto the existing saveLock Promise.
|
|
669
|
+
*
|
|
670
|
+
* @param {Function} mutator - Function that receives current annotations and returns result
|
|
671
|
+
* @returns {Promise} Promise that resolves with the mutator's return value
|
|
672
|
+
*/
|
|
673
|
+
async applyAnnotationsUpdate(mutator) {
|
|
674
|
+
// Chain onto saveLock to serialize read→mutate→save
|
|
675
|
+
this.saveLock = this.saveLock.then(async () => {
|
|
676
|
+
const current = await this.loadAnnotations();
|
|
677
|
+
const result = await mutator(current);
|
|
678
|
+
await this._saveAnnotationsInternal(current);
|
|
679
|
+
return result;
|
|
680
|
+
});
|
|
681
|
+
return this.saveLock;
|
|
682
|
+
}
|
|
683
|
+
|
|
626
684
|
async ensureDataFile() {
|
|
627
685
|
const dataDir = path.dirname(DATA_FILE);
|
|
628
686
|
if (!existsSync(dataDir)) {
|
|
@@ -648,14 +706,14 @@ class LocalAnnotationsServer {
|
|
|
648
706
|
// MCP Tool implementations
|
|
649
707
|
async readAnnotations(args) {
|
|
650
708
|
const annotations = await this.loadAnnotations();
|
|
651
|
-
const { status = 'pending', limit = 50, url } = args;
|
|
652
|
-
|
|
709
|
+
const { status = 'pending', limit = 50, offset = 0, url } = args;
|
|
710
|
+
|
|
653
711
|
let filtered = annotations;
|
|
654
|
-
|
|
712
|
+
|
|
655
713
|
if (status !== 'all') {
|
|
656
714
|
filtered = filtered.filter(a => a.status === status);
|
|
657
715
|
}
|
|
658
|
-
|
|
716
|
+
|
|
659
717
|
if (url) {
|
|
660
718
|
// Support both exact URL matching and base URL pattern matching
|
|
661
719
|
if (url.includes('*') || url.endsWith('/')) {
|
|
@@ -667,7 +725,7 @@ class LocalAnnotationsServer {
|
|
|
667
725
|
filtered = filtered.filter(a => a.url === url);
|
|
668
726
|
}
|
|
669
727
|
}
|
|
670
|
-
|
|
728
|
+
|
|
671
729
|
// Group annotations by base URL for better context
|
|
672
730
|
const groupedByProject = {};
|
|
673
731
|
filtered.forEach(annotation => {
|
|
@@ -682,11 +740,11 @@ class LocalAnnotationsServer {
|
|
|
682
740
|
// Handle invalid URLs gracefully
|
|
683
741
|
}
|
|
684
742
|
});
|
|
685
|
-
|
|
743
|
+
|
|
686
744
|
// Add project context to response
|
|
687
745
|
const projectCount = Object.keys(groupedByProject).length;
|
|
688
746
|
let multiProjectWarning = null;
|
|
689
|
-
|
|
747
|
+
|
|
690
748
|
if (projectCount > 1 && !url) {
|
|
691
749
|
const projectSuggestions = Object.keys(groupedByProject).map(baseUrl => `"${baseUrl}/*"`).join(' or ');
|
|
692
750
|
multiProjectWarning = {
|
|
@@ -698,7 +756,7 @@ class LocalAnnotationsServer {
|
|
|
698
756
|
};
|
|
699
757
|
console.warn(`MULTI-PROJECT WARNING: Found annotations from ${projectCount} different projects. Use url parameter: ${projectSuggestions}`);
|
|
700
758
|
}
|
|
701
|
-
|
|
759
|
+
|
|
702
760
|
// Build project info for better context
|
|
703
761
|
const projectInfo = Object.entries(groupedByProject).map(([baseUrl, annotations]) => ({
|
|
704
762
|
base_url: baseUrl,
|
|
@@ -706,9 +764,31 @@ class LocalAnnotationsServer {
|
|
|
706
764
|
paths: [...new Set(annotations.map(a => new URL(a.url).pathname))].slice(0, 5), // Show up to 5 unique paths
|
|
707
765
|
recommended_filter: `${baseUrl}/*`
|
|
708
766
|
}));
|
|
709
|
-
|
|
767
|
+
|
|
768
|
+
// Apply pagination with offset
|
|
769
|
+
const total = filtered.length;
|
|
770
|
+
const paginatedResults = filtered.slice(offset, offset + limit);
|
|
771
|
+
|
|
772
|
+
// Calculate pagination metadata
|
|
773
|
+
const pagination = {
|
|
774
|
+
total: total,
|
|
775
|
+
limit: limit,
|
|
776
|
+
offset: offset,
|
|
777
|
+
has_more: (offset + limit) < total
|
|
778
|
+
};
|
|
779
|
+
|
|
780
|
+
// Transform annotations to strip screenshot data and add has_screenshot flag
|
|
781
|
+
const annotationsWithScreenshotFlag = paginatedResults.map(annotation => {
|
|
782
|
+
const { screenshot, ...annotationWithoutScreenshot } = annotation;
|
|
783
|
+
return {
|
|
784
|
+
...annotationWithoutScreenshot,
|
|
785
|
+
has_screenshot: !!(screenshot && screenshot.data_url)
|
|
786
|
+
};
|
|
787
|
+
});
|
|
788
|
+
|
|
710
789
|
return {
|
|
711
|
-
annotations:
|
|
790
|
+
annotations: annotationsWithScreenshotFlag,
|
|
791
|
+
pagination: pagination,
|
|
712
792
|
projectInfo: projectInfo,
|
|
713
793
|
multiProjectWarning: multiProjectWarning
|
|
714
794
|
};
|
|
@@ -737,6 +817,71 @@ class LocalAnnotationsServer {
|
|
|
737
817
|
};
|
|
738
818
|
}
|
|
739
819
|
|
|
820
|
+
/**
|
|
821
|
+
* Get screenshot data for a specific annotation
|
|
822
|
+
* @param {Object} args - Arguments object
|
|
823
|
+
* @param {string} args.id - Annotation ID to get screenshot for
|
|
824
|
+
* @returns {Object} Screenshot data response with annotation_id, screenshot, and message
|
|
825
|
+
*/
|
|
826
|
+
async getAnnotationScreenshot(args) {
|
|
827
|
+
const { id } = args;
|
|
828
|
+
|
|
829
|
+
// Validate input
|
|
830
|
+
if (!id || typeof id !== 'string') {
|
|
831
|
+
return {
|
|
832
|
+
annotation_id: id || '',
|
|
833
|
+
screenshot: null,
|
|
834
|
+
message: 'Invalid annotation ID: must be a non-empty string'
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
try {
|
|
839
|
+
// Load annotations - we only need to find the specific one
|
|
840
|
+
const annotations = await this.loadAnnotations();
|
|
841
|
+
|
|
842
|
+
// Find annotation by ID
|
|
843
|
+
const annotation = annotations.find(a => a.id === id);
|
|
844
|
+
|
|
845
|
+
if (!annotation) {
|
|
846
|
+
return {
|
|
847
|
+
annotation_id: id,
|
|
848
|
+
screenshot: null,
|
|
849
|
+
message: 'Annotation not found'
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// Check if annotation has screenshot data
|
|
854
|
+
if (!annotation.screenshot || !annotation.screenshot.data_url) {
|
|
855
|
+
return {
|
|
856
|
+
annotation_id: id,
|
|
857
|
+
screenshot: null,
|
|
858
|
+
message: 'No screenshot available for this annotation'
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Return screenshot data in the contract format
|
|
863
|
+
return {
|
|
864
|
+
annotation_id: id,
|
|
865
|
+
screenshot: {
|
|
866
|
+
data_url: annotation.screenshot.data_url,
|
|
867
|
+
compression: annotation.screenshot.compression,
|
|
868
|
+
crop_area: annotation.screenshot.crop_area,
|
|
869
|
+
element_bounds: annotation.screenshot.element_bounds,
|
|
870
|
+
timestamp: annotation.screenshot.timestamp,
|
|
871
|
+
viewport: annotation.viewport || null
|
|
872
|
+
},
|
|
873
|
+
message: 'Screenshot retrieved successfully'
|
|
874
|
+
};
|
|
875
|
+
|
|
876
|
+
} catch (error) {
|
|
877
|
+
return {
|
|
878
|
+
annotation_id: id,
|
|
879
|
+
screenshot: null,
|
|
880
|
+
message: `Failed to retrieve screenshot: ${error.message}`
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
740
885
|
async deleteProjectAnnotations(args) {
|
|
741
886
|
const { url_pattern, confirm = false } = args;
|
|
742
887
|
|