-
Notifications
You must be signed in to change notification settings - Fork 21
Feature/#812 implement vscode extension and mcp #831
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Ivanruii
wants to merge
23
commits into
dev
Choose a base branch
from
feature/#812-implement-vscode-ext-and-mcp
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
a41b1a0
feat(mcp): implement wireframe tools and services
Ivanruii 22c7cc9
feat(vscode-extension): initial migration and integration
Ivanruii 4656961
feat(vscode-extension): implement script to copy MCP build output
Ivanruii 45f329f
feat(vscode-extension): refactor app URL handling to use environment …
Ivanruii d6d3f5c
feat(mcp): update type imports and enhance data URL parsing logic
Ivanruii 17c754d
feat: implement quickmock bridge protocol and integrate VSCode extens…
Ivanruii 274eed8
feat(vscode-extension): update VSCode URL and adjust type dependencies
Ivanruii 927a782
feat(vscode-extension): add environment variable for QM_APP_URL in la…
Ivanruii dc43f2b
feat: bundle Chromium, share app URL via ~/.quickmock, and reorganize…
Ivanruii d16ec66
style: standardize code formatting
Ivanruii 2282316
chore(changeset): initial 0.1.0 release
Ivanruii 471bd68
feat(vscode-extension): include QM_APP_ORIGIN in bridge server for if…
Ivanruii 189d822
feat(mcp): include QM_APP_ORIGIN in postMessage for iframe file loading
Ivanruii bb0a432
refactor(headless.renderer): remove unused browser launch arguments
Ivanruii 84c1797
feat: implement quickmock registry protocol and integrate with mcp an…
Ivanruii 683b438
feat(vscode-extension): add sandbox attribute to iframe for allow png…
Ivanruii 262935c
fix(mcp): correct log level from error to info in logInfo function
Ivanruii 9bcee08
fix(mcp): simplify server entry retrieval in cleanupStaleMcpRegistration
Ivanruii 0725a11
feat(bridge-protocol, registry-protocol): add message types and const…
nasdan 6af786a
feat(vscode-extension): update package configuration for module suppo…
nasdan 4325fa2
feat(mcp, vscode-extension): update dependencies and remove copy scri…
nasdan 31ef351
feat(config): update storage path and authentication settings for loc…
Ivanruii 07ebfb2
feat(mcp, vscode-extension): deliver MCP via npx instead of bundling …
Ivanruii File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| --- | ||
| 'quickmock': minor | ||
| '@lemoncode/quickmock-mcp': minor | ||
| --- | ||
|
|
||
| First public release of the QuickMock VS Code extension and its MCP server. | ||
|
|
||
| **`quickmock` (VS Code extension)** | ||
|
|
||
| - Custom editor for `.qm` files backed by the QuickMock web app, served inside a webview. | ||
|
|
||
| - `quickmock.appUrl` setting (default `https://quickmock.net/editor.html`) to point the editor and the MCP renderer at any QuickMock instance. Changes refresh open editors and respawn the MCP server. | ||
|
|
||
| - Automatic MCP server registration for VS Code / GitHub Copilot, Claude Code, Cursor, Windsurf and Claude Desktop, plus a dynamic `McpServerDefinitionProvider`. Existing entries are refreshed on activation so users always end up pointing at the right MCP invocation. | ||
|
|
||
| - The MCP server is no longer bundled inside the `.vsix`. In production the extension spawns it on demand via `npx -y @lemoncode/quickmock-mcp`, so users always run the latest published MCP without waiting for an extension release. In development it resolves the local workspace build. | ||
|
|
||
| **`@lemoncode/quickmock-mcp` (MCP server)** | ||
|
|
||
| - MCP tools to explore and render wireframes: `list_wireframes`, `get_wireframe_json`, `get_wireframe_pages`, `get_wireframe_assets` and `capture_wireframe`. | ||
|
|
||
| - Headless screenshot pipeline via `puppeteer-core` against the QuickMock app, using a postMessage bridge. | ||
|
|
||
| - On-demand Chromium download via `@puppeteer/browsers`, cached under `~/.quickmock/browsers`, so headless rendering works without relying on the user's local browser install. | ||
|
|
||
| - Reads the target app URL from `~/.quickmock/app-url` (written by the extension) with a production fallback, so the MCP works out of the box regardless of how it is spawned. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import type { ContentBbox } from '@lemoncode/quickmock-bridge-protocol'; | ||
| import type { useCanvasContext } from '#core/providers'; | ||
|
|
||
| const CONTENT_PADDING = 16; | ||
|
|
||
| export function computeContentBbox( | ||
| shapes: ReturnType<typeof useCanvasContext>['shapes'], | ||
| stageRef: ReturnType<typeof useCanvasContext>['stageRef'] | ||
| ): ContentBbox | undefined { | ||
| const stage = stageRef.current; | ||
| if (!stage || shapes.length === 0) return undefined; | ||
|
|
||
| const scale = stage.scaleX(); | ||
| const stageX = stage.x(); | ||
| const stageY = stage.y(); | ||
| const container = stage.container().getBoundingClientRect(); | ||
|
|
||
| const minX = Math.min(...shapes.map(s => s.x)); | ||
| const minY = Math.min(...shapes.map(s => s.y)); | ||
| const maxX = Math.max(...shapes.map(s => s.x + s.width)); | ||
| const maxY = Math.max(...shapes.map(s => s.y + s.height)); | ||
|
|
||
| return { | ||
| x: Math.max(0, container.left + stageX + minX * scale - CONTENT_PADDING), | ||
| y: Math.max(0, container.top + stageY + minY * scale - CONTENT_PADDING), | ||
| width: (maxX - minX) * scale + CONTENT_PADDING * 2, | ||
| height: (maxY - minY) * scale + CONTENT_PADDING * 2, | ||
| }; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| export const isVSCodeEnv = (): boolean => { | ||
| return new URLSearchParams(window.location.search).get('env') === 'vscode'; | ||
| }; | ||
|
|
||
| export const isHeadlessEnv = (): boolean => { | ||
| return new URLSearchParams(window.location.search).get('headless') === '1'; | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| import type { | ||
| AppMessage, | ||
| HostMessage, | ||
| PayloadOf, | ||
| } from '@lemoncode/quickmock-bridge-protocol'; | ||
| import { isVSCodeEnv } from './env.utils'; | ||
|
|
||
| type HandlerFor<T extends HostMessage['type']> = ( | ||
| payload: PayloadOf<HostMessage, T> | ||
| ) => void; | ||
|
|
||
| type AnyHandler = (payload: unknown) => void; | ||
|
|
||
| const handlers = new Map<string, Set<AnyHandler>>(); | ||
|
|
||
| export const sendToExtension = (msg: AppMessage): void => { | ||
| if (!isVSCodeEnv()) return; | ||
| window.parent.postMessage(msg, '*'); | ||
| }; | ||
|
|
||
| export const onMessage = <T extends HostMessage['type']>( | ||
| type: T, | ||
| handler: HandlerFor<T> | ||
| ): (() => void) => { | ||
| if (!isVSCodeEnv()) return () => {}; | ||
|
|
||
| const existing = handlers.get(type) ?? new Set<AnyHandler>(); | ||
| existing.add(handler as AnyHandler); | ||
| handlers.set(type, existing); | ||
|
|
||
| return () => { | ||
| const set = handlers.get(type); | ||
| if (!set) return; | ||
| set.delete(handler as AnyHandler); | ||
| if (set.size === 0) handlers.delete(type); | ||
| }; | ||
| }; | ||
|
|
||
| if (isVSCodeEnv()) { | ||
| window.addEventListener('message', (event: MessageEvent) => { | ||
| if (event.source !== window.parent) return; | ||
|
|
||
| const msg = event.data as Partial<HostMessage> | undefined; | ||
| if (!msg?.type) return; | ||
|
|
||
| const set = handlers.get(msg.type); | ||
| if (!set) return; | ||
|
|
||
| const payload = (msg as { payload?: unknown }).payload; | ||
| for (const handler of set) handler(payload); | ||
| }); | ||
| } |
34 changes: 34 additions & 0 deletions
34
apps/web/src/core/vscode/use-headless-render-complete.hook.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { computeContentBbox } from '#common/utils/compute-content-bbox.utils.ts'; | ||
| import { isHeadlessEnv } from '#common/utils/env.utils.ts'; | ||
| import { sendToExtension } from '#common/utils/vscode-bridge.utils.ts'; | ||
| import { useCanvasContext } from '#core/providers'; | ||
| import { APP_MESSAGE_TYPE } from '@lemoncode/quickmock-bridge-protocol'; | ||
| import { useEffect } from 'react'; | ||
|
|
||
| export function useHeadlessRenderComplete(hasReceivedFileRef: { | ||
| current: boolean; | ||
| }): void { | ||
| const { howManyLoadedDocuments, shapes, stageRef } = useCanvasContext(); | ||
|
|
||
| useEffect(() => { | ||
| if (!isHeadlessEnv() || !hasReceivedFileRef.current) return; | ||
|
|
||
| let innerRafId = 0; | ||
| // Double rAF: the first frame runs after React commits; the second waits | ||
| // for Konva to paint the updated canvas, so Puppeteer's screenshot reflects it. | ||
| // There was a previous issue when the canvas was blank because the screenshot ran before Konva painted. | ||
| const outerRafId = requestAnimationFrame(() => { | ||
| innerRafId = requestAnimationFrame(() => { | ||
| sendToExtension({ | ||
| type: APP_MESSAGE_TYPE.RENDER_COMPLETE, | ||
| payload: computeContentBbox(shapes, stageRef), | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| return () => { | ||
| cancelAnimationFrame(outerRafId); | ||
| cancelAnimationFrame(innerRafId); | ||
| }; | ||
| }, [howManyLoadedDocuments]); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| import { isHeadlessEnv, isVSCodeEnv } from '#common/utils/env.utils.ts'; | ||
| import { sendToExtension } from '#common/utils/vscode-bridge.utils.ts'; | ||
| import { useCanvasContext } from '#core/providers'; | ||
| import { APP_MESSAGE_TYPE } from '@lemoncode/quickmock-bridge-protocol'; | ||
| import { useEffect, useRef } from 'react'; | ||
| import { serializeDocument } from './vscode-sync.utils'; | ||
|
|
||
| const AUTO_SAVE_DEBOUNCE_MS = 500; | ||
|
|
||
| export function useVSCodeAutoSave(hasReceivedFileRef: { | ||
| current: boolean; | ||
| }): void { | ||
| const { fullDocument, howManyLoadedDocuments } = useCanvasContext(); | ||
|
|
||
| const prevLoadCountRef = useRef(howManyLoadedDocuments); | ||
| const lastSavedContentRef = useRef(''); | ||
| const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); | ||
|
|
||
| useEffect(() => { | ||
| if (!isVSCodeEnv() || isHeadlessEnv() || !hasReceivedFileRef.current) | ||
| return; | ||
|
|
||
| if (prevLoadCountRef.current !== howManyLoadedDocuments) { | ||
| prevLoadCountRef.current = howManyLoadedDocuments; | ||
| lastSavedContentRef.current = serializeDocument(fullDocument); | ||
| return; | ||
| } | ||
|
|
||
| const content = serializeDocument(fullDocument); | ||
|
|
||
| if (content === lastSavedContentRef.current) return; | ||
|
|
||
| debounceTimerRef.current = setTimeout(() => { | ||
| sendToExtension({ | ||
| type: APP_MESSAGE_TYPE.SAVE, | ||
| payload: { content }, | ||
| }); | ||
| lastSavedContentRef.current = content; | ||
| debounceTimerRef.current = null; | ||
| }, AUTO_SAVE_DEBOUNCE_MS); | ||
|
|
||
| return () => { | ||
| if (debounceTimerRef.current !== null) { | ||
| clearTimeout(debounceTimerRef.current); | ||
| debounceTimerRef.current = null; | ||
| } | ||
| }; | ||
| }, [fullDocument, howManyLoadedDocuments]); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| import { isHeadlessEnv, isVSCodeEnv } from '#common/utils/env.utils.ts'; | ||
| import { | ||
| onMessage, | ||
| sendToExtension, | ||
| } from '#common/utils/vscode-bridge.utils.ts'; | ||
| import { QuickMockFileContract } from '#core/local-disk/local-disk.model'; | ||
| import { useCanvasContext } from '#core/providers'; | ||
| import { | ||
| APP_MESSAGE_TYPE, | ||
| HOST_MESSAGE_TYPE, | ||
| type LoadFilePayload, | ||
| } from '@lemoncode/quickmock-bridge-protocol'; | ||
| import { useEffect, useRef } from 'react'; | ||
| import { deserializeDocument } from './vscode-sync.utils'; | ||
|
|
||
| export function useVSCodeFileLoad(): { current: boolean } { | ||
| const { loadDocument, setFileName } = useCanvasContext(); | ||
|
|
||
| const loadDocumentRef = useRef(loadDocument); | ||
| const setFileNameRef = useRef(setFileName); | ||
| useEffect(() => { | ||
| loadDocumentRef.current = loadDocument; | ||
| setFileNameRef.current = setFileName; | ||
| }); | ||
|
|
||
| const hasReceivedFileRef = useRef(false); | ||
|
|
||
| useEffect(() => { | ||
| if (!isVSCodeEnv()) return; | ||
|
|
||
| const unsubscribe = onMessage( | ||
| HOST_MESSAGE_TYPE.LOAD_FILE, | ||
| (payload: LoadFilePayload) => { | ||
| hasReceivedFileRef.current = true; | ||
| setFileNameRef.current(payload.fileName); | ||
| loadDocumentRef.current( | ||
| deserializeDocument(payload.data as QuickMockFileContract) | ||
| ); | ||
| } | ||
| ); | ||
|
|
||
| sendToExtension({ | ||
| type: isHeadlessEnv() | ||
| ? APP_MESSAGE_TYPE.READY | ||
| : APP_MESSAGE_TYPE.WEBVIEW_READY, | ||
| }); | ||
|
|
||
| return unsubscribe; | ||
| }, []); | ||
|
|
||
| return hasReceivedFileRef; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import { useHeadlessRenderComplete } from './use-headless-render-complete.hook'; | ||
| import { useVSCodeAutoSave } from './use-vscode-auto-save.hook'; | ||
| import { useVSCodeFileLoad } from './use-vscode-file-load.hook'; | ||
|
|
||
| /** | ||
| * Wires the full VS Code webview bridge. Each inner hook no-ops when not | ||
| * running inside a webview, so this can be called unconditionally. | ||
| */ | ||
| export function useVSCodeSync(): void { | ||
| const hasReceivedFileRef = useVSCodeFileLoad(); | ||
| useVSCodeAutoSave(hasReceivedFileRef); | ||
| useHeadlessRenderComplete(hasReceivedFileRef); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import { QuickMockFileContract } from '#core/local-disk/local-disk.model'; | ||
| import { | ||
| mapFromQuickMockFileDocumentToApplicationDocument, | ||
| mapFromQuickMockFileDocumentToApplicationDocumentV0_1, | ||
| mapFromShapesArrayToQuickMockFileDocument, | ||
| } from '#core/local-disk/shapes-to-document.mapper'; | ||
| import { DocumentModel } from '#core/providers/canvas/canvas.model'; | ||
|
|
||
| export function deserializeDocument(data: QuickMockFileContract) { | ||
| return data.version === '0.1' | ||
| ? mapFromQuickMockFileDocumentToApplicationDocumentV0_1(data) | ||
| : mapFromQuickMockFileDocumentToApplicationDocument(data); | ||
| } | ||
|
|
||
| export function serializeDocument(document: DocumentModel): string { | ||
| return JSON.stringify(mapFromShapesArrayToQuickMockFileDocument(document)); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,19 @@ | ||
| storage: ./storage | ||
| storage: /verdaccio/storage | ||
|
|
||
| auth: | ||
| htpasswd: | ||
| file: /verdaccio/storage/htpasswd | ||
| max_users: 1000 | ||
|
|
||
| uplinks: | ||
| npmjs: | ||
| url: https://registry.npmjs.org/ | ||
|
|
||
| packages: | ||
| '@lemoncode/*': | ||
| access: $anonymous | ||
| publish: $anonymous | ||
| access: $all | ||
| publish: $authenticated | ||
| '**': | ||
| access: $anonymous | ||
| publish: $anonymous | ||
| access: $all | ||
| publish: $authenticated | ||
| proxy: npmjs |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This workspace-level VS Code setting forces
quickmock.appUrlto a localhost dev server for everyone who opens the repo. That’s likely to break the extension/editor unless the dev server is running. Consider removing this from source control or moving it to a documented example (e.g..vscode/settings.example.json).