English · 简体中文
Describe a service. Get C++ that builds, runs, and answers — with the evidence to prove it.
Quick start · How uvp works · What uvp can do · Current status · FAQ · Documentation
npx uvp-agents setup
Important
uvp is distributed from Antruly/uvp and
published on npm as uvp-agents. A similar repository name or a copied README
does not establish that a download is official.
uvp turns a description of a C++ web service into a real CMake project, builds it against libuvcpp, starts it, drives requests at it, and hands back a report that separates what it observed from what it derived and from what it could not establish.
It is one CLI and one MCP server. The same 22 tool contracts back both, so an agent and a shell script get identical behaviour and identical evidence.
The generated code is not a uvp artifact. It is ordinary libuvcpp C++ that builds
with cmake -S . -B build && cmake --build build and nothing else installed. You
can walk away from uvp and keep the project.
- Node.js
^22.19.0 || ^24.11.0 || >=26.0.0 - A C++14 toolchain (
g++orclang++; MSVC 2019+ on Windows, where CI has exercised Visual Studio 2026 / MSVC 14.51 only) and CMake 3.20+ - libuvcpp 1.7.0 or newer, either
cmake --installed or unpacked from a release archive.uvp doctorand a faileduvp buildboth name this requirement, with the release URL and the environment variables that make an install discoverable, when none is found. pkg-config(orpkgconf, which CMake 3.22 and newer search) when libuvcpp comes from a release archive. Those ship onlylib/pkgconfig/uvcpp.pcand no CMake package, so the generated project's pkg-config fallback is the route that runs; an install made withcmake --installanswersfind_package(uvcpp CONFIG)instead and does not need it.
On Windows the archive route uses the same pkg-config lookup CMake does:
pkg-config.bat is one of the names CMake searches, and from CMake 3.29.1 a
non-.exe is skipped when --help fails or its output says Pure-Perl
(Strawberry Perl's shim). uvp doctor applies that rule too. The archive needs
a Release build; installation.md has the
exact steps.
Run the doctor first. It reports what is present, what is missing, and which capabilities each gap actually blocks — rather than failing as one monolith:
uvp doctor --jsonnpx uvp-agents setupTo run from a source checkout instead:
git clone https://github.com/Antruly/uvp
cd uvp
npm ci && npm run build:cached
node scripts/uvp.mjs setupSetup prints every configuration change it intends to make and asks for approval before writing anything. It backs up existing configuration.
Build me a JSON API on port 8080 with an
/itemscollection and a WebSocket echo endpoint. Then start it and prove/items/1returns 200 with the right body, and that the echo endpoint actually echoes.
The agent scaffolds the workspace, builds it, runs it, exercises it, and reports back a verification bundle. If the build fails, the compiler diagnostics come back as structured data and the agent can fix them.
uvp init ./svc --name svc --template rest-api --port 8080 --features websocket
uvp build ./svc
uvp run ./svc --port 8080
uvp http GET http://127.0.0.1:8080/items/1 --jsonuvp run detaches by default: the service is still listening after the command
returns, and a later uvp stop --all starts a new CLI process whose registry is
empty, so it cannot see it. Stop the pid uvp run printed, pass --no-detach to
keep the command in the foreground, or use the supervised pass below, which
starts, checks, and stops the service in one call.
Or as one supervised pass that cleans up after itself (one line, so it is
copy-pasteable in cmd.exe as well as a POSIX shell; double quotes keep the
JSONPath intact in every shell):
uvp verify ./svc --port 8080 --request GET /items/1 --assert status=200 --assert "json:$.value=first"Four stages, each of which produces evidence rather than an assertion.
1. Describe. You name a template, a port, and the transport modules you need.
Before anything is written, the requested features are checked against the
libuvcpp build you actually have — read out of its generated uvcpp_config.h and
its installed headers, never inferred from its version number. Two 1.7.0 builds
can differ in whether HTTP/3 was compiled in, so a version number is not
evidence.
2. Generate. uvp writes a complete CMake project. Where a requested module is
missing, the generated main.cpp carries a #if guard that names the CMake
option to rebuild libuvcpp with, so the failure is one readable line instead of a
cascade of unknown-identifier errors inside libuvcpp's own headers.
3. Build and run. CMake's output is parsed into structured diagnostics
tagged with the phase that produced them, so a header error is distinguishable
from a link error. The service starts under supervision and is considered ready
only when the readiness probe answers — never after a fixed sleep. The ready
field reports whether the probe succeeded or the timeout expired.
4. Exercise and report. Requests are issued over real sockets, assertions are
evaluated against the real response, and the result is a bundle whose status is
verified only when every step that was attempted succeeded. Anything skipped
becomes a residual unknown with a reason attached. Missing coverage is reported
as unknown, never as complete.
That last rule is the one that shapes everything else. A tool that says "verified" when it merely did not look is worse than one that says nothing.
| Task | Tools |
|---|---|
| Check the toolchain and which libuvcpp modules are present | uvp_doctor |
| Scaffold a buildable project (7 templates) | uvp_workspace_create |
| Search, validate, import, export, and remove template packages, and generate a workspace from one | uvp_template_search, uvp_template_get, uvp_template_validate, uvp_template_import, uvp_template_export, uvp_template_remove; uvp_workspace_create (template_package) |
| Configure and build with CMake | uvp_build |
| Install and build a generated Node frontend app | uvp_frontend_build |
| Start a service and wait for it to actually answer | uvp_run, uvp_run_stop |
| Drive HTTP/1.1, HTTP/2, and HTTP/3 requests, and report TLS | uvp_http_request |
| Probe a WebSocket endpoint | uvp_websocket_probe |
| Post a SOAP envelope and read the reply | uvp_soap_call |
| Measure throughput and latency percentiles | uvp_load_test |
| Record a session of exchanges for later reading | uvp_capture_start, uvp_capture_read |
| Replay a recorded session against a live target | uvp_capture_replay |
| Evaluate assertions against a recorded exchange | uvp_assert_exchange |
| Do all of the above as one supervised pass | uvp_verify |
| Compare two verification reports and see what moved | uvp_bundle_diff |
Templates: hello-http, rest-api, websocket-echo, websocket-test,
soap-service, static-files, http3-service.
Features: tls, http2, http3, grpc, websocket, soap, db. Each maps
to real libuvcpp CMake switches and configuration macros; the mapping is
documented, including the two features libuvcpp has no switch for.
Most templates start a uvcpp_web_app. http3-service does not: it is written
against uvcpp_http_server, which is the only type in 1.7.0 that can open a QUIC
listener, and it serves one route table over TCP and HTTP/3 at once.
db is the one feature that changes what a template keeps. rest-api is the
only template with a store, so --features db writes it over libuvcpp's
connection pool — SQLite, MySQL or PostgreSQL, from UVP_DATABASE_URL, with a
SQLite file in the working directory as the default — in place of its in-memory
map. The routes, status codes and JSON are the same either way. Declaring db on
any other template is a build requirement, as every feature is, and no more.
uvp is pre-1.0 and under active construction. Being precise about what that means is more useful than a percentage:
Working and verified. The full closure — describe → generate → build → run →
probe → evidence — passes against a real libuvcpp 1.7.0 install for all
templates. The generated projects build via find_package(uvcpp 1.7.0 CONFIG)
where a CMake package exists and via pkg-config where it does not, because the
prebuilt release archives ship the latter and not the former. The version floor
is enforced on both routes.
The CLI and the MCP server are both built, and both are exercised as processes
rather than as modules: tests/boundary/cli/cliContract.test.ts runs the CLI and
checks what it prints and what it exits with, and
tests/boundary/mcp/mcpSurface.test.ts speaks the stdio protocol to the server.
CI runs the same lanes on a clean machine, and its packaged lane also installs the
packed tarball into an empty prefix and runs the CLI's first commands and the MCP
handshake from the installed copy, which is where a file missing from files[]
shows up. What each CI lane has actually reported is tracked in
docs/roadmap.md rather than assumed here: the first run that
reached a test at all found four defects and stopped there, and a lane whose
coverage is claimed and not yet seen is marked as such next to it.
The WebSocket probe runs on two stacks: a Node client by default, and libuvcpp's
own client through uvcpp-probe, a small C++ program that speaks the C ABI. That
second stack is what answers whether a service uvp generated talks to the stack it
was generated for, since two clients that both claim RFC 6455 can disagree about
everything the RFC leaves open. uvcpp-probe is built by npm run build:native-probe, is a separate process rather than a module uvp loads, and is
depended on by nothing uvp generates. tests/toolchain/nativeProbe.test.ts
generates a workspace, builds it with the CMake uvp found, starts it, and probes
it with the real binary: what the run reports, what the stack refuses before
anything is spawned, and what a person reads without --json.
HTTP/3 is answered the same way, and for the same reason: Node's standard library
has no HTTP/3 client, so uvp http --http-version 3 goes through uvcpp-probe's
QUIC client — libuvcpp's own, which is the stack a generated service links
against. The same lane is what checks it, so the claim is that one implementation
agrees with itself across the wire rather than that two projects read a spec
alike. Three things this path will not do, and says so instead of guessing:
http:// is refused because QUIC has no cleartext mode, verify_tls is refused
because that client verifies against a CA bundle it is handed or not at all, and
redirects are refused because the probe makes one request. The result reports
verification: not_checked rather than a certificate nobody examined, and names
the response headers it read rather than presenting them as the whole set. That
half of the probe exists only when the libuvcpp it was built against has the
HTTP/3 C ABI: without it the probe is still built, complete for WebSocket, and
--version reports which exchange is missing and what to change — the install,
not the probe build — so a host without HTTP/3 loses one transport rather than the
whole native stack. See
the probe's HTTP/3 build gate.
The database-backed template is verified as a store rather than as a transport, and
the assertion is a restart: tests/toolchain/database.test.ts writes a value
unique to the run, stops the service, starts it again, and reads the value back —
which is the only thing that separates a store from a handler that answers
{"stored":1} and forgets. The lane exercises SQLite, the one dialect that needs
nothing else installed; MySQL and PostgreSQL go through the same generated code
and the same driver-loading path, and are not claimed as observed.
Template packages (0.4.0). This release defines uvp.template/1, a standard
package format for templates, with the local store, deterministic search,
inspect/export, a bounded secret/PII scan, and a read client for the
uvp.registry/1 index used by the official registry,
Antruly/uvp-templates. The six CLI
and MCP tools (uvp_template_*) and uvp init --template-package are available
here: a package's files are rendered with {{project.name}},
{{project.port}}, and {{features}}, uvp.json records
template_package { id, version, digest }, and the routes the manifest declares
are reported. The registry does not claim a build-verified package until its
runtime lane is enabled.
In progress. The web toolbox — request, WebSocket probe, SOAP call, load
test, capture, assertions — works against any service, not only ones uvp
generates. The toolchain provider port has two implementations: local and
container. The remote-ssh provider is not written; it is blocked on a real
verification route rather than on appetite.
Releases. uvp-agents@0.2.0 was the first published release; 0.3.0 was
published next. 0.4.0 is the current release prepared by release-please. The conventional-commit gate on pull
request titles and the release-please workflow
(.github/workflows/release-please.yml, added in #177 for #160) produce the
release PR. Regenerating the derived files on the release PR remains a
maintainer step, by the recorded decision on #160; the publish workflow is
dispatched by hand from the release tag.
The Windows local-msvc lane has landed and runs in CI. It exercises the
database refusal case. The parser reads MSVC's LNK shape, but only against
documented examples — no test drives a real link.exe failure, and the two tests
that drive a real linker still skip there because their fixture is a GNU ld.
The rest of that list has landed and is checked: every action under
.github/ is SHA-pinned with the release named beside it
(npm run verify:actions), the tarball packs to the same digest from the tree,
from an unpacked copy at another path, and after a clean rebuild of dist/
(npm run verify:pack-reproducible), and the release artifacts carry a
machine-readable inventory — sbom.cdx.json, CycloneDX 1.5 over the production
dependency closure, regenerated and compared byte for byte inside the gate, and
shipped in the tarball with the notices it agrees with.
See the roadmap for what is planned and the testing guide for which lanes exist.
A generated rest-api workspace, verified end to end:
$ uvp verify ./svc --port 8091 --request GET /items/1 \
--assert status=200 --assert 'json:$.value=first'build succeeded (Debug, unix-makefiles, 4.1s)
libuvcpp 1.7.0 via CMake package (/opt/uvcpp/lib/cmake/uvcpp)
service listening on 8091, ready after 38ms
exchange GET /items/1 -> 200 in 2ms
status_equals 200 pass
body_json_path_equals $.value=first pass
status verified
When something is skipped, it says so instead:
status partial
unverified http2 responsiveness — requested feature http2 is not present in the
installed libuvcpp build (UVCPP_NGHTTP2_ENABLE is 0)
Why TypeScript, if the output is C++?
Because the hard part is not the C++. It is the contracts, the code generation, the diagnostic parsing, and the evidence algebra — all of which change often and are painful to get right in C++. The C++ that uvp produces is ordinary C++ that links libuvcpp directly; there is no uvp runtime library in the generated project and nothing to install alongside it.
This mirrors the engineering split that makes REA work: a replaceable control plane in TypeScript, with the substrate reached through a narrow, tested boundary.
Why is a failing build not an error?
A build that fails is the tool working. The result is the diagnostics, and they are structured, phase-tagged, and machine-readable. Only the inability to run the toolchain at all is an error. Conflating the two makes an agent's fix-and-retry loop indistinguishable from a broken installation.
What does "unknown" mean here?
It means uvp did not establish the answer, and knows it did not. Every
unestablished question is recorded with a reason — toolchain_missing,
unsupported_by_build, cancelled, out_of_scope, and so on. A coverage report
is complete only when uvp actually looked at everything in scope.
The distinction that matters: "the build lacks HTTP/2" and "uvp could not determine whether HTTP/2 is available" are different findings, and only one of them is true when a probe is missing.
What is the libuvcpp version floor, and why 1.7.0?
Generated projects require libuvcpp 1.7.0 or newer through both discovery routes. 1.7.0 is the most feature-complete release and is the baseline the templates are verified against. It is a floor rather than a pin because the API the templates use has been stable across the 1.x line, and pinning would force a rebuild of libuvcpp for no benefit.
An older libuvcpp fails the check with the version it found named in the message.
Does uvp work on Windows?
The generated projects do — the CMake templates branch for MSVC and the CMake
package route works there. The local provider is exercised on Windows by the
windows-toolchain CI lane with MSVC, where the database refusal case runs.
MSVC LNK link failures have a parser format since #142, but it is checked
against documented examples only: no test drives a real link.exe failure, and
the two tests that drive a real linker skip there because their fixture is a GNU
ld. The doctor reports what it can and cannot establish rather than pretending
otherwise.
- Installation and setup — requirements, libuvcpp discovery, agent configuration
- CLI and evidence — every command, output shape, exit status
- MCP contracts — the generated tool catalog
- Architecture — layers, dependency direction, the provider port
- Evidence model — observed, derived, unknown, and how to read a verification report
- libuvcpp feature mapping — what each feature needs from a libuvcpp build, verified against 1.7.0
- First-release support matrix — what the CI lanes have observed, derived, and left unknown
- Release notes — what shipped in the first public release and what remains unknown
- Tool design — how a uvp tool boundary is chosen
- Testing — test depths and real-toolchain lanes
- Roadmap — what is planned and what gates admission
- Releasing — the release process
- AGENTS.md — repository rules for AI agents
- AI development workflow — roles, ownership, review, and the approval gate
- AI engineer operations — identity, task-taking, and hand-back rules
- Current development plan — current progress, open work, and directions
- CONTRIBUTING.md — contribution scope and AI governance
uvp generates and runs C++ web services on your machine, with your permissions. Generated services listen on network sockets and, when you ask for it, make outbound requests. It is your responsibility to know what you are building and where it is reachable. See DISCLAIMER.md.
Bug fixes, documentation improvements, tests, and new templates are welcome. uvp is an AI-native project and AI-assisted contributions are expected — every contribution must still have a human owner who has read the diff and takes responsibility for it. See CONTRIBUTING.md for the AI contribution policy and AGENTS.md for the rules that apply to agents specifically.
MIT. Third-party components retain their own licenses; see THIRD_PARTY_NOTICES.md.