sharpit
An embedded, local-only developer console for FastAPI apps. One call mounts a web console at /sharpit and a
JSON API at /sharpit/api inside your own app, so a running service becomes understandable in minutes: its
configuration and where each value came from, every route, the dependency providers, loggers you can retune live,
and a vulnerability scan of the installed packages.
sharpit is a Python/FastAPI port of BootUI by Julien Dubois (Apache-2.0), the Spring Boot developer console. It ports BootUI's design goals, JSON contract and test cases; the UI is new.
Status: alpha (0.1.0, FastAPI only). Django and Flask are not supported. See CHANGELOG.md.
Install
sharpit is not on PyPI yet. Until 0.1.0 is published, install it from git:
pip install "sharpit @ git+https://github.com/ffroliva/sharpit.git"
# or
uv add "sharpit @ git+https://github.com/ffroliva/sharpit.git"
Once published, it is pip install sharpit or uv add sharpit.
Python 3.12+. Runtime dependencies are only starlette>=0.44 and packaging>=23. Optional extras:
| Extra | Adds | Why |
|---|---|---|
sharpit[fastapi] |
fastapi>=0.115 |
the supported host |
sharpit[settings] |
pydantic-settings>=2.4 |
the Config panel shows which source each value came from |
Quickstart
from fastapi import FastAPI
import sharpit
app = FastAPI()
sharpit.install(app) # mounts /sharpit and /sharpit/api
Run it with a dev signal and open http://127.0.0.1:8000/sharpit/:
fastapi dev app.py # or: uvicorn app:app --reload
To show where your own settings values come from, pass the pydantic-settings instance (or a list of them):
sharpit.install(app, settings=Settings()). Other options are keyword arguments, see the table below.
Why does a plain uvicorn app:app give 404?
sharpit is a development tool, so it fails closed. With no reason to be on, every path under /sharpit answers a
plain 404 rendered by the host app's own 404 handler. The mount stays in place while inactive, as BootUI's does, so
sharpit still owns the paths under it: a host route such as /{user}/... cannot answer for /sharpit/..., and a host
with a catch-all or authenticate-first route answers /sharpit/* differently from its other paths (a 404 where it
would otherwise send 401), which shows that sharpit is installed. It switches on when the first of these applies:
SHARPIT_ENABLED=ON(always on;OFFalways off).- An active profile is in
SHARPIT_ENABLED_PROFILES(defaultdev,local,development). Profiles are read from the variables inSHARPIT_PROFILE_VARS(defaultSHARPIT_PROFILES,APP_ENV,ENVIRONMENT), soAPP_ENV=devworks. - A reloading dev server is detected from the command line:
uvicorn ... --reloadorfastapi dev.
A profile in SHARPIT_DISABLED_PROFILES (default prod,production) wins over 2 and 3 and switches it off.
uvicorn.run(..., reload=True) from a script is not detected: set APP_ENV=dev or SHARPIT_ENABLED=ON.
When sharpit was asked for but ended up off, it logs the reason at WARNING; an explicit OFF or nothing asking for it
logs at INFO. When on, it prints a banner on stderr and GET /sharpit/api/overview reports the reason under
activation.
Panels
| Panel | What it shows |
|---|---|
| Overview | app, Python and package versions, active profiles, activation reason and warnings |
| Configuration | environment, .env files, pydantic-settings and os.getenv keys, the winning source of each value, masked |
| Mappings | every route of the host app |
| Loggers | logger tree with levels you can change at runtime |
| Beans | dependency providers, middleware and lifespan, with their dependencies |
| Vulnerabilities | installed distributions checked against OSV and scored with EPSS, on explicit request |
| MCP server | status and switch for the read-only MCP endpoint |
| CLI | the same tool catalogue over plain HTTP |
GET /sharpit/api/panels lists what this build offers and why a panel is unavailable or read-only.
MCP for AI agents
The MCP endpoint is off by default. Enable it with SHARPIT_MCP_ENABLED=ON (or install(app, mcp_enabled="ON"),
or the switch in the MCP Server panel), then point a streamable-HTTP MCP client at
http://127.0.0.1:8000/sharpit/api/mcp (POST only). It exposes read-only tools: get_overview, get_config,
get_mappings, get_loggers, get_beans, get_vulnerabilities_report. There is no action tool, so an agent cannot
change a logger or start a scan through MCP. The same guard chain and masking apply to it as to the UI.
An agent skill that teaches an AI assistant to install and use sharpit is in
skills/sharpit. The sharpit command-line client
is planned and not shipped; the /sharpit/api/cli HTTP endpoint is.
Settings
Every setting is an environment variable and an install() keyword. The environment variable wins over the
keyword, so a deployment can always force SHARPIT_ENABLED=OFF. An empty value counts as unset. The name rule is
SHARPIT_ + the keyword in upper case. An invalid value never raises: that setting takes its fail-safe value
(disabled, read-only, masked) and a warning is recorded and shown on the Overview panel. Only an unusable path or
api_path raises ValueError, and an unknown keyword raises TypeError with a suggestion. Booleans accept
true/false/yes/no/on/off/1/0; durations accept 10s, 500ms or a bare number of seconds.
| Env var | Keyword | Default | Meaning |
|---|---|---|---|
SHARPIT_ENABLED |
enabled |
AUTO |
ON, OFF or AUTO (use the rules above) |
SHARPIT_ENABLED_PROFILES |
enabled_profiles |
dev,local,development |
profiles that turn it on |
SHARPIT_DISABLED_PROFILES |
disabled_profiles |
prod,production |
profiles that turn it off |
SHARPIT_PROFILE_VARS |
profile_vars |
SHARPIT_PROFILES,APP_ENV,ENVIRONMENT |
variables the profiles are read from |
SHARPIT_PATH |
path |
/sharpit |
console mount point |
SHARPIT_API_PATH |
api_path |
/sharpit/api |
must sit under path |
SHARPIT_ALLOW_NON_LOCALHOST |
allow_non_localhost |
false |
accept non-loopback callers (token required) |
SHARPIT_ALLOWED_HOSTS |
allowed_hosts |
empty | extra Host names to accept |
SHARPIT_TRUSTED_PROXIES |
trusted_proxies |
empty | proxy addresses or ranges admitted as remote callers (token still required) |
SHARPIT_TRUST_CONTAINER_GATEWAY |
trust_container_gateway |
OFF |
AUTO trusts the detected container gateway |
SHARPIT_AUTHENTICATION_TOKEN |
authentication_token |
generated | bearer token for untrusted sources |
SHARPIT_READ_ONLY |
read_only |
false |
refuse every write action |
SHARPIT_PANELS_<ID>_ENABLED |
panels={"<id>": {"enabled": ...}} |
true |
per-panel switch |
SHARPIT_PANELS_<ID>_READ_ONLY |
panels={"<id>": {"read_only": ...}} |
false |
per-panel read-only; <ID> is the panel id in capitals with - as _: OVERVIEW, CONFIG, MAPPINGS, LOGGERS, BEANS, VULNERABILITIES, MCP_SERVER, CLI |
SHARPIT_EXPOSE_VALUES |
expose_values |
MASKED |
MASKED, METADATA_ONLY or FULL (FULL logs a warning) |
SHARPIT_MASK_SECRETS |
mask_secrets |
true |
mask secret-looking names and values; sharpit's own token is masked even when false |
SHARPIT_SHOW_BANNER |
show_banner |
true |
print the startup banner to stderr |
SHARPIT_STATE_DIR |
state_dir |
.sharpit |
where dismissed vulnerability rules are stored (sharpit.yml, created on first write) |
SHARPIT_CONFIG_SCAN |
config_scan |
true |
true scans your top-level package for os.getenv keys, false disables, or a list of package names |
SHARPIT_VULNERABILITIES_OSV_ENABLED |
vulnerabilities_osv_enabled |
true |
allow the OSV scan |
SHARPIT_VULNERABILITIES_OSV_BASE_URI |
vulnerabilities_osv_base_uri |
https://api.osv.dev |
OSV endpoint |
SHARPIT_VULNERABILITIES_EPSS_ENABLED |
vulnerabilities_epss_enabled |
true |
enrich findings with EPSS scores |
SHARPIT_VULNERABILITIES_EPSS_BASE_URI |
vulnerabilities_epss_base_uri |
https://api.first.org |
EPSS endpoint |
SHARPIT_VULNERABILITIES_REQUEST_TIMEOUT |
vulnerabilities_request_timeout |
10s |
per-request timeout |
SHARPIT_VULNERABILITIES_MAX_PACKAGES |
vulnerabilities_max_packages |
500 |
packages sent to OSV |
SHARPIT_VULNERABILITIES_MAX_ADVISORIES |
vulnerabilities_max_advisories |
200 |
advisories kept per scan |
SHARPIT_MCP_ENABLED |
mcp_enabled |
OFF |
ON enables the MCP endpoint |
SHARPIT_MCP_MAX_RESULTS |
mcp_max_results |
200 |
cap on items per tool call |
SHARPIT_MCP_MAX_PAYLOAD_BYTES |
mcp_max_payload_bytes |
1048576 |
largest request body |
SHARPIT_MCP_MAX_CONCURRENT_CALLS |
mcp_max_concurrent_calls |
20 |
parallel tool calls |
SHARPIT_MCP_EXECUTION_TIMEOUT |
mcp_execution_timeout |
30s |
per tool call |
SHARPIT_MCP_MAX_RESPONSE_BYTES |
mcp_max_response_bytes |
4194304 |
largest response body |
SHARPIT_CLI_ENABLED |
cli_enabled |
true |
enable the /sharpit/api/cli endpoint |
SHARPIT_CLI_MAX_RESULTS |
cli_max_results |
200 |
cap on items per call |
SHARPIT_CLI_MAX_CONCURRENT_CALLS |
cli_max_concurrent_calls |
20 |
parallel calls |
SHARPIT_CLI_EXECUTION_TIMEOUT |
cli_execution_timeout |
30s |
per call |
The first Config load scans your package's source for os.getenv keys and caches the result. On a large application
that one-time scan can take several seconds. Set SHARPIT_CONFIG_SCAN=false to skip it, or narrow it to the smaller
top-level packages that read settings (SHARPIT_CONFIG_SCAN=billing_config,accounts; a dotted name scans its whole
top-level package).
Safety model
- Local only. Requests must come from a loopback address with a loopback or allow-listed
Host. IfForwarded,X-Forwarded-FororX-Real-IPis present and no trusted proxy is configured, the caller counts as remote. - Cross-site requests are refused on every method (
Sec-Fetch-Site: cross-site, or, on API paths, anOriginthat differs from the request's full origin: scheme, host and port; other paths compare the host), even when your app hasCORSMiddleware(allow_origins=["*"]). - Remote access needs a token. Only loopback, or the container gateway under
SHARPIT_TRUST_CONTAINER_GATEWAY=AUTO, with no forwarded header skips the token. Callers admitted bySHARPIT_ALLOW_NON_LOCALHOST=trueor by aSHARPIT_TRUSTED_PROXIESaddress (whose requests may carry a rewritten peer) must present the token asAuthorization: Bearer <token>. SetSHARPIT_AUTHENTICATION_TOKEN; otherwise a token is generated per process and printed once to stderr. A browser trades it for anHttpOnly; SameSite=Strictcookie throughPOST /sharpit/api/auth/session. - Secrets are masked (
******) in values, defaults and suggestions, in API, MCP and CLI output, including pydanticSecretStr. sharpit masks any URL that carries userinfo, including a user name with no password (https://<key>@o1.ingest.sentry.io/1,redis://admin@cache:6379), because DSN-style keys sit in that slot. A harmless user name in a URL is shown as******too. That is intentional: sharpit errs towards hiding. - GET has no side effects. No network call, no file write, no logger change. The vulnerability scan (OSV and EPSS) runs only on an explicit POST.
- Fails closed. When off, or in a
prod/productionprofile (unlessSHARPIT_ENABLED=ONforces it on, with a warning), the whole mount answers a plain 404 (see Why does a plainuvicorn app:appgive 404? for what that does and does not hide).
Behind a TLS-terminating proxy
The Origin check compares scheme, host and port. If a proxy terminates TLS and forwards plain HTTP, uvicorn must see
the original scheme or browser POSTs from the UI are rejected. Start uvicorn with --proxy-headers and
--forwarded-allow-ips (or set FORWARDED_ALLOW_IPS) to the proxy's address, and add that address to
SHARPIT_TRUSTED_PROXIES. Requests through the proxy still need the token.
Multiple workers and --reload
sharpit keeps its state in the process that serves the request. With uvicorn --workers N, gunicorn or --reload:
- Token. When
SHARPIT_ALLOW_NON_LOCALHOST=trueand no token is configured, each process generates its own token, so a token printed by one worker is rejected by the others and a reload invalidates the browser session. SetSHARPIT_AUTHENTICATION_TOKENto a fixed value in these setups. - MCP switch, logger levels, scan cache. The MCP on/off switch, the last scan result and logger levels changed in
the panel live in memory per process, so each worker has its own. Dismissed vulnerability rules are shared because
they live in
sharpit.yml.
For a dev tool, the simplest setup is one worker.
Deviations from BootUI
sharpit follows BootUI's JSON contract and keeps its guard and masking rules, with deliberate differences (a stricter cross-site check, Python-specific activation, a new UI, deferred features). The log is section 14 of the design spec.
License
Apache License 2.0. See LICENSE and NOTICE. sharpit is derived from BootUI, Copyright Julien Dubois.
Metadata
Release files for sharpit 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| sharpit-0.1.0.tar.gz | 351.5 kB | With provenance | Details for sharpit-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| sharpit-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for sharpit-0.1.0-py3-none-any.whl |
Total release size: 479.7 kB
Security for sharpit 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for sharpit-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.