The dev console is a page that the application serves to your browser while you develop: the startup report of the boot that is running, with its anomalies first, then one tab per extension that has something to show — its boot facts and, for the extensions that give them, live values and charts, such as the connections of each Mansart pool — then the configuration, the beans of the CDI container and the JVM last. It listens on a port of its own, on the loopback address, it only reads, and it is on in a dev launch only.
Getting it NEW
Nothing to declare. mvn vidocq:dev adds the console itself, at the Vidocq version of the application’s Chappe extension — with a warning when the plugin is another version (Vidocq/vidocq#148) — the moment the
application already has vidocq-runtime-chappe-webserver-extension: the console serves its page through
Chappe, on a listener of its own, so without Chappe there is nothing to serve it and nothing is added. It also
adds, for every runtime extension that has one, that extension’s -dev panel module
(A -dev module, never packaged), so the extension’s tab is live. One
INFO line lists what was added:
[INFO] Dev tools: vidocq-runtime-cassini-rest-extension-dev, vidocq-runtime-mansart-pool-extension-dev
An application that has never served HTTP — no Chappe, so no console either — logs why instead, and starts without it:
[INFO] Dev tools: no dev console, it needs vidocq-runtime-chappe-webserver-extension
A test that reads the console — an assertion against its /api/snapshot, say — declares it itself, together
with the -dev modules it needs, at test scope: mvn test, vidocq:test and the continuous tests of
vidocq:dev add none of them.
<dependency>
<groupId>io.vidocq.runtime.extensions.essentials</groupId>
<artifactId>vidocq-runtime-devconsole-extension</artifactId>
<version>0.4.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
vidocq:run, vidocq:package, vidocq:jlink, vidocq:jpackage and vidocq:docker never contain the console
or any -dev module: every one of their jars carries Vidocq-Dev-Only: true in its manifest, and these
goals drop such a jar from what they launch or ship, even when the project declares it — a declared console, for
instance, kept from before this changed. Each dropped jar logs one WARNING per goal run, never fails the build,
and names what to remove:
[WARNING] vidocq-runtime-devconsole-extension is dev-only: not packaged; remove the dependency, vidocq:dev brings it
vidocq:checkpom gives the same advice earlier, at validate: a warning, never a failure, for a dev-only
dependency declared in any scope but test. See Dev
tools and binaries for the full picture, companions included.
The console’s SPI, vidocq-runtime-devconsole-spi, is not marked: it is an ordinary API jar of five small types,
and it ships in a binary whenever an extension that keeps the all-in-one DevConsolePanel form
(A -dev module, never packaged) requires it, so that such an extension
still resolves outside vidocq:dev.
Then run the application in dev mode:
mvn vidocq:dev
The banner promises the console, and one INFO record gives its address once it listens:
Java 25.0.3+9-LTS | dev (vidocq.launch.mode) | devconsole :8888
...
[INFO ][2026-09-18 21:28:11.834][main ][i.v.r.e.e.d.DevConsoleExtension#bound ] : Vidocq dev console: http://127.0.0.1:8888/
Open that URL. The browser is never opened for you.
When it is on NEW
vidocq.devconsole.enabled is auto by default: the console starts when the launch mode of the boot is dev — mvn vidocq:dev, a run from an IDE, -Dvidocq.launch.mode=dev — and stays off in test and prod. true and false force it on or off, whatever the mode.
auto also turns it on for a dev read from the shape of a build tree, such as an application run from target/classes by a CI job. That is accepted: the console listens on the loopback address only, and a port that is already taken never stops the application (When the port is taken NEW).
When the console is off, its section of the startup report says why, and nothing listens:
devconsole off (auto, launch mode prod)
off (vidocq.devconsole.enabled=false) when it was forced off. Every dev reload starts a new console, with a new listener; a reload whose boot fails takes the console down with it, and the page says so (The page NEW).
Configuration NEW
| Key | Default | Description |
|---|---|---|
|
|
|
|
|
The port the console listens on, from |
|
|
The address the console listens on: a host name or an IP address, an IPv6 literal with or without its brackets. Any address that is not a loopback one raises VIDOCQ-DEVC-002 — not a loopback address NEW. A value with anything but ASCII letters, digits and |
Like every key, they can be set with -D, an environment variable (VIDOCQ_DEVCONSOLE_PORT), vidocq.properties or a profile (%dev.vidocq.devconsole.port). On the Maven command line, mvn vidocq:dev -Dvidocq.devconsole.port=9000 reaches the application: vidocq:dev forwards the -Dvidocq.* keys to the JVM it forks, as vidocq:run does. The console declares these three keys, so the key audit reports a mistyped one as VIDOCQ-CFG-003: nothing reads a vidocq.devconsole.prot=18090, and the console listens on 8888, but the boot says so.
[VIDOCQ-CFG-003] Configuration key 'vidocq.devconsole.prot' is read by nothing and has no effect. Known keys in this namespace: vidocq.devconsole.enabled, vidocq.devconsole.host, vidocq.devconsole.port
Without the console on the module path, nothing claims the namespace, and a configured vidocq.devconsole.* key is not audited.
The console declares its listener, dev, itself: vidocq.chappe.listeners must not list it (A listener of an extension’s own).
Where it listens NEW
The URL record NEW
Once its listener is bound, the console logs one INFO record on the logger io.vidocq.devconsole, with the URL last and nothing after its final /, so that a terminal or an IDE console makes it a link:
Vidocq dev console: http://127.0.0.1:8888/
It is the address the console really bound: the free port chosen for 0 or for a taken port, localhost for a wildcard address such as 0.0.0.0, an IPv6 address in brackets. It is logged on the first boot of the JVM, and again on a dev reload only when the URL has changed; with port 0, a reload asks for the port the previous boot bound, so the URL does not change and the tab you have open keeps working. Chappe’s own listener 'dev' started line is logged at DEBUG, to leave this record alone.
The detailed startup report has the console’s section:
devconsole Dev console | 0 ms
http://127.0.0.1:8888/
enabled auto
configured 127.0.0.1:8888
dev http://127.0.0.1:8888/
The banner segment NEW
The banner, printed before any extension is loaded, already names the console on its context line, after the debugger:
| Configuration | Segment |
|---|---|
|
|
|
|
|
|
|
none: the port is only known once bound |
the console absent, or off |
none |
The segment is the configured address, a promise made before the bind: the URL record is the one to trust, and it always prints on the first boot. When the 80 columns of the banner cannot hold everything, the segment goes before the debugger’s address, which is what attaching a debugger needs (What the banner shows). The single INFO line of a log output keeps it: … | prod (vidocq.launch.mode) | devconsole 0.0.0.0:18093.
When the port is taken NEW
Two applications in dev mode on one machine both want port 8888. A dev tool must never stop an application from starting, so the second console listens on a free port instead, and says so loudly, three times. Chappe names both ports, then the console logs its URL and a boxed WARNING that holds numbers and the URL only, then the startup report raises VIDOCQ-DEVC-004 — port taken NEW:
[WARN ][2026-09-18 21:28:01.448][main ][i.v.r.e.e.c.ChappeServerBootstrap#start ] : Chappe listener 'dev': port 18092 is taken, listening on port 57738 instead
[INFO ][2026-09-18 21:28:01.449][main ][i.v.r.e.e.d.DevConsoleExtension#bound ] : Vidocq dev console: http://127.0.0.1:57738/
[WARN ][2026-09-18 21:28:01.450][main ][i.v.r.e.e.d.DevConsoleExtension#bound ] : Vidocq dev console: port 18092 is taken, listening on port 57738 instead
+------------------------------------------------------+
| |
| DEV CONSOLE: port 18092 is taken |
| It listens on port 57738 instead: |
| |
| http://127.0.0.1:57738/ |
| |
+------------------------------------------------------+
[INFO ][2026-09-18 21:28:01.451][main ][i.v.r.e.e.c.ChappeServerBootstrap#start ] : Chappe listener 'default' started on http://localhost:18090/
[WARN ][2026-09-18 21:28:01.457][main ][i.v.r.c.r.StartupAnomalies#warn ] : [VIDOCQ-DEVC-004] The dev console's port 18092 is taken: it listens on port 57738 instead, http://127.0.0.1:57738/ Free port 18092, or set vidocq.devconsole.port to another port, 0 for any free one
The page itself carries a banner with both ports. The box never quotes the configuration: it prints the two port numbers and a URL whose shape the console checked, so nothing configured can draw inside it. Only the application’s own listeners still fail the boot on a taken port, as they always did.
Falling back costs about one second of boot: before the console takes a free port, Chappe retries the bind with a backoff, five attempts with pauses of 100 to 400 ms, as it does for any port (1,387 ms against 358 ms in one measured boot). A dev reload pays that second again for as long as the port stays taken: the console keeps the port it bound across reloads only for port 0 (The URL record NEW), and asks for the configured port on every boot, so that it gets it back once it is free. Until then, each reload falls back to a free port chosen anew, usually another one, so the URL record, the box and VIDOCQ-DEVC-004 — port taken NEW come again with the new port, and an open tab has to be pointed at it.
The page NEW
The page is plain HTML, one stylesheet and one JavaScript module, all served by the console: no build step, and nothing loaded from another site, not even a font. It reads one document, GET /api/snapshot, about once a second.
-
Tabs. Startup first, then one tab per section of the startup report, in the order of the report, and the console’s own Configuration, CDI (Vauban) and JVM last. The page keeps the selected tab and the pause in the browser’s
localStorage, and nothing else. -
Polling. One request at a time. None while the tab is hidden, or while Pause is pressed.
-
A reload. When the application stops answering, during a dev reload, the page greys the last data under
reloading…and retries every second; after a minute it saysunreachableand retries every five seconds. A new boot is recognised by its id: the page draws its boot facts again and starts its charts over. -
Charts. The page keeps five minutes of history, 300 points per value, in the browser only: nothing is stored on the server. Time is the server’s clock, so a missed poll or a debugger paused on a breakpoint leaves a gap, never a lie.
-
Themes. Light and dark follow the system’s preference.
Startup NEW
The startup report of the boot that is running, whatever the level the log printed: the launch mode and the signal it was read from, the anomalies first, with their code, message, hint and the section that raised them, then the sections of the core — launch, vidocq, phases, layer, configuration, extensions — as tables, and the console’s own section. The full report, as the detailed level renders it, closes the tab, with a button that copies it for a bug report. Until the boot has written its report, the tab says booting….
A reload of the dev loop logs the summary report only; the console still has every row, since Vidocq collects them all for it whenever it is on.
Panels NEW
Every section that a library adds to the startup report is a panel, with the boot facts it wrote: its summary line, its rows and its anomalies. A section that is also a dev console panel adds live values:
-
a level (a gauge), such as the active connections: its number, and a fill bar when it has a maximum;
-
a total that only grows (a counter), such as the borrows so far: its total, and
+N since last poll; -
a duration, formatted;
-
an absent value, greyed with its reason, such as
leak detection off: never a zero that would read as a measure; -
tables;
-
charts of the values that change, drawn from the history the console keeps: a level, or the growth per second of a counter;
-
one card per group, such as one per pool, with its values, its charts, and the boot facts that belong to it.
When a panel’s sampling fails, its tab shows the class of the exception, never its message, and keeps its boot facts; the console logs VIDOCQ-DEVC-005 — a panel failed to sample NEW once and tries again on the next poll. A sample that took more than 5 ms is flagged slow, and one that went past the console’s limits is flagged as such.
The panels shipped today:
| Panel | Shows |
|---|---|
Mansart pools |
One card per pool of |
Mansart Data NEW |
The catalogue of |
REST (Cassini) NEW |
The routes of |
Metrics (Dirac) NEW |
The MicroProfile Metrics registries of |
Health (Knock) NEW |
The health checks of |
Schema migration NEW |
The datasources |
Dev services NEW |
What |
MCP server NEW |
The langchain4j-cdi MCP server of |
Configuration NEW |
The console’s own: the keys the application sets, the source each value comes from, the value in a dev launch only and never a secret, and the keys nothing reads. See Configuration NEW. |
CDI (Vauban) NEW |
The console’s own: the beans, interceptors and observers of the Vauban container, the application’s first. See CDI (Vauban) NEW. |
Logs NEW |
The console’s own, in a |
Tests NEW |
The console’s own panel under |
JVM |
The console’s own, below. |
An extension adds its panel with the panel SPI.
Configuration NEW
The console’s own panel, config, just before the CDI container: which keys the application sets, which source each value comes from, and which keys nothing reads. Everything is read once, when the console starts, from the configuration sources of the boot: the configuration does not change during a boot, and a dev reload boots again.
-
Which keys: those of the
vidocq.andmp.namespaces, whatever source sets them, and those the application’s own sources define: itsvidocq.propertiesorapplication.properties, an externalvidocq.properties, amicroprofile-config.propertiesunder Ravel, or a source of its own. The system properties and the environment are not listed key by key — the hundreds ofjava.*properties and variables such asPATHwould drown the rest — but a-Dproperty or an environment variable that overrides a key of the application’s file is listed, with the source that wins. -
Boot facts: a summary such as
12 keys: 5 of the application, 6 vidocq., 1 mp.; 4 sources, then:-
sources: every source with its ordinal, in lookup order, the one that wins first; -
values: what the table shows of the values, below; -
status: what the last column means, below; -
keys left out: how many keys the table could not show, only when it could not; -
source: the configuration sources, read once at boot.
-
-
Table
keys, the application’s keys first, thenvidocq., thenmp., each by name, 100 rows at most:-
key; -
sourceandordinal: the source that wins, the first in lookup order that has a value for the key — the very valueVidocqConfig.getValuereturns. An environment variableDB_USERwins fordb.userwhen it is set; -
value: in adevlaunch only, the raw value of that source. Under Ravel it is read before profiles and${…}expressions, so an expression is shown as written, never expanded; -
status:unusedwhen aVIDOCQ-CFG-003anomaly of this boot names the key — a key nothing reads, a typo most of the time;claimedwhen the key falls under a namespace the core or a loaded extension reads, such asvidocq.mcp.*;not auditedfor any other key, the application’s own among them. The status is read from the startup report, which is written after the console starts: until then it readsreport not written yet.
-
What is never shown:
-
A secret. A key whose last segment names one —
password,passwd,pwd,secret,token,key,credentials,apikey,api-keyorprivate-key, in any case, alone or as the end of the segment, sodb.adminPassword,DB_PASSWORDandvidocq.mcp.requestStateSecrettoo — readsconfigured. The rule is on the key: a value that merely contains the wordpasswordis shown when its key names no secret. -
The credentials of a URL.
jdbc:postgresql://admin:s3cr3t@db/ordersreadsjdbc:postgresql://@db/orders, and a query parameter that names a secret by the same rule,password=orpwd=among them, readspassword=. -
Any value outside a
devlaunch. A console forced on in atestorprodlaunch shows the keys and their sources, andnot shown outside devfor every value.
The values are made safe in the application’s JVM, before the snapshot is written: a secret never reaches the page, and the panel logs nothing.
CDI (Vauban) NEW
The console’s own panel, cdi, just before the logs of a dev launch and the JVM: what Vauban, the runtime’s CDI container, holds for this boot, as Quarkus’s ArC card shows it. Everything is read once, when the console starts, from the container’s metadata: the beans its bean manager lists for Object and @Any, its interceptors and its observer methods. No bean is created and no context is read, so a lazy bean stays lazy.
-
Boot facts: a summary such as
33 beans (29 application-scoped, 4 dependent), 0 interceptors, 0 decorators, 1 observer, then:-
beans,interceptorsandobservers: how many, and how many of them are the application’s, and how many come from a library — every class that is not the application’s; -
scopes: the beans by scope, most first; -
decorators: always none — Vauban implements CDI Lite, which has no decorators; -
application: how the application’s classes were told apart. When Vidocq booted the application in a module layer of its own, asvidocq:dev,vidocq:run, the packaged launcher and a@VidocqMaintrampoline do, they are the classes of that layer, and every other class — Vidocq’s, an extension’s such as the MCP server’s, a third-party jar’s — is a library’s. Otherwise they are the classes outside theio.vidocq,jakartaandjavapackages, plus those of the main module; -
beans codegen,interceptors codegen,observers codegen: how many rows each code-generation verdict has, such as41 APT, 12 Class-File, 3 partial, 20 reflection; -
beans left out,interceptors left out,observers left out: how many rows a table could not show, only when it could not; -
source: Vauban’s metadata, read once at boot.
-
-
Tables, the application’s rows first, then by class name, 100 rows each at most:
-
beans: the class, the kind (managed,producer method,producer field,synthetic,built-in), the scope, the qualifiers without@Any, which every bean has, whether it is an alternative, and where it comes from,applicationorlibrary. A disabled alternative is not listed: the container does not resolve it; -
interceptors: the class, its interceptor bindings, its priority ordisabled: no @Priority, and where it comes from,applicationorlibrary; -
observers: the event type, its qualifiers, the declaring class and method,syncorasync, and where it comes from,applicationorlibrary.
-
Every table ends with two columns that say whether a row runs code generated at build time or falls back to reflection, as Vauban reads it from what each generated _VaubanComponents provider declares:
-
codegen:APTwhen the annotation processor generated everything the row needs,Class-Filewhenvauban:generateorvidocq:generatedid,APT + Class-Filefor both,partialwhen some of it falls back,reflectionwhen all of it does,unknownwhen a provider built before Vauban declared its coverage serves it — rebuild that jar — andn/afor synthetic and built-in beans; -
by reflection: what falls back, such asfield logger,@PostConstruct init()orclient proxy. An interceptor’s@AroundInvokealways does today (vauban#109), and so does a producer field.
A third-party jar’s beans read reflection until vidocq:generate scans the jar; a CDI bean archive is scanned by default, and mvn vidocq:analyze-deps tells which jars are and why (the vidocq:generate goal).
A table with no row reads none. The tables are written as the panel’s values rather than its boot facts because a value table has column heads; they are computed once, and every poll hands the page the same rows. The panel keeps class names as strings only, never a class or a bean of the application.
Logs NEW
The console’s own panel, logs, between the CDI container and the JVM, in a dev launch only: what the application logged in this boot, and a level changed without a restart. When the console starts, it adds a handler of its own to the root logger of java.util.logging, and removes it when the boot stops, so that a dev reload never stacks two. The handler keeps the last 500 records in memory, in a bounded ring written without a lock: logging never waits for the console.
-
Boot facts: a summary such as
the last 500 records, root logger at INFO, thenbackend, where the logs go (java.util.logging, or the class of theSystem.LoggerFinderthat took them),kept, androot level. -
Counters
warningsanderrors: the WARNING and the SEVERE records seen since the boot, not only those still kept, drawn per second in the chart Warnings and errors. -
Table
records, newest first, 100 rows at most: the time (HH:mm:ss.SSS), the level, the logger with its packages cut to their initials (i.v.c.CassiniExtension), the thread, and the message, formatted with its parameters asjava.util.loggingformats it. A record logged with an exception adds the exception’s class and the first line of its message, never the stack trace. -
Table
levels: the root logger, then every logger whose level is set, by name, anduntil the next reloadnext to those the button set. -
Button Set level: a logger name, empty for the root logger, and a level of
java.util.logging, fromSEVEREtoFINEST,ALLorOFF. It answers, for example,io.vidocq.cassini at FINE until the next reload. The level lasts until the next dev reload, which puts every level the button set back as it was: a reload starts from the configured levels. The console holds the loggers it changed, sincejava.util.loggingholds its loggers weakly and would otherwise forget the level. No confirmation is asked: it is harmless, and undone on the next reload.
What is never shown or kept:
-
The record itself. Each record is written once into strings when it is logged; the ring never keeps the record, its parameters or its exception, which may be objects of the application and would keep it in memory past a reload.
-
The credentials of a URL in a message, removed as the configuration panel removes them:
jdbc:postgresql://admin:s3cr3t@db/ordersreadsjdbc:postgresql://*@db/orders. A message is cut at 500 characters, and the page cuts what it shows at 200. A log message can still carry anything the application wrote into it, which is why the panel exists in adevlaunch only. -
Records that go elsewhere. When an SLF4J or Log4j bridge replaces the JDK’s
System.LoggerFinder, what Vidocq and the libraries log never reachesjava.util.logging:records,warningsanderrorsare absent with the reason, such aslogs go to Log4jSystemLoggerFinder, not java.util.logging, rather than an empty table that would read as a quiet application. Thelevelstable and the button still act onjava.util.logging.
Tests NEW
The console’s own panel, tests, between the logs and the JVM, in a dev launch run by vidocq:dev with
continuous testing only: the last test run. The Maven plugin writes it to
target/vidocq-dev-tests.json and names that file with -Dvidocq.dev.tests.results; a thread of the console,
vidocq-devconsole-tests, reads it again whenever it changes, and a poll never touches the disk.
-
Boot facts: the last run when the console started, the results file and the log.
-
Text
state: the state and the trigger of the last run, such asfailed (change)orrunning (test-change);results unreadablewhen the file could not be read, the previous values staying. -
Gauges
run,failures,errorsandskipped: those of the last complete run, the previous one while a run is in progress. The chart Tests plots the failures and the errors. -
Table
failed-tests, 100 rows at most: the test asClass#method, the exception’s class, and the first line of its message. -
Buttons Run all tests and Rerun failed tests: each answers
queuedand the run shows in the panel once it ends; Rerun failed tests answersno failed test to rerunwhen the last run had none. A rerun that matches no test runs every test instead NEW. A button never cancels the run in flight: it waits for it.
What is never shown: a stack trace — it is in the log — or the credentials of a URL in a message. The buttons
never send a test name: they write the word run-all or rerun-failed to target/vidocq-dev-tests.request,
and the plugin works out which tests failed.
JVM NEW
The console’s own panel, always last, read from the JVM’s management beans — the same figures that Dirac’s base metrics read, with nothing to add to the application.
-
Boot facts: the Java version and vendor, the VM, the garbage collectors, the maximum heap, the processors, the process id, and
source JMX. Never a JVM argument or a system property: a-Dcan carry a password, as the dev services' do. -
Live values: the heap in use (out of its maximum, or of what is committed when the JVM has no maximum), the heap committed, the non-heap in use, the threads (with daemon threads and the peak), the classes loaded, the uptime, the CPU load of the process and of the system as percentages, and the load average. A figure the JVM does not publish is
not available, never a zero. -
One group per garbage collector: its collections, and its time as the JVM counts it: pause time for G1, Parallel and Serial; ZGC and Shenandoah publish their pauses as collectors of their own, which get their own card.
-
Charts: memory (heap in use, committed, and the maximum as a ceiling), non-heap, threads, CPU, and per collector the share of time it took and its collections per second.
Where the history lives NEW
The console keeps five minutes of every gauge and counter — 300 points, one a second — and it keeps them on the server, in a ring filled by a thread of its own named vidocq-devconsole-history. That thread runs for the life of the boot, whether or not a page is open.
It used to be the page that kept them, one point per poll. A browser stops the timers of a hidden tab, so the history stopped the moment the tab stopped being the visible one — and on macOS, Chrome counts a window that another application covers as hidden. The result was a hole in every curve covering exactly the minutes you had left the console to go and cause something, which is the interval you came back to look at.
-
A poll asks for what it is missing.
GET /api/snapshot?since=<the newest point the page holds>carries the points after that one only; with nosince— a first load, or a newboot— it carries the whole ring. A tab that comes back after three minutes away sends its stalesinceand gets the three minutes it missed, in one document. -
An absent value is a hole, not a zero. A tick where a panel wrote
absentkeeps its place on the axis with a null value, and the curve breaks there. Bridging it would draw a straight line asserting a measure nobody took. -
A tick the server missed is still a hole. A long pause, a suspended process, a laptop that slept: those points are simply not there, the gap between neighbours is too wide, and the curve breaks. Moving the history to the server removes one cause of holes; it does not hide the ones that remain true.
-
It is bounded, and the bound is a number. 256 series of 300 points, measured full at 1,966,960 bytes — 1.88 MB — and it does not grow with the length of a run. Series are kept first seen, first served; one that arrives past the cap keeps its live value on its tile and gets no curve, and the snapshot says
historyTruncated. -
It is forgotten with its boot. A new
bootid — a dev reload included — starts every curve again, andonStopstops the thread before the panels go.
MCP inspector NEW
In a dev launch of an application that has vidocq-runtime-langchain4j-cdi-mcp-extension, the mcp tab lists
the application’s tools, prompts, resources and resource templates, and can call each of them. The tab has four
sub-tabs: Monitoring, with the panel’s values, charts and boot facts, then Tools, Prompts and Resources,
resources and resource templates sharing the last. A sub-tab of a kind is a list to pick the item from, its
description and its form under it, then its result in a block of its own, then the history of that kind’s calls;
past ten items a text filter above the list narrows it. The page remembers the open sub-tab in this browser, and,
until the page is reloaded, the item picked in each.
A tool’s arguments are a generated form when its input schema allows one — scalars, enums of strings,
and objects of those one level down — and the JSON editor otherwise, started from its required
properties, which checks and completes them against the schema. A "JSON" switch shows the same values in that editor
on a form too, and switching back keeps them. A prompt’s arguments and a resource template’s variables are always a
flat form, one string per argument or variable.
The call goes through the application’s own /mcp, from the console’s own JVM, in protocol 2026-07-28, with no
session opened: the URL is the one the startup report prints for /mcp, a loopback one preferred when more than
one listener carries it. When no listener has bound /mcp, the tab shows absent: /mcp has no bound address and
offers nothing to call. The inspector sends no credentials and never goes through a proxy: when /mcp is behind
authentication, every call answers HTTP 401. The console shows the first 128 actions of a panel; past that, the
inspector’s line ends with (first 128 shown).
Confirmation. Before it sends a tool call, the page asks Call <name>? It runs the application’s code, and may
change data. unless the tool is readOnlyHint: true and not destructiveHint: true. A prompt get or a resource
read never asks.
A result shows in a block of its own under the form: a header with the outcome, ok in green, error in red,
running… while the call runs, then the summary line, the round trip the page measured and the viewer’s buttons;
then the body, JSON going through the JSON viewer; and, folded under "Exchange", the JSON-RPC
request and response exactly as they were sent and received (or the SSE events, for a streamed answer). Each item
keeps its last result while the page is open: picking it again in the list shows it. A refused call, or a console
that does not answer, shows its reason in that block too, as an error. What each outcome shows:
| Outcome | summary |
error |
body |
|---|---|---|---|
tool result |
|
from |
the content: the text items joined, or the JSON of |
prompt |
|
false |
the messages as JSON |
resource |
|
false |
the first text content, or the JSON of the contents |
JSON-RPC error |
|
true |
the error object |
a tool asking the client for input |
|
true |
the server’s missing-capability error, or its input request |
transport failure |
|
true |
none |
A response body past 1 MiB fails the call as a transport failure; the panel never buffers more of it than that. The body and the details the page shows are truncated at 256 KiB regardless, as any action’s are.
A tool that asks the client for input. The inspector declares no client capability — neither elicitation, nor
sampling, nor roots. A tool that checks isSupported() first, as langchain4j-cdi recommends, runs its fallback, which
is how you test it. A tool that asks anyway gets the server’s missing-capability error (-32021), which the
inspector reports with the refusal line above, naming the capability. Nothing is parked on the server, whatever the
request-state mode: in CONTINUATION mode too, the call ends with that error before any invocation waits for an
answer.
History. The panel keeps the last 20 calls of the boot, newest first, each with its time, action, outcome, duration and arguments, and a "Replay" button that picks the action in the list and fills its form with those arguments, and sends nothing until you submit. Each sub-tab shows the calls of its own kind, under the result. A dev reload clears the history along with the catalogue; an item it removed loses its result, the list falling back to the first item of its kind, and a kind left with no item sends the tab back to Monitoring.
Secrets. An argument whose name holds, ignoring case, password, passwd, secret, token, apikey,
api-key, api_key, credential or authorization, at any depth, is shown as in the history, the
"Exchange" details and the console’s log line; a string or number value of four characters or more that belongs to
such an argument is also replaced by wherever it appears in the summary, the details and the body of an error
or an input request, in case the server quotes it. The MCP server itself always receives the real value, and a
result body is shown exactly as the server returned it. A masked value is left empty on a replay: type it again.
What v1 does not do. Elicitation, sampling and roots are not supported — a tool that needs one gets the refusal line above, not a dialog. There is no support for subscriptions, notifications or progress, for completion, or for calling an MCP server other than the application’s own.
JSON viewer NEW
Every JSON the page shows — the body of an application/json result and the details under "Exchange" — goes
through a small viewer instead of plain text. Keys, strings, numbers, and true, false and null each have their
colour, in the light and the dark themes. Every object and array folds under a ▾/▸ toggle; folded, it reads
{…} 3 keys or […] 12 items. A click on the toggle or on that summary flips the node; Alt+click flips it and
everything under it. The first two levels start open, only the first when the document holds more than 500 values.
Expand all, Collapse all and Copy sit next to what they act on: in the result’s header for the body, at the
top of "Exchange" for the details. Copy writes the JSON indented, masked values as the server sent them; when the
browser refuses the clipboard, the button says Clipboard refused and nothing else happens. An integer too large
for a JavaScript number, such as 9007199254740993, is shown and copied as the server wrote it in a browser that
gives JSON.parse the source text of a number, such as a current Chrome; elsewhere it is rounded to the nearest
JavaScript number.
The viewer keeps what is folded while the page polls, and forgets it when a new result replaces the one on screen. A body that does not parse as JSON is shown as text. The tree is built with DOM calls only, never as markup, so a key or a string is never read as HTML.
A table of rows NEW
A result of rows — what Query answers in a pool’s tab or in the JDQL tab of Mansart Data, a table’s columns,
its first rows — is drawn as a table. Each column’s header holds its name and, dimmed under it, its type; the header
stays in view while the rows scroll, and a wide table scrolls sideways inside the result. A NULL is written NULL,
dimmed, and is never mistaken for an empty string, which is an empty cell. A value longer than 200 characters is cut
with …, and the whole of it shows when the pointer rests on the cell. When the panel sent only the first rows,
more rows not shown says so under them.
The result’s bar adds Table and JSON, which shows the same body in the JSON viewer, and Copy as
CSV, which copies the rows as RFC 4180 CSV: the header first, , between fields, a field with a comma, a quote or a
line end quoted, each line ended by CRLF, NULL an empty field and an empty string "". The page remembers which of
the two it shows while it polls. A cell of a column named replay is a button named after the action it fills, as
Tables offers Describe and Preview for each table; nothing is sent until you submit.
A panel answers rows with PanelAction.ActionResult.rows(summary, columns, rows, more): columns are
ActionResult.Column(name, type) records, a name and a type of 200 characters at most; each row holds one value per
column, null, a Boolean, a Number or a String, anything else written as its toString(). The body,
{"columns": [{"name", "type"}…], "rows": [[…]…], "more": bool} of type application/x-rows+json
(ActionResult.ROWS), stays within the 256 KiB of a result: the rows past it are left out and more set. A body of
another shape is shown as any other JSON.
JSON editor NEW
A json argument of an action — an MCP tool’s arguments in the MCP inspector, the arguments of a
repository method in the Mansart Data tab — is typed in a small code editor written for the page, which loads
nothing from elsewhere. It reads the argument’s JSON Schema as you type:
-
Colours. Keys, strings, numbers,
true,falseandnulltake the JSON viewer's colours, in the light and the dark themes; what is no JSON at all is red. The lines are numbered. -
Diagnostics. The first syntax error —
unterminated string,expected ',' or '}',trailing comma,nothing after the value… — then what the schema says, at every depth: a missingrequiredkey (marked on its object’s{), a value of the wrongtype(integerrefuses1.5), a value outside itsenum, a string longer thanmaxLengthare errors; a key its object’spropertiesdoes not list, and a string that does not look like itsformat(date,time,date-time,uuid), are warnings. Each is underlined, red or orange, with a dot on its line; its message shows when the caret or the pointer is on it, and the line under the editor counts them:1 error, 2 warnings. They never stop a call: the server stays the judge. -
Completion.
Ctrl+Spacelists what fits where the caret is, and a"typed where a key goes opens the list by itself. In an object, the keys of its schema it does not have yet, required first and read-only last, each with its type,requiredorgenerated, and its description; the value comes with the key, an object with its required keys. After a:or in an array: theenumvalues,trueandfalse,null,{}or[].UpandDownmove,EnterorTabor a click accepts,Escapecloses, and typing filters the list. -
Keys. An opening bracket or quote brings its closing one, a closing character typed before the same steps over it,
Backspacebetween an empty pair deletes both,Enterkeeps the line’s indentation and opens an indented line between{}or[],TabandShift+Tabindent and outdent the selected lines by two spaces.EscapethenTableaves the editor. The bracket next to the caret and its match are outlined. -
Format. The Format button, or
Shift+Alt+F, indents the JSON by two spaces, one member per line, and keeps every string and number as written: an id past 2^53 or a\u00e9escape does not change. A text that does not parse is not formatted, and the line under the editor says why:not formatted: line 3: trailing comma. -
Undo. Every edit the editor makes — an accepted completion, a formatting, a closed pair — is undone by one
Ctrl+Z(⌘Z).
The editor reads these keywords of the schema: type (one name or a list), properties, required,
additionalProperties, items (a schema, or a list for the positions of an array), enum, maxLength, format,
readOnly, description, and a $ref to /$defs/… or /definitions/…. Any other $ref is not followed; what
is under anyOf, oneOf, allOf, not or patternProperties is accepted as it is. A schema the editor does not
understand makes it check less, never fail. What you typed is sent as you typed it. Past 100 000 characters the
editor stops colouring and checking, and says past 100 000 characters: no colours, no checks; completion and
Format still work.
The generated form NEW
When the schema allows it, a json argument is a form rather than JSON, with a JSON switch to the editor that
keeps the values both ways. The schema’s root is "type": "object", with no $ref, anyOf, oneOf, allOf or
not, and each property is a string, number, integer or boolean, an enum of strings, or an object whose own
properties all are such: one level of nesting, drawn as a group of fields under the object’s name. A string of
"format": "textarea" is a field of several lines (A JSON argument). Any
other schema gets the editor alone.
-
A required property is starred,
title *, and a call without it is refused with its path, before anything is sent:entity.title is required. A nested object that is not required and left empty is not sent at all. -
A
readOnlyproperty, such as an id the database generates, readsid (generated), its field showinggenerated. It is never required: left empty it is not sent; filled, it is. -
A field of
"format"date,time,date-timeoruuidshows the shape it expects:2026-09-30,14:30:00,2026-09-30T14:30:00,123e4567-e89b-12d3-a456-426614174000. -
The editor starts from the required properties, at each level. Replay fills the form, a nested object included; a value the panel masked is left for you to type again.
Query editor NEW
A query argument — the query of the JDQL tab of Mansart Data, in Query, Update / Delete and Export CSV
(A JDQL console for Mansart Data) — is typed in the
same editor, in its query mode, five lines high, with the vocabulary its panel publishes: the entities and their
attributes. The page fetches that vocabulary once, when the first form that needs it is drawn, and again after a dev
reload only; until it arrives, or when it cannot be had, the editor works with the keywords alone and says why under
it, no vocabulary: 404. It arrives without moving the text or the caret.
-
Colours. Keywords, functions, the entity, its attributes, strings, numbers and parameters (
:name,?1) each have a colour of their own, in the light and the dark themes; a name the vocabulary does not know is plain. -
Completion.
Ctrl+Spacelists what fits where the caret is: afterFROMorUPDATE, the entities, each with its table; afterproject., the attributes of the entity the reference leads to, one step at a time however long the path; in aSELECT,WHERE,ORDER BYorSETclause, the attributes of the query’s entity, even whenFROMcomes after the caret, each with its Java type and its column, thenthis, the functions, inserted with(and the caret inside, and the other keywords; anywhere else, the keywords. Typing filters the list, ignoring case; a keyword is inserted in capitals. -
Diagnostics. An unknown entity, an unknown attribute of the entity or of the one a reference leads to, a path through an attribute that is no reference (
title is not a reference), an unterminated string, and a parenthesis never closed or closing none are errors, underlined as the JSON editor’s. Nothing else: the grammar is the server’s to judge, and it says what is wrong when the query runs. An entity written by its full class name is not checked. -
Keys.
(and'come in pairs; a'typed right before a closing one steps over it, and a doubled''inside a string is one quote. The other keys are the JSON editor’s. -
Format. Format, or
Shift+Alt+F, writes the keywords in capitals, starts each clause on a line of its own andANDorORon an indented line; theANDof aBETWEENstays where it is, and names, strings, numbers and parameters are kept as written. A query with an unterminated string is not formatted:not formatted: line 2: unterminated string.
Its parameters NEW
The params of a query is a JSON editor whose schema follows the query as you type it: one key per named parameter,
in order of first use, all required, and no other. A parameter compared with an attribute takes its type, its format
and its values: after WHERE status = :status, completion offers "status" with the values of its enum, and the
editor flags the key while it is missing. LIKE :pattern takes a string, BETWEEN :low AND :high the attribute’s
type for both, IN :statuses a list of it, SET title = :title the attribute’s type; the description of each says
where it is used, such as compared with price (number). A parameter used twice keeps its first typed use; one used
otherwise, such as price * :factor, takes any value; a positional one, ?1, has no key. Nothing is refused before
the call. Its text is sent as typed; empty, it holds {}.
SQL NEW
The sql of a pool’s Query and Execute (Tables and a
SQL editor) is typed in the same editor, with what its panel publishes of the pool’s tables, and reads SQL as SQL is
written:
-
Several tables. Every table after
FROM,JOIN,UPDATE,INSERT INTOorDELETE FROMis in the statement’s scope, and so is each table of aFROMlist; a name after a table, orASand a name, is its alias (FROM tasks t,JOIN projects AS p), a keyword never. A table of another schema is writtensales.orders. -
Quoted names.
"Order","due date"are names, a doubled""inside one quote; the editor looks them up as written, and a name written without quotes as the database stores it: in lower case for PostgreSQL, in capitals for H2."pairs and steps over as'does.--and/* … */comments are dimmed and never checked. -
Completion. After
FROMorJOIN, the tables; aftert.ortasks., that table’s columns; inSELECT,ON,WHERE,GROUP BY,HAVING,ORDER BYandSET, the aliases and the tables in scope, then the columns of each, their table in their detail (varchar(200) · column · tasks), the functions and the keywords. A name that needs quotes — a keyword, a space, a capital the database would not keep — is inserted with them. -
Diagnostics. An unknown table, an unknown column of a table or an alias, and an unknown alias before a dot (
unknown table or alias x) are errors; a column named alone that two tables in scope have is a warning,title is in tasks t and projects p. A table written with its schema is looked up as written, then without it, and not checked when neither is known. What a sub-query, aWITH’s table, an alias of the select list, a cast’s type (::text`) or a function the editor does not know holds is never checked: the database judges it when the statement runs. A sub-query or a function’s rows afterFROMorJOIN,LATERALor not —(SELECT …) x,generate_series(1, 3) AS g(n)— is a table whose columns are unknown, named by its alias, and so is aWITH r(a) AS (…); the field ofEXTRACT(EPOCH FROM due_date)is no column. -
Format. Each clause on a line of its own, a
LEFT JOIN … ONon one line,ANDandORindented, the keywords in capitals, quoted names, strings and comments as written, a sub-query on the line it is on.
A :name in the statement is a parameter, as in JDQL: params follows it, and a parameter compared with t.price
takes the type of price in `t’s table.
A query language for a panel NEW
A panel gives a json argument’s property this mode in two steps.
-
Its
languages(), onDevConsolePanelorLivePanel, returns thePanelLanguagerecords it offers: an id, which follows the key rule ofPanelSample, such asjdql, and the text of one JSON object, at most 1 MiB and 64 levels deep, checked when the record is built. It is read once per boot, withactions(), in adevlaunch only. Two languages of one id leave the panel without languages, and the console logs a WARNING saying so; alanguages()that throws leaves it without languages too. The snapshot names each panel’s ids,"languages": ["jdql"], never their content, whichGET /api/language/<panel>/<id>serves as the panel wrote it:GETorHEAD, adevlaunch only (404otherwise), no token since it only reads, anOrigin, when there is one, that is the console’s own (403), and never logged. -
The schema of the argument names it. A string property of
"format": "textarea"whose schema also says"contentMediaType": "text/x-query"and"x-language": "<id>"is the query editor; a string property of"format": "textarea"and"x-parameters-of": "<query property>", beside the query, is the JSON editor of its parameters. Both are sent as strings, as typed.
{ "mode": "query",
"dialect": {
"keywords": ["SELECT", "FROM", "WHERE", "ORDER", "BY", "AND", "OR", "NOT", "IS", "NULL", "BETWEEN", "LIKE",
"IN", "ASC", "DESC", "UPDATE", "SET", "DELETE", "COUNT", "THIS", "SUM", "AVG", "MIN", "MAX",
"TRUE", "FALSE"],
"functions": ["UPPER", "LOWER", "LENGTH", "ABS", "CONCAT", "COUNT", "SUM", "AVG", "MIN", "MAX"],
"clauses": ["SELECT", "FROM", "WHERE", "ORDER BY", "SET", "UPDATE", "DELETE FROM"],
"targetAfter": ["FROM", "UPDATE"],
"self": "this",
"quote": "'" },
"targets": {
"Task": { "detail": "table task",
"attributes": {
"id": { "type": "integer", "detail": "Long · id, generated" },
"title": { "type": "string", "detail": "String · column title" },
"dueDate": { "type": "string", "format": "date", "detail": "LocalDate · column due_date" },
"project": { "type": "integer", "detail": "→ Project · column project_id", "target": "Project" } } } } }
mode is query. keywords are matched ignoring case and written in capitals; functions are the names that may be
followed by (; clauses start a clause; targetAfter are the words after which the entity is named; self is the
entity’s own name in an expression; quote delimits a string, doubled inside it. A word the dialect leaves out takes
JDQL’s. targets maps each entity’s name to its detail and attributes; an attribute has a JSON Schema type
(string, integer, number, boolean), optionally a format and an enum, a detail, and a target when it
refers to another entity of the language. Data the editor does not understand makes it know less, never fail.
SQL’s options NEW. aliases, identifierQuote and unquotedCase make a dialect SQL’s (SQL NEW),
with SQL’s own words; JDQL declares none of them:
"dialect": { "keywords": […], "functions": […], "self": null, "quote": "'",
"aliases": true,
"targetAfter": ["FROM", "JOIN", "UPDATE", "INTO"],
"identifierQuote": "\"",
"unquotedCase": "lower",
"clauses": ["SELECT", "FROM", "JOIN", "ON", "WHERE", "GROUP BY", "HAVING", "ORDER BY", "LIMIT", "OFFSET", "SET",
"VALUES", "UPDATE", "DELETE FROM", "INSERT INTO"] }
aliases puts every target after a targetAfter word in scope, with its alias, skips -- and /* comments, and
leaves a sub-query unchecked; identifierQuote makes a quoted name a name; unquotedCase, lower or upper, says how
the database stores a name written without quotes, and is left out when it keeps it as written; "self": null says
there is none. A target of another schema is keyed schema.name and says its "schema", so that completion quotes each
part as needed.
Security NEW
The console shows what the application is made of, so it is built to be seen by you only:
-
Loopback by default. It listens on
127.0.0.1. Another address raises VIDOCQ-DEVC-002 — not a loopback address NEW, in every mode. -
Its own address only. A request is answered only when its
Hostheader names the console:localhost,127.0.0.1,[::1]or the host it listens on, optionally with the port it bound; when that host is not a loopback one, any IP address too. The host it listens on is the configured one only when the console accepted it: a value refused by the ASCII-only rule of Configuration NEW is reported as VIDOCQ-DEVC-003 — invalid value NEW and replaced by127.0.0.1, for the bind, the banner and this check alike. Anything else gets403. This stops DNS rebinding, where a page of another site has its name resolved to127.0.0.1to reach local ports: the browser still sends that site’s name. -
Read-only outside development NEW. In a
testorprodlaunch the console answersGETandHEADonly, anything else gets405,POST /api/action/…included: no panel’sactions()is called, and the snapshot carries neither a token nor an action. -
Actions in a
devlaunch, for the console’s own page only NEW. A panel may offer actions, such as setting a log level; the console runs one only for a request its own page sent, behind the checks of Actions NEW. -
A read-only dev MCP in a
devlaunch NEW.POST /mcpserves the page’s facts to a coding agent as MCP tools that change nothing, behind the checks of Dev MCP NEW: no token, since nothing it answers is not already on the page, but anOrigin, when there is one, that is the console’s own. -
Query languages, read-only, in a
devlaunch NEW.GET /api/language/<panel>/<id>serves the vocabulary of a panel’s query editor, its entities and attributes, behind the checks of A query language for a panel NEW:GETorHEAD, no token, anOrigin, when there is one, that is the console’s own; outsidedev,404. -
No reading from another site. No CORS header, so another site’s page can send a request but never read the answer. Every answer carries
X-Content-Type-Options: nosniff,Referrer-Policy: no-referrerandContent-Security-Policy: default-src 'self'; frame-ancestors 'none'. -
No secret. A section reports a secret only as
configuredornot configured: the API has no way to pass its value. A configuration value that may carry one, such as a JDBC URL, is shown in a dev launch only, without its credentials; outside it, the panel says what it is, such as the database kind. The console never shows JVM arguments or system properties, nor the message of an exception thrown by a panel. -
Text stays text. Everything a panel writes reaches the page as text, never as markup, with control characters replaced and cut at 200 characters.
-
Links are built, never written NEW. A panel’s button or a route’s URL becomes a link only when Vidocq built it from a listener the report declared and a path: an absolute
httporhttpsURL, opened in a new tab withrel="noopener noreferrer". A panel has no way to pass a URL of its own. See Links. -
A warning outside development. Forced on in a
testorprodlaunch, the console raises VIDOCQ-DEVC-001 — on outside development NEW, naming where it listens.
Actions NEW
In a dev launch, a panel may offer actions, such as setting a log level (Actions): buttons next to its links, with a field per argument and, when the panel asks for one, an inline confirmation. That makes the console no longer read-only, and a write is a different threat from a read: a page of any site you visit can send a POST to http://127.0.0.1:8888/. It cannot read the answer, but it does not need to. So POST /api/action/<panel>/<action> runs only when every check passes, in this order, each failure answering without running anything:
-
the
Host, as for every request (403); -
the method is
POST(405), and the body isapplication/json, a charset allowed (415): a cross-site form cannot send it without a CORS preflight, which the console never answers; -
Originis present and ishttp://followed by the veryHostthe request was let in with: the origin of the page the console served there (403). A missing origin,null,https, another name for the same address or another port are refused; -
the header
X-Vidocq-Console-Tokenequals the token of the boot, 32 random bytes in hex drawn fromSecureRandomwhen the console starts, compared in constant time; the page reads it from/api/snapshot(console.actionToken), which another site cannot read (403); -
the panel and the action exist (
404), the body is at most 64 KiB for an action with ajsonargument, such as the MCP inspector's, 4 KiB otherwise (413), and it is one JSON object of strings whose keys are exactly the arguments the action declared, each value accepted by its check, ajsonvalue parsing as a JSON object (400).
A refusal at step 3 or 4 is logged as VIDOCQ-DEVC-006 — an action request from elsewhere NEW. An action runs on a virtual thread, one at a time per panel (409 while another runs), and the request waits for it up to 60 seconds: 200 {"result": "…"}, 500 {"error": "<exception class>"}, or 202 {"state": "running"} past the limit, the action going on. The snapshot lists each action with its last outcome of the boot, so the page shows it after a reload too. Every run is logged at INFO, Vidocq dev console: action <panel>/<action> by <address>: <result>; a failure at WARNING with the class of the exception, which is all the page shows of it, never its message. The decision and the options set aside are in ADR 0001, docs/adr/0001-dev-console-actions.md in the repository; ADR 0001, amendment 1, adds the json argument, the structured result and the groups this section and Actions describe.
Dev MCP NEW
In a dev launch, the console is also an MCP server, as Quarkus’s Dev MCP is: a coding agent, such as Claude Code, asks the running application what it serves instead of guessing it from the sources. It is served by the console itself, on the console’s own listener, never the application’s, so it works whether or not the application uses MCP or langchain4j-cdi, and adds no dependency. The console prints its URL after its own:
Vidocq dev console: http://127.0.0.1:8888/
Vidocq dev MCP: http://127.0.0.1:8888/mcp
and the devconsole section of the report shows it on its mcp row. To point Claude Code at it:
claude mcp add --transport http vidocq-dev http://127.0.0.1:8888/mcp
Any MCP client that speaks the Streamable HTTP transport does the same; the MCP Inspector lists the tools with npx @modelcontextprotocol/inspector --cli --transport http --server-url http://127.0.0.1:8888/mcp --method tools/list.
| Tool | What it answers |
|---|---|
|
The startup report as the page shows it: the console (version, URL, boot id), the launch mode and why, the anomalies, every section that is not a panel with its lines, and the whole report as text. |
|
The panels of the page, the extensions' and the console’s own: |
|
One panel, by |
|
The anomalies of the startup report, each with its code, message, hint and source. |
|
The routes of the |
Each tool answers one JSON object, as structuredContent and, for a client that reads text only, indented in one text content. They are built from the very document the page polls, /api/snapshot, minus what only the page needs to act — the token of the boot and the actions — so an agent sees nothing the page does not: secrets read configured, values are masked as on the page (Security NEW).
-
Read-only. No tool runs an action; every tool says so in its description and carries
readOnlyHint. The actions stay the page’s (Actions NEW), until an ADR decides otherwise. -
The protocol, the part it needs. One JSON-RPC 2.0 request per
POST, answeredapplication/json, never an event stream, and no session: noMcp-Session-Idis given or needed. It answersinitialize(the protocol version asked for when it is2025-03-26,2025-06-18or2025-11-25, the newest of them otherwise;serverInfo.nameisvidocq-dev-console),ping,tools/listandtools/call; a notification, such asnotifications/initialized, gets202. An unknown method is the JSON-RPC error-32601, parameters it cannot use-32602, a body that is not JSON-32700and anything but one request object, a batch included,-32600, these two with400.GET /mcpgets405: the server never starts a stream. -
The checks, in this order. The
Host, as for every request (403); the method isPOST(405); the body isapplication/json(415), which a cross-site form cannot send; theOrigin, when there is one, is the console’s own, as for an action (403, logged as VIDOCQ-DEVC-006 — an action request from elsewhere NEW) — an MCP client that is no browser sends none, and a browser always sends one on a cross-sitePOST; theAcceptheader takesapplication/json(406); the body is at most 64 KiB (413) and nests at most 32 levels. No token: the tools answer what the page shows, which theHostcheck already keeps to the console’s own address. -
Outside
dev, absent. In atestorprodlaunch,POST /mcpgets405like any otherPOST, and the URL is neither printed nor in the report.
Anomaly codes NEW
The first four are anomalies of the console’s section of the startup report: each is one WARNING record on io.vidocq.startup.anomaly, logged the moment it is found and recalled by the report (Anomalies). The fifth comes later, from a poll, the sixth from a refused action request, and the seventh, eighth and ninth NEW from matching the live panels the -dev modules bring against the report’s sections, once per boot — all five on the console’s own logger, io.vidocq.devconsole.
VIDOCQ-DEVC-001 — on outside development NEW
vidocq.devconsole.enabled=true turned the console on in a test or prod launch. It names where the console listens, the configured host and the bound port:
[VIDOCQ-DEVC-001] The dev console is on in a prod launch: it shows the startup report and the live values of this application on 0.0.0.0:18093 Remove vidocq.devconsole.enabled=true outside development
Remove the key, or scope it to a profile: %dev.vidocq.devconsole.enabled=true.
VIDOCQ-DEVC-002 — not a loopback address NEW
The console listens on an address that is not a loopback one, in any mode, so whoever reaches the machine can read the page:
[VIDOCQ-DEVC-002] The dev console listens on [0:0:0:0:0:0:0:0]:18093, which is not a loopback address: whoever reaches this machine can read the startup report and the live values of this application Remove vidocq.devconsole.host, or set it to 127.0.0.1
The address is the one bound, as the JVM writes it: 0.0.0.0 reads as the IPv6 wildcard.
VIDOCQ-DEVC-003 — invalid value NEW
A vidocq.devconsole.* key has a value the console does not accept; the default is used, and the message says which:
[VIDOCQ-DEVC-003] Invalid value 'yes' for vidocq.devconsole.enabled (auto, true, false): using auto
[VIDOCQ-DEVC-003] Invalid value '99999' for vidocq.devconsole.port (0 to 65535): using 8888
[VIDOCQ-DEVC-003] Invalid value 'a host' for vidocq.devconsole.host (a host name or an IP address): using 127.0.0.1
A host is refused for any character but ASCII letters, digits and .-_:%[], or beyond 255 characters: the console listens on 127.0.0.1 instead, and the banner says so too (The banner segment NEW). A mistyped key, rather than a wrong value, is the key audit’s to report: VIDOCQ-CFG-003 (Configuration NEW).
VIDOCQ-DEVC-004 — port taken NEW
The configured port was taken: the console listens on a free port instead (When the port is taken NEW).
[VIDOCQ-DEVC-004] The dev console's port 18092 is taken: it listens on port 57738 instead, http://127.0.0.1:57738/ Free port 18092, or set vidocq.devconsole.port to another port, 0 for any free one
VIDOCQ-DEVC-005 — a panel failed to sample NEW
A panel’s sample() threw a RuntimeException or a LinkageError, an invalid value key included. That poll misses the panel’s live values, its boot facts stay, and the console calls it again on the next poll. The WARNING is logged on io.vidocq.devconsole once per panel and boot, with the class of the exception only; its stack trace follows at DEBUG on the same logger:
[VIDOCQ-DEVC-005] Panel 'acme-cache' failed to sample: IllegalArgumentException
It is a bug of the panel: see Writing a dev console panel.
VIDOCQ-DEVC-006 — an action request from elsewhere NEW
In a dev launch, an action request was refused for its Origin or its X-Vidocq-Console-Token (Security NEW), or a dev MCP request NEW for its Origin: it did not come from the console’s own page. Most likely a page of another site tried to make the console do something, or a stale tab of a previous boot sent its old token. Nothing ran. The WARNING is logged on io.vidocq.devconsole with the origin the request named, once per origin, reason and boot, 64 at most:
[VIDOCQ-DEVC-006] Dev console action refused, origin 'https://evil.example.com': not the console's own origin
[VIDOCQ-DEVC-006] Dev console action refused, origin (none): not the console's own origin
[VIDOCQ-DEVC-006] Dev console action refused, origin 'http://127.0.0.1:8888': no valid X-Vidocq-Console-Token
[VIDOCQ-DEVC-006] Dev console MCP request refused, origin 'https://evil.example.com': not the console's own origin
The last one, from the console’s own origin, is a tab that outlived its boot: reload it.
VIDOCQ-DEVC-007 — two live panels for one section NEW
Two -dev modules' live panels (A -dev module, never packaged)
name the same section id. The first one found wins; the second is skipped, never sampled. Logged once per
boot, the first time the console reads the written report:
[VIDOCQ-DEVC-007] Two live panels for the section 'mansart-pool': io.acme.dev.FirstLivePanel is kept, io.acme.dev.SecondLivePanel is skipped
Two extensions share an id by mistake, or the application declares two versions of the same -dev module on
the module path. Remove the one that should not be there.
VIDOCQ-DEVC-008 — a live panel with no section NEW
A -dev module’s live panel names a section id that the startup report does not have — the runtime
extension’s own contributor failed, was skipped, or does not exist. The panel is never shown; it never invents
a section:
[VIDOCQ-DEVC-008] The live panel 'mansart-pool' has no section in the startup report: not shown
The application has the -dev module without its matching runtime extension, or that extension’s contributor
did not run. Check for VIDOCQ-RPT-001 (Anomalies) on
the same boot.
VIDOCQ-DEVC-009 — a panel shipped twice NEW
A section’s own contributor already implements DevConsolePanel (the all-in-one form,
Writing a dev console panel), and a -dev module’s live panel also claims that
section’s id. The live panel wins; the contributor’s own sample and charts are never called:
[VIDOCQ-DEVC-009] The section 'mansart-pool' is written by io.acme.MansartPoolExtension, itself a panel, and a live panel makes it live: the live panel wins; the extension ships its panel twice
The extension should drop one of the two: either its own DevConsolePanel implementation, or the -dev
module, not both.
Loggers NEW
| Logger | Records |
|---|---|
|
The URL record (INFO), the boxed WARNING of a taken port, a console that could not start (WARNING), VIDOCQ-DEVC-005 — a panel failed to sample NEW, and the stack trace of a failed sample (DEBUG). In a |
|
VIDOCQ-DEVC-001 — on outside development NEW to VIDOCQ-DEVC-004 — port taken NEW, as every startup anomaly. |
Next steps NEW
-
Writing a dev console panel NEW — show the live values of an extension
-
Startup report — what the Startup tab shows
-
vidocq-runtime-maven-plugin —
vidocq:dev