Skip to content

Commit ba84de6

Browse files
Merge pull request #13 from ashtonchew/feature/dynamic-context-control
feat: add dynamic context control for get-pointed-element tool
2 parents 1f1087f + 3c48015 commit ba84de6

23 files changed

+576
-568
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
"@mcp-pointer/server": minor
3+
"@mcp-pointer/shared": minor
4+
---
5+
6+
**Architecture Cleanup & Improvements**
7+
8+
- **Server**: Store full CSS properties in `cssProperties` instead of filtering to 5 properties
9+
- **Server**: Remove LEGACY_ELEMENT_SELECTED support - only DOM_ELEMENT_POINTED is now supported
10+
- **Server**: Delete unused files (`mcp-handler.ts`, `websocket-server.ts`)
11+
- **Server**: Simplify types - remove StateDataV1 and LegacySharedState
12+
- **Server**: Dynamic CSS filtering now happens on-the-fly during MCP tool calls based on cssLevel parameter
13+
14+
This enables full CSS details to be accessible without re-pointing to elements, with filtering applied server-side based on tool parameters.

CONTRIBUTING.md

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,16 @@ packages/
6262
├── server/ # @mcp-pointer/server - MCP Server (TypeScript)
6363
│ ├── src/
6464
│ │ ├── start.ts # Main server entry point
65-
│ │ ├── cli.ts # Command line interface
66-
│ │ ├── websocket-server.ts
67-
│ │ └── mcp-handler.ts
65+
│ │ ├── cli.ts # Command line interface
66+
│ │ ├── message-handler.ts # Message routing & state building
67+
│ │ ├── services/
68+
│ │ │ ├── websocket-service.ts # WebSocket with leader election
69+
│ │ │ ├── mcp-service.ts # MCP protocol handler
70+
│ │ │ ├── element-processor.ts # Raw→Processed conversion
71+
│ │ │ └── shared-state-service.ts # State persistence
72+
│ │ └── utils/
73+
│ │ ├── dom-extractor.ts # HTML parsing utilities
74+
│ │ └── element-detail.ts # Dynamic CSS/text filtering
6875
│ ├── dist/
6976
│ │ └── cli.cjs # Bundled standalone CLI
7077
│ └── package.json
@@ -73,15 +80,17 @@ packages/
7380
│ ├── src/
7481
│ │ ├── background.ts # Service worker
7582
│ │ ├── content.ts # Element selection
76-
│ │ └── element-sender-service.ts
83+
│ │ └── services/
84+
│ │ └── element-sender-service.ts # WebSocket client
7785
│ ├── dev/ # Development build (with logging)
7886
│ ├── dist/ # Production build (minified)
7987
│ └── manifest.json
8088
8189
└── shared/ # @mcp-pointer/shared - Shared TypeScript types
8290
├── src/
83-
│ ├── Logger.ts
84-
│ └── types.ts
91+
│ ├── logger.ts
92+
│ ├── types.ts
93+
│ └── detail.ts # CSS/text detail level constants
8594
└── package.json
8695
```
8796

@@ -119,9 +128,16 @@ packages/
119128
├── server/ # @mcp-pointer/server - MCP Server (TypeScript)
120129
│ ├── src/
121130
│ │ ├── start.ts # Main server entry point
122-
│ │ ├── cli.ts # Command line interface
123-
│ │ ├── websocket-server.ts
124-
│ │ └── mcp-handler.ts
131+
│ │ ├── cli.ts # Command line interface
132+
│ │ ├── message-handler.ts # Message routing & state building
133+
│ │ ├── services/
134+
│ │ │ ├── websocket-service.ts # WebSocket with leader election
135+
│ │ │ ├── mcp-service.ts # MCP protocol handler
136+
│ │ │ ├── element-processor.ts # Raw→Processed conversion
137+
│ │ │ └── shared-state-service.ts # State persistence
138+
│ │ └── utils/
139+
│ │ ├── dom-extractor.ts # HTML parsing utilities
140+
│ │ └── element-detail.ts # Dynamic CSS/text filtering
125141
│ ├── dist/
126142
│ │ └── cli.cjs # Bundled standalone CLI
127143
│ └── package.json
@@ -130,15 +146,17 @@ packages/
130146
│ ├── src/
131147
│ │ ├── background.ts # Service worker
132148
│ │ ├── content.ts # Element selection
133-
│ │ └── element-sender-service.ts
149+
│ │ └── services/
150+
│ │ └── element-sender-service.ts # WebSocket client
134151
│ ├── dev/ # Development build (with logging)
135152
│ ├── dist/ # Production build (minified)
136153
│ └── manifest.json
137154
138155
└── shared/ # @mcp-pointer/shared - Shared TypeScript types
139156
├── src/
140-
│ ├── Logger.ts
141-
│ └── types.ts
157+
│ ├── logger.ts
158+
│ ├── types.ts
159+
│ └── detail.ts # CSS/text detail level constants
142160
└── package.json
143161
```
144162

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ The extension lets you visually select DOM elements in the browser, and the MCP
2121

2222
- 🎯 **`Option+Click` Selection** - Simply hold `Option` (Alt on Windows) and click any element
2323
- 📋 **Complete Element Data** - Text content, CSS classes, HTML attributes, positioning, and styling
24+
- 💡 **Dynamic Context Control** - Request visible-only text, suppress text entirely, or dial CSS detail from none → full computed styles per MCP call
2425
- ⚛️ **React Component Detection** - Component names and source files via Fiber (experimental)
2526
- 🔗 **WebSocket Connection** - Real-time communication between browser and AI tools
2627
- 🤖 **MCP Compatible** - Works with Claude Code and other MCP-enabled AI tools
@@ -102,7 +103,9 @@ After configuration, **restart your coding tool** to load the MCP connection.
102103
Your AI tool will automatically start the MCP server when needed using the `npx -y @mcp-pointer/server@latest start` command.
103104
104105
**Available MCP Tool:**
105-
- `get-pointed-element` - Get textual information about the currently pointed DOM element from the browser extension
106+
- `get-pointed-element` – Returns textual information about the currently pointed DOM element. Optional arguments:
107+
- `textDetail`: `0 | 1 | 2` (default `2`) controls how much text to include (`0 = none`, `1 = visible text only`, `2 = visible + hidden`).
108+
- `cssLevel`: `0 | 1 | 2 | 3` (default `1`) controls styling detail, from no CSS (0) up to full computed styles (3).
106109
107110
## 🎯 How It Works
108111

packages/chrome-extension/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020

2121
## 0.5.0
2222

23+
### Minor Changes
24+
25+
- Added dynamic context control (text detail & css levels)
26+
2327
### Patch Changes
2428

2529
- Updated dependencies [d91e764]

packages/chrome-extension/src/background.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ chrome.runtime.onMessage
6868

6969
sendResponse({ success: true });
7070
}
71+
72+
return true; // Keep message channel open for async response
7173
});
7274

7375
// Handle extension install/update

packages/chrome-extension/src/utils/element.ts

Lines changed: 1 addition & 210 deletions
Original file line numberDiff line numberDiff line change
@@ -1,218 +1,9 @@
11
// Disable ESLint rule for underscore dangle usage in this file (React internals)
22
/* eslint-disable no-underscore-dangle */
33

4-
import {
5-
ComponentInfo, CSSProperties, ElementPosition, TargetedElement, RawPointedDOMElement,
6-
} from '@mcp-pointer/shared/types';
4+
import { RawPointedDOMElement } from '@mcp-pointer/shared/types';
75
import logger from './logger';
86

9-
export interface ReactSourceInfo {
10-
fileName: string;
11-
lineNumber?: number;
12-
columnNumber?: number;
13-
}
14-
15-
/**
16-
* Get source file information from a DOM element's React component
17-
*/
18-
export function getSourceFromElement(element: HTMLElement): ReactSourceInfo | null {
19-
// Find React Fiber key
20-
const fiberKey = Object.keys(element).find((key) => key.startsWith('__reactFiber$')
21-
|| key.startsWith('__reactInternalInstance$'));
22-
23-
if (!fiberKey) return null;
24-
25-
const fiber = (element as any)[fiberKey];
26-
if (!fiber) return null;
27-
28-
// Walk up fiber tree to find component fiber (skip DOM fibers)
29-
let componentFiber = fiber;
30-
while (componentFiber && typeof componentFiber.type === 'string') {
31-
componentFiber = componentFiber.return;
32-
}
33-
34-
if (!componentFiber) return null;
35-
36-
// Try multiple source locations (React version differences)
37-
// React 18: _debugSource
38-
if (componentFiber._debugSource) {
39-
return {
40-
fileName: componentFiber._debugSource.fileName,
41-
lineNumber: componentFiber._debugSource.lineNumber,
42-
columnNumber: componentFiber._debugSource.columnNumber,
43-
};
44-
}
45-
46-
// React 19: _debugInfo (often null)
47-
if (componentFiber._debugInfo) {
48-
return componentFiber._debugInfo;
49-
}
50-
51-
// Babel plugin: __source on element type
52-
if (componentFiber.elementType?.__source) {
53-
return {
54-
fileName: componentFiber.elementType.__source.fileName,
55-
lineNumber: componentFiber.elementType.__source.lineNumber,
56-
columnNumber: componentFiber.elementType.__source.columnNumber,
57-
};
58-
}
59-
60-
// Alternative: _owner chain
61-
if (componentFiber._debugOwner?._debugSource) {
62-
return {
63-
fileName: componentFiber._debugOwner._debugSource.fileName,
64-
lineNumber: componentFiber._debugOwner._debugSource.lineNumber,
65-
columnNumber: componentFiber._debugOwner._debugSource.columnNumber,
66-
};
67-
}
68-
69-
// Check pendingProps for __source
70-
if (componentFiber.pendingProps?.__source) {
71-
return {
72-
fileName: componentFiber.pendingProps.__source.fileName,
73-
lineNumber: componentFiber.pendingProps.__source.lineNumber,
74-
columnNumber: componentFiber.pendingProps.__source.columnNumber,
75-
};
76-
}
77-
78-
return null;
79-
}
80-
81-
/**
82-
* Extract React Fiber information from an element
83-
*/
84-
export function getReactFiberInfo(element: HTMLElement): ComponentInfo | undefined {
85-
try {
86-
// Use comprehensive source detection
87-
const sourceInfo = getSourceFromElement(element);
88-
89-
// Also get component name
90-
const fiberKey = Object.keys(element).find((key) => key.startsWith('__reactFiber$')
91-
|| key.startsWith('__reactInternalInstance$'));
92-
93-
if (fiberKey) {
94-
const fiber = (element as any)[fiberKey];
95-
if (fiber) {
96-
// Find component fiber
97-
let componentFiber = fiber;
98-
while (componentFiber && typeof componentFiber.type === 'string') {
99-
componentFiber = componentFiber.return;
100-
}
101-
102-
if (componentFiber && componentFiber.type && typeof componentFiber.type === 'function') {
103-
const componentName = componentFiber.type.displayName
104-
|| componentFiber.type.name
105-
|| 'Unknown';
106-
107-
let sourceFile: string | undefined;
108-
if (sourceInfo) {
109-
const fileName = sourceInfo.fileName.split('/').pop() || sourceInfo.fileName;
110-
sourceFile = sourceInfo.lineNumber
111-
? `${fileName}:${sourceInfo.lineNumber}`
112-
: fileName;
113-
}
114-
115-
const result = {
116-
name: componentName,
117-
sourceFile,
118-
framework: 'react' as const,
119-
};
120-
121-
logger.debug('🧬 Found React Fiber info:', result);
122-
return result;
123-
}
124-
}
125-
}
126-
127-
return undefined;
128-
} catch (error) {
129-
logger.error('🚨 Error extracting Fiber info:', error);
130-
return undefined;
131-
}
132-
}
133-
134-
/**
135-
* Extract all attributes from an HTML element
136-
*/
137-
export function getElementAttributes(element: HTMLElement): Record<string, string> {
138-
const attributes: Record<string, string> = {};
139-
for (let i = 0; i < element.attributes.length; i += 1) {
140-
const attr = element.attributes[i];
141-
attributes[attr.name] = attr.value;
142-
}
143-
return attributes;
144-
}
145-
146-
/**
147-
* Generate a CSS selector for an element
148-
*/
149-
export function generateSelector(element: HTMLElement): string {
150-
let selector = element.tagName.toLowerCase();
151-
if (element.id) selector += `#${element.id}`;
152-
if (element.className) {
153-
const classNameStr = typeof element.className === 'string'
154-
? element.className
155-
: (element.className as any).baseVal || '';
156-
const classes = classNameStr.split(' ').filter((c: string) => c.trim());
157-
if (classes.length > 0) selector += `.${classes.join('.')}`;
158-
}
159-
return selector;
160-
}
161-
162-
/**
163-
* Get element position relative to the page
164-
*/
165-
export function getElementPosition(element: HTMLElement): ElementPosition {
166-
const rect = element.getBoundingClientRect();
167-
return {
168-
x: rect.left + window.scrollX,
169-
y: rect.top + window.scrollY,
170-
width: rect.width,
171-
height: rect.height,
172-
};
173-
}
174-
175-
/**
176-
* Extract relevant CSS properties from an element
177-
*/
178-
export function getElementCSSProperties(element: HTMLElement): CSSProperties {
179-
const computedStyle = window.getComputedStyle(element);
180-
return {
181-
display: computedStyle.display,
182-
position: computedStyle.position,
183-
fontSize: computedStyle.fontSize,
184-
color: computedStyle.color,
185-
backgroundColor: computedStyle.backgroundColor,
186-
};
187-
}
188-
189-
/**
190-
* Extract CSS classes from an element as an array
191-
*/
192-
export function getElementClasses(element: HTMLElement): string[] {
193-
if (!element.className) return [];
194-
const classNameStr = typeof element.className === 'string'
195-
? element.className
196-
: (element.className as any).baseVal || '';
197-
return classNameStr.split(' ').filter((c: string) => c.trim());
198-
}
199-
200-
export function adaptTargetToElement(element: HTMLElement): TargetedElement {
201-
return {
202-
selector: generateSelector(element),
203-
tagName: element.tagName,
204-
id: element.id || undefined,
205-
classes: getElementClasses(element),
206-
innerText: element.innerText || element.textContent || '',
207-
attributes: getElementAttributes(element),
208-
position: getElementPosition(element),
209-
cssProperties: getElementCSSProperties(element),
210-
componentInfo: getReactFiberInfo(element),
211-
timestamp: Date.now(),
212-
url: window.location.href,
213-
};
214-
}
215-
2167
/**
2178
* Extract raw React Fiber from an element (if present)
2189
*/

packages/chrome-extension/src/utils/types.ts

Lines changed: 0 additions & 15 deletions
This file was deleted.

packages/server/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
Server ready for browser extension updates.
2222

23+
- Added dynamic context control (text detail & css levels)
24+
2325
### Patch Changes
2426

2527
- 1c9cef4: Replace jsdom with node-html-parser for better bundling

0 commit comments

Comments
 (0)