Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f7637a0
feat(shared): add detail level enums and optional text/css fields to
ashtonchew Sep 27, 2025
4860ac2
feat(extension): capture text variants and full computed styles in el…
ashtonchew Sep 27, 2025
3254237
feat(server): allow textDetail/cssLevel request parameters and prune …
ashtonchew Sep 27, 2025
041a2ba
feat(server): add element shaping utility for dynamic detail control
ashtonchew Sep 27, 2025
de8f249
feat(server): cover element detail normalization and shaping
ashtonchew Sep 27, 2025
cf3fc32
docs(readme): document optional MCP tool arguments for context control
ashtonchew Sep 27, 2025
2e468ab
feat(changeset): update version with minor change patch
ashtonchew Sep 27, 2025
7cf0678
fix(extension): use LEGACY_ELEMENT_SELECTED enum value
ashtonchew Oct 1, 2025
b5b1d29
feat(server): adapt dynamic context control to ProcessedPointedDOMEle…
ashtonchew Oct 1, 2025
cbb4704
feat(server): store full CSS properties in ProcessedPointedDOMElement
ashtonchew Oct 1, 2025
4ce493e
refactor(server): remove LEGACY_ELEMENT_SELECTED support and dead code
ashtonchew Oct 1, 2025
ca7a516
docs: add v0.6.0 changeset and update architecture documentation
ashtonchew Oct 1, 2025
4fb84d0
fix(extension): return true from message listener to keep channel open
ashtonchew Oct 2, 2025
db24adb
refactor(extension): remove legacy element serializer
ashtonchew Nov 7, 2025
d74cd48
feat(shared): switch detail levels to enums
ashtonchew Nov 7, 2025
08339fb
refactor(server): simplify shared state and css payload
ashtonchew Nov 7, 2025
2faf088
docs(readme): clarify numeric detail levels
ashtonchew Nov 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .changeset/remove-legacy-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@mcp-pointer/server": minor
"@mcp-pointer/shared": minor
---

**Architecture Cleanup & Improvements**

- **Server**: Store full CSS properties in `cssProperties` instead of filtering to 5 properties
- **Server**: Remove LEGACY_ELEMENT_SELECTED support - only DOM_ELEMENT_POINTED is now supported
- **Server**: Delete unused files (`mcp-handler.ts`, `websocket-server.ts`)
- **Server**: Simplify types - remove StateDataV1 and LegacySharedState
- **Server**: Dynamic CSS filtering now happens on-the-fly during MCP tool calls based on cssLevel parameter

This enables full CSS details to be accessible without re-pointing to elements, with filtering applied server-side based on tool parameters.
42 changes: 30 additions & 12 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,16 @@ packages/
├── server/ # @mcp-pointer/server - MCP Server (TypeScript)
│ ├── src/
│ │ ├── start.ts # Main server entry point
│ │ ├── cli.ts # Command line interface
│ │ ├── websocket-server.ts
│ │ └── mcp-handler.ts
│ │ ├── cli.ts # Command line interface
│ │ ├── message-handler.ts # Message routing & state building
│ │ ├── services/
│ │ │ ├── websocket-service.ts # WebSocket with leader election
│ │ │ ├── mcp-service.ts # MCP protocol handler
│ │ │ ├── element-processor.ts # Raw→Processed conversion
│ │ │ └── shared-state-service.ts # State persistence
│ │ └── utils/
│ │ ├── dom-extractor.ts # HTML parsing utilities
│ │ └── element-detail.ts # Dynamic CSS/text filtering
│ ├── dist/
│ │ └── cli.cjs # Bundled standalone CLI
│ └── package.json
Expand All @@ -73,15 +80,17 @@ packages/
│ ├── src/
│ │ ├── background.ts # Service worker
│ │ ├── content.ts # Element selection
│ │ └── element-sender-service.ts
│ │ └── services/
│ │ └── element-sender-service.ts # WebSocket client
│ ├── dev/ # Development build (with logging)
│ ├── dist/ # Production build (minified)
│ └── manifest.json
└── shared/ # @mcp-pointer/shared - Shared TypeScript types
├── src/
│ ├── Logger.ts
│ └── types.ts
│ ├── logger.ts
│ ├── types.ts
│ └── detail.ts # CSS/text detail level constants
└── package.json
```

Expand Down Expand Up @@ -119,9 +128,16 @@ packages/
├── server/ # @mcp-pointer/server - MCP Server (TypeScript)
│ ├── src/
│ │ ├── start.ts # Main server entry point
│ │ ├── cli.ts # Command line interface
│ │ ├── websocket-server.ts
│ │ └── mcp-handler.ts
│ │ ├── cli.ts # Command line interface
│ │ ├── message-handler.ts # Message routing & state building
│ │ ├── services/
│ │ │ ├── websocket-service.ts # WebSocket with leader election
│ │ │ ├── mcp-service.ts # MCP protocol handler
│ │ │ ├── element-processor.ts # Raw→Processed conversion
│ │ │ └── shared-state-service.ts # State persistence
│ │ └── utils/
│ │ ├── dom-extractor.ts # HTML parsing utilities
│ │ └── element-detail.ts # Dynamic CSS/text filtering
│ ├── dist/
│ │ └── cli.cjs # Bundled standalone CLI
│ └── package.json
Expand All @@ -130,15 +146,17 @@ packages/
│ ├── src/
│ │ ├── background.ts # Service worker
│ │ ├── content.ts # Element selection
│ │ └── element-sender-service.ts
│ │ └── services/
│ │ └── element-sender-service.ts # WebSocket client
│ ├── dev/ # Development build (with logging)
│ ├── dist/ # Production build (minified)
│ └── manifest.json
└── shared/ # @mcp-pointer/shared - Shared TypeScript types
├── src/
│ ├── Logger.ts
│ └── types.ts
│ ├── logger.ts
│ ├── types.ts
│ └── detail.ts # CSS/text detail level constants
└── package.json
```

Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ The extension lets you visually select DOM elements in the browser, and the MCP

- 🎯 **`Option+Click` Selection** - Simply hold `Option` (Alt on Windows) and click any element
- 📋 **Complete Element Data** - Text content, CSS classes, HTML attributes, positioning, and styling
- 💡 **Dynamic Context Control** - Request visible-only text, suppress text entirely, or dial CSS detail from none → full computed styles per MCP call
- ⚛️ **React Component Detection** - Component names and source files via Fiber (experimental)
- 🔗 **WebSocket Connection** - Real-time communication between browser and AI tools
- 🤖 **MCP Compatible** - Works with Claude Code and other MCP-enabled AI tools
Expand Down Expand Up @@ -102,7 +103,9 @@ After configuration, **restart your coding tool** to load the MCP connection.
Your AI tool will automatically start the MCP server when needed using the `npx -y @mcp-pointer/server@latest start` command.

**Available MCP Tool:**
- `get-pointed-element` - Get textual information about the currently pointed DOM element from the browser extension
- `get-pointed-element` – Returns textual information about the currently pointed DOM element. Optional arguments:
- `textDetail`: `0 | 1 | 2` (default `2`) controls how much text to include (`0 = none`, `1 = visible text only`, `2 = visible + hidden`).
- `cssLevel`: `0 | 1 | 2 | 3` (default `1`) controls styling detail, from no CSS (0) up to full computed styles (3).

## 🎯 How It Works

Expand Down
4 changes: 4 additions & 0 deletions packages/chrome-extension/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@

## 0.5.0

### Minor Changes

- Added dynamic context control (text detail & css levels)

### Patch Changes

- Updated dependencies [d91e764]
Expand Down
1 change: 1 addition & 0 deletions packages/chrome-extension/src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ chrome.runtime.onMessage
);

sendResponse({ success: true });
return true; // Keep message channel open for async response
}
});

Expand Down
211 changes: 1 addition & 210 deletions packages/chrome-extension/src/utils/element.ts
Original file line number Diff line number Diff line change
@@ -1,218 +1,9 @@
// Disable ESLint rule for underscore dangle usage in this file (React internals)
/* eslint-disable no-underscore-dangle */

import {
ComponentInfo, CSSProperties, ElementPosition, TargetedElement, RawPointedDOMElement,
} from '@mcp-pointer/shared/types';
import { RawPointedDOMElement } from '@mcp-pointer/shared/types';
import logger from './logger';

export interface ReactSourceInfo {
fileName: string;
lineNumber?: number;
columnNumber?: number;
}

/**
* Get source file information from a DOM element's React component
*/
export function getSourceFromElement(element: HTMLElement): ReactSourceInfo | null {
// Find React Fiber key
const fiberKey = Object.keys(element).find((key) => key.startsWith('__reactFiber$')
|| key.startsWith('__reactInternalInstance$'));

if (!fiberKey) return null;

const fiber = (element as any)[fiberKey];
if (!fiber) return null;

// Walk up fiber tree to find component fiber (skip DOM fibers)
let componentFiber = fiber;
while (componentFiber && typeof componentFiber.type === 'string') {
componentFiber = componentFiber.return;
}

if (!componentFiber) return null;

// Try multiple source locations (React version differences)
// React 18: _debugSource
if (componentFiber._debugSource) {
return {
fileName: componentFiber._debugSource.fileName,
lineNumber: componentFiber._debugSource.lineNumber,
columnNumber: componentFiber._debugSource.columnNumber,
};
}

// React 19: _debugInfo (often null)
if (componentFiber._debugInfo) {
return componentFiber._debugInfo;
}

// Babel plugin: __source on element type
if (componentFiber.elementType?.__source) {
return {
fileName: componentFiber.elementType.__source.fileName,
lineNumber: componentFiber.elementType.__source.lineNumber,
columnNumber: componentFiber.elementType.__source.columnNumber,
};
}

// Alternative: _owner chain
if (componentFiber._debugOwner?._debugSource) {
return {
fileName: componentFiber._debugOwner._debugSource.fileName,
lineNumber: componentFiber._debugOwner._debugSource.lineNumber,
columnNumber: componentFiber._debugOwner._debugSource.columnNumber,
};
}

// Check pendingProps for __source
if (componentFiber.pendingProps?.__source) {
return {
fileName: componentFiber.pendingProps.__source.fileName,
lineNumber: componentFiber.pendingProps.__source.lineNumber,
columnNumber: componentFiber.pendingProps.__source.columnNumber,
};
}

return null;
}

/**
* Extract React Fiber information from an element
*/
export function getReactFiberInfo(element: HTMLElement): ComponentInfo | undefined {
try {
// Use comprehensive source detection
const sourceInfo = getSourceFromElement(element);

// Also get component name
const fiberKey = Object.keys(element).find((key) => key.startsWith('__reactFiber$')
|| key.startsWith('__reactInternalInstance$'));

if (fiberKey) {
const fiber = (element as any)[fiberKey];
if (fiber) {
// Find component fiber
let componentFiber = fiber;
while (componentFiber && typeof componentFiber.type === 'string') {
componentFiber = componentFiber.return;
}

if (componentFiber && componentFiber.type && typeof componentFiber.type === 'function') {
const componentName = componentFiber.type.displayName
|| componentFiber.type.name
|| 'Unknown';

let sourceFile: string | undefined;
if (sourceInfo) {
const fileName = sourceInfo.fileName.split('/').pop() || sourceInfo.fileName;
sourceFile = sourceInfo.lineNumber
? `${fileName}:${sourceInfo.lineNumber}`
: fileName;
}

const result = {
name: componentName,
sourceFile,
framework: 'react' as const,
};

logger.debug('🧬 Found React Fiber info:', result);
return result;
}
}
}

return undefined;
} catch (error) {
logger.error('🚨 Error extracting Fiber info:', error);
return undefined;
}
}

/**
* Extract all attributes from an HTML element
*/
export function getElementAttributes(element: HTMLElement): Record<string, string> {
const attributes: Record<string, string> = {};
for (let i = 0; i < element.attributes.length; i += 1) {
const attr = element.attributes[i];
attributes[attr.name] = attr.value;
}
return attributes;
}

/**
* Generate a CSS selector for an element
*/
export function generateSelector(element: HTMLElement): string {
let selector = element.tagName.toLowerCase();
if (element.id) selector += `#${element.id}`;
if (element.className) {
const classNameStr = typeof element.className === 'string'
? element.className
: (element.className as any).baseVal || '';
const classes = classNameStr.split(' ').filter((c: string) => c.trim());
if (classes.length > 0) selector += `.${classes.join('.')}`;
}
return selector;
}

/**
* Get element position relative to the page
*/
export function getElementPosition(element: HTMLElement): ElementPosition {
const rect = element.getBoundingClientRect();
return {
x: rect.left + window.scrollX,
y: rect.top + window.scrollY,
width: rect.width,
height: rect.height,
};
}

/**
* Extract relevant CSS properties from an element
*/
export function getElementCSSProperties(element: HTMLElement): CSSProperties {
const computedStyle = window.getComputedStyle(element);
return {
display: computedStyle.display,
position: computedStyle.position,
fontSize: computedStyle.fontSize,
color: computedStyle.color,
backgroundColor: computedStyle.backgroundColor,
};
}

/**
* Extract CSS classes from an element as an array
*/
export function getElementClasses(element: HTMLElement): string[] {
if (!element.className) return [];
const classNameStr = typeof element.className === 'string'
? element.className
: (element.className as any).baseVal || '';
return classNameStr.split(' ').filter((c: string) => c.trim());
}

export function adaptTargetToElement(element: HTMLElement): TargetedElement {
return {
selector: generateSelector(element),
tagName: element.tagName,
id: element.id || undefined,
classes: getElementClasses(element),
innerText: element.innerText || element.textContent || '',
attributes: getElementAttributes(element),
position: getElementPosition(element),
cssProperties: getElementCSSProperties(element),
componentInfo: getReactFiberInfo(element),
timestamp: Date.now(),
url: window.location.href,
};
}

/**
* Extract raw React Fiber from an element (if present)
*/
Expand Down
15 changes: 0 additions & 15 deletions packages/chrome-extension/src/utils/types.ts

This file was deleted.

2 changes: 2 additions & 0 deletions packages/server/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

Server ready for browser extension updates.

- Added dynamic context control (text detail & css levels)

### Patch Changes

- 1c9cef4: Replace jsdom with node-html-parser for better bundling
Expand Down
Loading