Editor Extensions (VS Code + JetBrains)

June 24, 2026 · View on GitHub

Two IDE extensions bring MockServer controls into VS Code and JetBrains IDEs. Both publish the server's own JSON Schema for *.mockserver.json validation and talk to a running MockServer over REST. There is no Language Server: both editors' built-in JSON engines handle validation, completion, and hover from the schema alone. The design spec is at docs/plans/editor-extensions-value-roadmap.md.

Key facts:

  • VS Code: TypeScript, published to VS Code Marketplace and Open VSX (publisher id mockserver).
  • JetBrains: Kotlin, targets platformType=IC (IntelliJ Community) since build 243 (2024.3).
  • Shared artifact: a generated mockserver-expectation.schema.json (draft-07, self-contained), one copy bundled in each extension.
  • Docker image tag defaults to the extension's own version at runtime — never a hardcoded constant.
  • Marketplace branding: VS Code ships media/icon.png (128×128, generated from media/icon.svg); JetBrains ships META-INF/pluginIcon.svg (+ pluginIcon_dark.svg). Both are the teal "M" mark on a dark slate (#1e1e1e) rounded square.

Architecture Overview

flowchart TB
    core["mockserver-core\nmodel/schema/*.json\n(45 source schema files)"]
    gen["scripts/generate-editor-expectation-schema.mjs\n(schema assembly — run locally)"]
    schema["mockserver-expectation.schema.json\n(generated, committed artifact)"]
    vsc_schema["mockserver-vscode/schemas/\nmockserver-expectation.schema.json"]
    jb_schema["mockserver-jetbrains/src/main/resources/\nschemas/mockserver-expectation.schema.json"]
    vsc["VS Code extension\nTypeScript\ncontributes.jsonValidation\nmockServerClient.ts + codeLens.ts"]
    jb["JetBrains plugin\nKotlin\nJsonSchemaProviderFactory\nMockServerRestClient + Actions"]
    ms["Running MockServer\n(Docker or any)\nhttp://localhost:1080"]

    core --> gen --> schema
    schema --> vsc_schema
    schema --> jb_schema
    vsc_schema --> vsc
    jb_schema --> jb
    vsc <-->|"REST (PUT/GET)\n+ webview iframe"| ms
    jb <-->|"REST (PUT/GET)\n+ JCEF JBCefBrowser"| ms

The schema is a committed artifact regenerated by running node scripts/generate-editor-expectation-schema.mjs whenever mockserver-core schemas change. It is not auto-regenerated in CI.

Feature Inventory

FeatureVS Code command(s)JetBrains action id(s)REST endpoint
Schema validation/completion/hovercontributes.jsonValidation (automatic)MockServerSchemaProviderFactory (automatic)
Load expectationsmockserver.loadExpectationsMockServer.LoadExpectationsPUT /mockserver/expectation
Diff against livemockserver.diffAgainstLivePUT /mockserver/retrieve?type=active_expectations
Record-to-code (recorded expectations)mockserver.saveRecordedMockServer.SaveRecordedExpectationsPUT /mockserver/retrieve?type=recorded_expectations&format=json|java
Generate expectations from OpenAPImockserver.generateFromOpenApiMockServer.GenerateFromOpenApiPUT /mockserver/openapi
Send test requestmockserver.sendRequestMockServer.SendRequest<spec.method> <spec.path> on the server
View request logmockserver.viewRequestLogPUT /mockserver/retrieve?type=requests&format=json
Show drift reportmockserver.showDriftMockServer.ShowDriftReportGET /mockserver/drift
Drift inline diagnosticsmockserver.showDriftDiagnostics— (VS Code only)GET /mockserver/drift
Find requests by tracemockserver.findByTraceMockServer.FindByTracePUT /mockserver/retrieve?type=requests&format=json (client-side filter)
Reset servermockserver.resetMockServer.ResetPUT /mockserver/reset
Upload WASM modulemockserver.uploadWasmMockServer.UploadWasmPUT /mockserver/wasm/modules?name=<name>
List WASM modulesmockserver.listWasmMockServer.ListWasmGET /mockserver/wasm/modules
Open dashboard (browser)mockserver.openDashboardMockServer.OpenDashboardbrowser → /mockserver/dashboard
Open dashboard (docked in-IDE)mockserver.openDashboardInEditor (reveals the docked Panel webview)MockServer.OpenDashboardInIdewebview/JCEF → /mockserver/dashboard
Start Docker containermockserver.startMockServer.StartDockerdocker run subprocess
Stop Docker containermockserver.stop— (VS Code only)docker stop subprocess
Settings (port, image, container name)mockserver.* workspace configSettings | Tools | MockServer

Notes:

  • "Diff against live" and "Stop Docker" are VS Code-only features at current implementation.
  • "Save Recorded Expectations" offers a JSON / Java DSL format choice in both extensions.
  • Drift inline diagnostics map DriftRecord.expectationId to a line in the open .mockserver.json file; this requires an open editor with the expectation file and is VS Code-specific.
  • "Find by trace" retrieves the full request log then filters client-side by W3C traceparent header.

VS Code Extension

Location: mockserver-vscode/

Language/runtime: TypeScript, compiled to out/extension.js.

Entry point: src/extension.tsactivate() registers all commands, CodeLens providers, the status-bar item, and the mockserver-live virtual document scheme.

Activation: activationEvents is ["onStartupFinished"] — this is required, not optional. The status-bar item and the CodeLens providers are created in activate(), so without a startup activation event they would not appear until the user first invoked a command from the palette (commands themselves get implicit onCommand: activation). onStartupFinished defers activation until the window is interactive, so the passive surfaces light up with negligible startup cost.

Key components

FileRole
src/extension.tsActivation, command handlers, Docker subprocess calls, status bar, and view registration (Actions tree + docked dashboard reveal via mockserver.dashboard.focus)
src/mockServerClient.tsREST client; free of the vscode API so it is unit-testable. Takes an injectable FetchLike so tests run without a live server
src/codeLens.tsExpectationCodeLensProvider (top of *.mockserver.json(c)) + ScratchRequestCodeLensProvider (top of *.mockserver-request.json)
src/actionsView.tsMockServerActionsProvider (TreeDataProvider) backing the Activity Bar Actions view — a status leaf (MockServer · localhost:<port>) plus grouped action leaves (Server / Author / Inspect / WASM), each running an existing command. Takes an injected getPort so refresh re-reads the port
src/dashboardView.tsMockServerDashboardViewProvider (WebviewViewProvider) backing the docked Panel Dashboard view; reuses client.buildDashboardWebviewHtml(...)
schemas/mockserver-expectation.schema.jsonGenerated schema, wired via contributes.jsonValidation to *.mockserver.json and *.mockserver.jsonc
snippets/expectation.jsonCode snippets, contributed for both json and jsonc languages
media/icon.png / media/icon.svgMarketplace icon (PNG, wired via package.json icon) and its SVG source
media/activitybar.svgMonochrome (currentColor) M glyph for the Activity Bar / Panel view containers — the themed mockserver.svg can't recolor in those slots

Discoverability surfaces (IntelliJ-style)

Beyond the command palette, the extension exposes its commands through:

  • A MockServer Activity Bar view container (viewsContainers.activitybar id mockserver, icon media/activitybar.svg) holding the mockserver.actions TreeView — a status leaf (MockServer · localhost:<port>, click reveals the docked dashboard) and collapsible groups Server / Author / Inspect / WASM whose leaves each run an existing command. This is the idiomatic VS Code analogue of the JetBrains tool-window buttons. view/title icon buttons (Start, Open Dashboard, Reset, Refresh) sit on the view's title bar; mockserver.refreshView fires the tree's change event to re-read the port.
  • A status-bar item ($(server) MockServer :<port>) whose click runs mockserver.statusBarMenu — an internal command (registered but deliberately not in contributes.commands) showing a quick pick of Open Dashboard / Start / Stop / View Request Log.
  • contributes.menus: file-scoped commands appear in the editor title bar and right-click menu gated by when clauses on resourceFilename (*.mockserver.json(c) → Load / Diff / drift; *.mockserver-request.json → Send Test Request), and the same when clauses hide those file-scoped commands from the palette when no matching editor is active.

Docked dashboard

The live dashboard is a WebviewView docked in the bottom Panel (viewsContainers.panel id mockserverDashboard → view mockserver.dashboard, "type": "webview"), so it gets full editor width and is not mixed in with editor tabs — the analogue of the JetBrains JCEF dashboard tool window. MockServerDashboardViewProvider.resolveWebviewView sets enableScripts and the html from client.buildDashboardWebviewHtml(...) (a full-bleed <iframe> at http://localhost:<port>/mockserver/dashboard; the CSP allows frame-src http://localhost:*/127.0.0.1), registered with retainContextWhenHidden: true. mockserver.openDashboardInEditor (title "MockServer: Open Dashboard") now reveals this docked view via mockserver.dashboard.focus rather than opening an editor tab; mockserver.openDashboard still opens the external browser.

JSONC support

Expectation files may use .mockserver.jsonc with comments and trailing commas. The client uses jsonc-parser (a runtime dependency, NOT a dev dependency) to parse them. The .vscodeignore must therefore NOT blanket-ignore node_modules/vsce bundles production dependencies and prunes dev ones. If node_modules/** were ignored, the extension would fail at runtime with "Cannot find module 'jsonc-parser'".

Settings

Read fresh on every use from vscode.workspace.getConfiguration("mockserver"):

SettingDefaultNotes
mockserver.dockerImagemockserver/mockserver:<extension-version>Blank = derive from extension version
mockserver.containerNamemockserver-vscodeContainer started/stopped by the extension
mockserver.port1080Host port; also used for dashboard URL

JetBrains Plugin

Location: mockserver-jetbrains/

Language: Kotlin, compiled against IntelliJ Platform 2024.3 (IC), since build 243, until build 253.*.

Build: Gradle (build.gradle.kts) using the IntelliJ Platform Gradle Plugin 2.16. Requires Gradle 9.5.1+ (provided by the Gradle wrapper). JVM toolchain: 17.

Plugin id: com.mock-server.mockserver

Key components

FileRole
MockServerRestClient.ktREST client using java.net.http.HttpClient (JDK 11+). Free of IntelliJ platform APIs: pure request builders (build*Request) plus a thin send() wrapper. Must never run on the EDT
MockServerSchemaProviderFactory.ktImplements JsonSchemaProviderFactory; associates the bundled schema with *.mockserver.json / *.mockserver.jsonc files via SchemaType.embeddedSchema. Registered in plugin.xml under the extension point JavaScript.JsonSchema.ProviderFactory (<extensions defaultExtensionNs="JavaScript"><JsonSchema.ProviderFactory .../></extensions>) — see gotcha below
MockServerToolWindowFactory.ktBottom tool window ("MockServer"): a localhost:<port> status line, an inline Port field (commits on Enter / focus-loss, validates 1–65535, writes back to MockServerSettings.state.port and refreshes the status line; reverts on invalid), bold section headers, and every action as an icon button, invoking registered actions via ActionManager.getAction(id) + ActionUtil.invokeAction(...) with a SimpleDataContext carrying the project and selected editor. Actions read the port at click time (effectivePort()), so they pick up an edited port without restart; the already-loaded JCEF dashboard reflects a port change only when next reopened
MockServerDashboardToolWindowFactory.ktRight-side tool window ("MockServerDashboard"): embeds JBCefBrowser when JCEF (JBCefApp.isSupported()) is available; degrades to a fallback panel with an external-browser button when not. A CefLoadHandler.onLoadError swaps in a friendly "no MockServer running" panel (with a retry link) on a real load failure
MockServerEdt.ktShared runOnEdt(project, block) helper — posts UI work back to the EDT via invokeLater(block, defaultModalityState) { project.isDisposed }, so a result delivered after the project/tool-window closes is dropped instead of throwing AlreadyDisposedException. Every background action uses this single helper
MockServerSettings.ktApplication-level service (@Service(APP), persisted to mockserver.xml). Exposes effectiveImage(), effectivePort(), effectiveContainerName(), dashboardUrl()
MockServerConfigurable.ktSettings UI under Settings | Tools | MockServer
LoadExpectationsAction.ktRepresentative action: reads the active editor document, validates it, runs Task.Backgroundable for the HTTP call, posts result notification on EDT via invokeLater
src/main/resources/META-INF/plugin.xmlAction group under ToolsMenu — actions carry AllIcons icons and are grouped with <separator>s (Server / Editor / WASM); two tool window registrations; schema provider extension point
src/main/resources/META-INF/pluginIcon.svg / pluginIcon_dark.svgMarketplace plugin icon (rendered directly from SVG by JetBrains)

EDT discipline

All MockServerRestClient.send(...) calls block on the network and must run on a background thread. Actions use Task.Backgroundable (which shows a cancellable progress indicator) and marshal UI work back through the shared runOnEdt(project) { ... } helper in MockServerEdt.kt, which wraps invokeLater with a project.isDisposed expiry condition (so a late result after the project closes is dropped, not thrown). The tool window buttons invoke actions via ActionUtil.invokeAction(...), which handles threading internally.

JCEF dashboard

The dashboard tool window uses JBCefBrowser(dashboardUrl). The browser's native Chromium process is registered with Disposer.register(toolWindow.disposable, browser) so it is released when the tool window closes. The JBCefApp.isSupported() guard means the embedded dashboard works only with the JetBrains-bundled JRE; remote/headless environments fall back gracefully. A CefLoadHandler.onLoadError (ignoring ERR_NONE/ERR_ABORTED) replaces the page with a dark-themed "No MockServer running on localhost:<port>" panel and a retry link when the server is unreachable, instead of showing a raw Chromium ERR_CONNECTION_REFUSED page.

Settings

Stored in mockserver.xml via PersistentStateComponent:

FieldDefaultNotes
dockerImagemockserver/mockserver:<plugin-version>Blank = derive from plugin version
containerNamemockserver-ideContainer started by the extension
port1080Used for all REST calls and dashboard URL

Shared Schema Generation

Script: scripts/generate-editor-expectation-schema.mjs

The mockserver-core JSON Schema set consists of ~45 cross-referencing files under mockserver/mockserver-core/src/main/resources/org/mockserver/model/schema/. The server assembles them at runtime in org.mockserver.validator.jsonschema.JsonSchemaValidator#addReferencesIntoSchema. An editor needs one self-contained document, so the script performs the same assembly ahead of time.

What the script does:

  1. Reads expectation.json (the root schema) and every file listed in REFERENCE_FILES — this list mirrors JsonSchemaExpectationValidator's reference list exactly.
  2. Hoists all nested definitions from each reference file to the top level (matching addReferencesIntoSchema).
  3. Builds the root as a concrete object/array union that accepts either a single expectation or an array (initialization-JSON form): type: ["object","array"] with the expectation's properties/additionalProperties/anyOf inlined for the object form and items/minItems for the array form. It is deliberately NOT a root oneOf of $refs — IntelliJ's JSON-schema engine cannot provide completion or validation through a root oneOf reached only via $ref (it can't pick a branch), so a oneOf root silently breaks autocompletion and error highlighting in the JetBrains plugin (VS Code handles it either way). The object-only and array-only keywords never interfere, and the required-based anyOf branches are satisfied vacuously by array instances.
  4. Replaces every $ref: "#/definitions/draft-07" (the "this field is itself a JSON Schema" marker on embedded-schema fields like the jsonSchema body matcher) with a permissive inline schema { "type": ["object","boolean"] }. It must NOT be rewritten to the remote http://json-schema.org/draft-07/schema# URI: IntelliJ resolves a remote subschema $ref by fetching the URL over the network, which fails silently offline / behind a TLS proxy and makes IntelliJ discard the entire schema (no completion or validation for the file) — VS Code tolerates an unresolvable remote ref, IntelliJ does not. The draft-07 meta-schema is also deliberately not bundled (its $id would collide with validators' built-in draft-07, and its internal "$ref": "#" self-reference would re-root to the bundle). The top-level $schema dialect declaration stays — IntelliJ resolves that from its built-in meta-schema without a fetch. Trade-off: embedded-schema fields are no longer meta-validated as nested JSON Schemas (a niche feature), in exchange for the whole file validating at all.
  5. Runs two self-checks: (a) every #/definitions/X reference in the bundle must resolve — catches drift if the Java validator's reference list changes without updating REFERENCE_FILES; and (b) the root must stay IntelliJ-navigable (a concrete object root with inline properties, never a bare oneOf) — catches a regression to the broken root shape.
  6. Writes the result to both mockserver-vscode/schemas/ and mockserver-jetbrains/src/main/resources/schemas/.

When to re-run: whenever any schema file under mockserver-core/.../schema/ changes, or when JsonSchemaExpectationValidator's reference list changes. The generated file is committed; it is not auto-regenerated in CI.

node scripts/generate-editor-expectation-schema.mjs

Build, Test, and CI

VS Code

cd mockserver-vscode
npm ci
npm run compile          # tsc
npm test                 # node ./out/test/extension.test.js
npm run generate-schema  # regenerate the schema (runs the mjs script)
npm run package          # vsce package → .vsix

vscode:prepublish runs generate-schema then compile, so the schema is always current in published packages.

JetBrains

cd mockserver-jetbrains
./gradlew test           # JUnit 5; tests cover MockServerRestClient and helpers (no IDE required)
./gradlew buildPlugin    # produces build/distributions/*.zip
./gradlew runIde         # sandbox IDE with the plugin loaded

Buildkite pipeline

Pipeline: .buildkite/pipeline-editors.yml

StepImageWhat it does
VS Code compile + testnode:20npm ci && npm run compile && npm test; node_modules/ is removed inside the container on exit to avoid root-owned files breaking the next checkout
JetBrains plugin testseclipse-temurin:17-jdkCopies the project to /tmp/jb (avoids root-owned Gradle outputs in the workspace), then ./gradlew test --no-daemon; marked soft_fail: true

The JetBrains step runs as root and copies to /tmp to avoid a known elastic-ci-stack issue: Gradle + the IntelliJ Platform Gradle Plugin write build/, .gradle/, .kotlin/, and .intellijPlatform/ as root, and those directories prevent the next build's git checkout from cleaning the workspace.

Local test harness

scripts/try-editor-extensions.sh            # VS Code Extension Development Host
scripts/try-editor-extensions.sh jetbrains  # JetBrains sandbox IDE (./gradlew runIde)
scripts/try-editor-extensions.sh both
scripts/try-editor-extensions.sh --install  # package + install into real VS Code
scripts/try-editor-extensions.sh --no-server --port 9090

The script starts a mockserver-try Docker container (unless --no-server), creates a demo workspace under .tmp/editor-demo/ with demo.mockserver.json and petstore.openapi.json, and opens the workspace so schema validation and CodeLens are active immediately.

Gotchas

GotchaDetail
Icon? gitignore ruleThe repo .gitignore has a rule that matches Icon? (macOS resource-fork files); mockserver-jetbrains/src/main/resources/icons/ contains mockserver.svg and mockserver_dark.svg. If these files appear missing after a fresh clone on macOS, force-add them: git add -f mockserver-jetbrains/src/main/resources/icons/
.vscodeignore must not exclude node_modulesjsonc-parser is a runtime (production) dependency shipped in the .vsix. A blanket node_modules/** ignore would drop it and break the extension at runtime with "Cannot find module". vsce prunes dev dependencies automatically — only production deps need to ship
WASM body matcher field name is moduleNameThe expectation JSON body matcher for a WASM custom rule uses { "type": "WASM", "moduleName": "<name>" } — the WasmBodyDTO field and both server deserializers use moduleName, NOT wasm/wasmBody. Both extensions' upload UI and docs reflect this; keep any new copy in step
JetBrains tool-window buttons use ActionUtil.invokeActionButtons in MockServerToolWindowFactory look up the registered AnAction by id via ActionManager.getInstance().getAction(actionId) and fire it with ActionUtil.invokeAction(action, dataContext, ...). This reuses each action's own validation, notifications, and threading — do not call the action's REST logic directly from the button handler. The 5-arg data-context overload is @Suppress("DEPRECATION")-annotated: on platform 243 every non-deprecated replacement requires ActionUiKind (a later-platform API), so the migration is deferred until the minimum platform is raised
Schema must be regenerated after mockserver-core changesThe committed schema in both extensions is a snapshot. After any change to mockserver-core/.../schema/*.json or to JsonSchemaExpectationValidator's reference list, run node scripts/generate-editor-expectation-schema.mjs and commit the updated files in both extensions
JetBrains soft_fail in CIThe JetBrains test step has soft_fail: true in pipeline-editors.yml. A failing JetBrains step does not block the build — check Buildkite manually if JetBrains tests become relevant to a change
JetBrains Marketplace renders <description>, NOT the READMEThe JetBrains Marketplace page is built from the <description> HTML (limited subset: <p>, <ul>, <li>, <b>, <em>, <a>, <br> — no <img>/<code>/<style>) inside plugin.xml, not mockserver-jetbrains/README.md. Marketing copy must live in the <description>; screenshots/GIFs are uploaded separately in the Marketplace UI. The VS Code Marketplace and Open VSX, by contrast, render mockserver-vscode/README.md verbatim (images/GIFs allowed). When updating the pitch, update BOTH surfaces
Screenshot assets live in three placesThe IntelliJ screenshots are committed at mockserver-jetbrains/docs/screenshots/*.png (raw — rendered in the GitHub/plugin README) AND copied to jekyll-www.mock-server.com/images/intellij_*.png (raw — rendered on the mock_server/ide_extensions.html website page; both surfaces get a frame from the .ui_image CSS class, so the committed images stay raw). For the JetBrains Marketplace gallery (which has NO CSS), ready-to-upload framed copies — rounded corners + equal padding baked in — live in mockserver-jetbrains/docs/screenshots/marketplace/, regenerated from the raw screenshots by mockserver-jetbrains/docs/make-marketplace-screenshots.py <src-dir> <out-dir> (Pillow). JetBrains Marketplace screenshots are NOT taken from the repo automatically — they must be uploaded manually in the Marketplace UI
VS Code Marketplace screenshots come from the README, by ABSOLUTE URLThe VS Code Marketplace and Open VSX render mockserver-vscode/README.md, so screenshots embedded there appear on the listing automatically (no manual gallery upload, unlike JetBrains). But relative image paths break: vsce rewrites them against the repository field (github.com/mock-server/mockserver), which is the wrong repo for this monorepo. So the VS Code README references screenshots by absolute https://www.mock-server.com/images/vscode_*.png URLs — the same files added to jekyll-www.mock-server.com/images/. The website and the extension publish together on release, so the URLs resolve by the time the Marketplace page is live
VS Code activationEvents must include onStartupFinishedThe status-bar item and CodeLens providers are created in activate(). With an empty activationEvents they only appear after the first palette command runs (commands get implicit onCommand: activation). Keep onStartupFinished so the passive surfaces light up on a fresh window
JetBrains JSON-schema EP is JavaScript.JsonSchema.ProviderFactoryThe bundled JSON plugin defines the provider extension point as JavaScript.JsonSchema.ProviderFactory (the JavaScript namespace is historical — it is present in IntelliJ Community via com.intellij.modules.json). Register it as <extensions defaultExtensionNs="JavaScript"><JsonSchema.ProviderFactory .../></extensions>. Registering under any other name (e.g. com.intellij.json + jsonSchema.ProviderFactory) silently does nothing — IntelliJ does not fail the build or reliably log an error, the factory is just never instantiated, so there is no validation or completion at all. Verify the real EP name in the IDE's plugins/json/lib/json.jar META-INF/plugin.xml (grep extensionPoint ... ProviderFactory). A headless regression test — MockServerSchemaWiringTest (a BasePlatformTestCase) — guards this: it asserts JsonSchemaService actually binds the bundled schema to a *.mockserver.json file, so a wrong EP / null schema resource fails the build instead of silently disabling validation