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.
Files changed (3) hide show
  1. package/README.md +10 -6
  2. package/lib/server.js +159 -14
  3. 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 the MCP (Model Context Protocol) using SSE (Server-Sent Events) transport.
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/sse"
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/sse"
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/sse"
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/sse"
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 communicates over the SSE protocol. Use your editor's steps for setting up an SSE-compatible MCP server, and use the URL: `http://127.0.0.1:3846/sse`
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 from the Vibe Annotations extension with enhanced context including element screenshots and parent hierarchy. Use when users want to review, implement, or address their UI feedback and comments. MULTI-PROJECT SAFETY: This tool now 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. Returns enhanced warnings when multiple projects detected, with suggested URL filters for each project. Annotations include viewport dimensions for responsive breakpoint mapping. Use this tool when users mention: annotations, comments, feedback, suggestions, notes, marked changes, or visual issues they\'ve identified.',
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: filtered.slice(0, limit),
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-annotations-server",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Global MCP server for Vibe Annotations browser extension",
5
5
  "main": "lib/server.js",
6
6
  "type": "module",