recon runs as a local stdio MCP server so MCP-compatible AI tools can call it
directly, with no API keys and no glue code. The default pip install recon-tool
includes the MCP server.
Works with Claude Desktop, Cursor, VS Code + Copilot, ChatGPT, or any other MCP client.
Warning
recon mcp runs with the privileges of the calling user or editor process.
Treat connected AI agents as untrusted input: prompt injection, tool
poisoning, and parameter tampering are possible. Start with manual approvals,
configure permissions through the client's documented controls, and prefer
an isolated workspace or container for production agent use.
The local stdio server is the default. An entirely optional, operator-owned remote Streamable HTTP container and Cloud Run reference are documented in the cloud deployment plan. They are for users who benefit from shared access or bounded scale-out; they are not required for any local workflow, and the project does not operate a hosted endpoint. The remote materials are a draft framework intended to be directionally useful. They have local artifact checks but are not yet provider-validated or production-ready.
- Install recon:
pip install recon-toolChoose one installation form, then use recon mcp install to generate the
client-specific server stanza:
| Form | Command | Tradeoff |
|---|---|---|
| pip | pip install recon-tool |
Direct installation with the lowest repeat-startup overhead. |
| uv tool | uv tool install recon-tool |
Isolated persistent tool environment. |
| uvx | uvx --from recon-tool recon mcp |
No persistent recon installation; the first run may download and cache the package. |
For persisted client configuration, prefer the installer-generated,
interpreter-bound launcher over copying the uvx command. It binds the client
to the recon installation you just verified and removes the working directory
from Python's import path before loading recon.
-
Wire the MCP server into your client. Two ways:
a. One-shot install (recommended). Let recon write the right config block at the right per-OS path:
recon mcp install --client=claude-desktop # supported: claude-desktop, claude-code, cursor, vscode, windsurf, kiro recon mcp install --client=cursor --dry-run # preview without writing recon mcp install --client=cursor --scope=workspace # project-local instead of user-global
The command merges the recon stanza into your existing config without touching sibling MCP servers. It refuses an existing
reconvalue that is not an object unless--forceis explicit. Client-supported custom fields survive a--forcererun; canonical launcher fields are refreshed, and unsupported legacy approval fields are removed for Claude Code and VS Code. Writes are atomic (sibling tempfile plusos.replace), so a partial-write failure leaves the original config intact. When the config path is a symlink, the installer updates its resolved target and preserves the link.b. Reviewed manual install. Generate the exact interpreter-bound block for the client, inspect it, and copy that preview only if automated writing is unavailable:
recon mcp install --client=cursor --dry-run
The preview includes the client-specific top-level key, including
serversfor VS Code andmcpServersfor other supported clients. Merge that stanza without replacing sibling servers. The generated launcher avoids PATH ambiguity and strips the workspace from the import path. Do not replace it with a barereconcommand or hand-writepython -m recon_tool.serverin an untrusted working directory. -
Ask your AI tool something like: "Run a recon lookup on gamma.invalid and summarize the observed public configuration, naming anything unresolved."
Example multi-step prompt for deeper analysis:
"Look up alpha.invalid with format=json and explain=true. Then run assess_exposure and find_hardening_gaps. Finally, simulate_hardening with DMARC reject and MTA-STS enforce applied, and explain the model-bound public-evidence index change without treating it as an overall security verdict."
recon mcp prints a warning banner to stderr before the stdio transport
starts so JSON-RPC framing stays clean on stdout. The entrypoint warns about
the server's local privilege level and the need for manual approvals:
================================================================================
recon MCP Server vX.Y.Z
WARNING: This server runs with the privileges of the calling user.
Treat connected AI agents as untrusted input.
Start with manual approvals; only enable auto-approval for tools you
deliberately trust.
================================================================================
recon intentionally does not add a separate "safe mode" or "full auto" CLI flag here. Approval policy belongs in the MCP client config, and the safest default is to require confirmation through the client's permission system.
If you launch recon mcp (or python -m recon_tool.server) by hand in a shell, recon detects that stdin is a TTY and prints a "this is not a REPL" panel before exiting. The MCP server is meant to be spawned by an MCP client over stdio JSON-RPC. Running it interactively used to surface a Pydantic JSON-parse traceback the first time you pressed Enter; the panel replaces that.
If you genuinely need to drive the JSON-RPC loop by hand (e.g. piping crafted requests for debugging), set RECON_MCP_FORCE_STDIO=1 (case-insensitive: 1, true, yes, on all enable the bypass) before launching.
Importing the server module does not install a shared logging handler. The actual stdio runtime supplies a temporary stderr handler only when no host logging configuration exists, then removes it on exit. If the loop fails unexpectedly, stderr receives one bounded control-free line with the exception type instead of raw multiline exception text.
Start with lookup_tenant for a single-domain summary. Use format="json"
with explain=true when provenance fields are required. Use
analyze_posture for categorized public observations, and
compare_postures or cluster_verification_tokens only for an
operator-supplied domain set. Graph export, posterior inspection, hypothesis
testing, simulation, catalog mutation, and fingerprint discovery are specialist
workflows; a first-time user does not need them for a normal lookup.
The domain_report MCP prompt applies the same domain validator as the tools
before placing a canonical apex in prompt text. Malformed prompt arguments fail
instead of being reflected into an instruction template.
| Tool | Network calls? | What it does | Parameters |
|---|---|---|---|
lookup_tenant |
Cache first; may resolve | Compact agent-readable summary by default; format="json" returns the detailed record as serialized JSON text. Collection uses public DNS and unauthenticated endpoints: authoritative DNS may observe resolver traffic, MTA-STS is the only default target-owned HTTP request, and Google CSE / BIMI certificate probes require explicit opt-in. When format="json" and explain=true, the serialized record includes an explanation_dag: evidence occurrences link to matching slug and rule nodes, which link to signal, insight, observation, or confidence terminals. Schema-version-1 provenance_complete and disconnected_terminals report graph reachability. exact_provenance_complete and lineage_disconnected_terminals report whether every terminal has an explicit generation-time evidence-to-rule path. Flat records and terminal nodes carry lineage_status (exact, exact_rule_only, reconstructed, or unsupported); exact and exact-rule-only records carry one emitter ID in lineage_rule_ids. |
domain, format: text / json / markdown, explain: bool |
analyze_posture |
Cache first; may resolve | Neutral posture observations across email, identity, infrastructure. Accepts an optional profile argument: one of fintech, healthcare, saas-b2b, high-value-target, public-sector, higher-ed, or a custom name from ~/.recon/profiles/. |
domain, explain: bool, profile: str (optional) |
cluster_verification_tokens |
Cache first; may resolve each domain | Report exact TXT site-verification token reuse. Shared administration, copied configuration, managed service, and stale residue remain compatible explanations; reuse does not establish ownership or current use. Optional peer caps report omitted counts for compact agent output. | domains: array of domain strings, peer_limit_per_domain (0 means raw) |
assess_exposure |
Cache first; may resolve | Model-bound public-evidence index on a 0-100 compatibility scale, with an exact-evidence floor, bounded ceiling, complete weighted component ledger, and email, identity, and infrastructure sections. The current component model assigns at most 90 points. It summarizes only collected public observables and is not an overall security score (see correlation.md for the inference model). | domain |
find_hardening_gaps |
Cache first; may resolve | Categorized public-configuration review prompts with exact generator IDs, basis states, typed predicates, bounded scopes, retained evidence, and "Consider" guidance. It is not an overall assessment (see correlation.md). | domain |
compare_postures |
Cache first; may resolve both domains | Side-by-side comparison of two domains' public configuration evidence, including each namespace's exact exposure-index ledger. Floors and ceilings are not an overall security ranking. | domain_a, domain_b |
chain_lookup |
Yes | Recursively follows every related_domains observation from each ordinary lookup, currently including CT, CNAME, Exchange/identity endpoint, autodiscover, and DKIM tenant-domain breadcrumbs. Each queued name can trigger another full public-metadata lookup. Co-occurrence does not establish ownership or a corporate relationship. Optional result caps report omitted counts for compact agent output while preserving raw JSON as the default. |
domain, depth (1-3), result_limit (0 means raw) |
discover_fingerprint_candidates |
Cache first; may resolve | Mine unclassified CNAME suffixes for candidates that require independent triage. Surviving heuristics do not establish a third-party service, ownership, current use, or a reusable provider pattern. Pair with the /recon-fingerprint-triage skill and require independent documentation or repeated validation evidence before proposing YAML. |
domain, skip_ct: bool, keep_intra_org: bool, min_count: int |
reload_data |
No | Reload fingerprint, signal, and posture definitions, clear the process lookup cache, and preserve the rate limiter plus ephemeral catalog | none |
get_fingerprints |
No | List loaded fingerprint definitions with slugs, categories, and detection types. Use limit and offset for bounded pages. Definitions are capabilities, not target evidence. |
category (str, optional filter), limit (int, optional page size), offset (int, default 0; used with limit) |
get_signals |
No | List loaded signal definitions with rules, layers, and conditions. Definitions are capabilities, not target evidence. | category, layer (optional filters) |
explain_signal |
No unless domain is provided |
Query a signal's trigger conditions and current state for a domain | signal_name, domain (optional) |
test_hypothesis |
Cache first; may resolve | List observations related to a theory while semantic likelihood remains explicitly unresolved. | domain, hypothesis |
simulate_hardening |
Cache first; may resolve | What-if: re-compute the model-bound public-evidence index with hypothetical fixes. Current and simulated ledgers are returned separately, and changed components are labeled hypothetical_value. This is not a prediction of overall security change (see correlation.md). |
domain, fixes (array) |
inject_ephemeral_fingerprint |
No | Inject a temporary fingerprint into the current server process; it is process-wide, not conversation-scoped | name, slug, category, confidence, detections (array) |
reevaluate_domain |
No | Replace one result in the process lookup cache after replaying retained apex/root TXT, SPF, MX, NS, and CNAME observations against current fingerprints. Owner-qualified detection types require a fresh lookup. | domain |
list_ephemeral_fingerprints |
No | List all ephemeral fingerprints in the current server process | none |
clear_ephemeral_fingerprints |
No | Remove all ephemeral fingerprints from the current server process | none |
get_infrastructure_clusters (v1.8+) |
Cache first; may resolve | Surfaces the CT co-occurrence community-detection report already computed during lookup: algorithm, modularity score, cluster list. Read-only exposure of computed state. Optional member caps report omitted counts for compact agent output. | domain, member_limit_per_cluster (0 means raw) |
export_graph (v1.8+) |
Cache first; may resolve | Companion to get_infrastructure_clusters. Returns the underlying graph as nodes + weighted edges + cluster_assignment for downstream Mermaid / GraphViz / CSV rendering. Optional node and edge caps report omitted counts for compact agent output. |
domain, node_limit (0 means raw), edge_limit (0 means raw) |
get_posteriors (v1.9.0; stable v2.0+) |
Cache first; may resolve | Exposes model-relative Bayesian-network posteriors and evidence-responsive uncertainty bands for the nine high-level claim nodes. Top-level degraded_sources and collection_masked_units preserve the collection failures that were treated as structurally unobserved rather than negative evidence. Read-only exposure of the inference computed during lookup. See correlation.md for the inference model and limits. |
domain |
explain_dag (v1.9.0; stable v2.0+) |
Cache first; may resolve | Renders the Bayesian evidence DAG for a domain as plain text or Graphviz DOT text. Pairs with get_posteriors for full audit-trail inspection. |
domain, output_format: text / dot (default text) |
For assess_exposure, read observability.components before interpreting the
compatibility posture_score. Each of the nine weighted components carries a
stable generator ID, state, typed metadata predicates, observation scope,
awarded and unresolved points, and exact retained evidence. An
observed_value may contribute points; observed_empty records a completed
bounded opportunity; unresolved covers evidence that cannot establish the
modeled state or absence; unavailable names a failed collection opportunity;
and hypothetical_value appears only in simulation output. The index floor is
the sum of awarded points. The ceiling adds unresolved and unavailable modeled
capacity. Neither value is an overall security grade.
For find_hardening_gaps, read observation_state before the prose.
observed_weak_configuration and observed_configuration_inconsistency carry
retained public-record evidence. bounded_non_observation identifies a
successfully collected named scope. unresolved_hideable_state means the
control may exist outside that scope, such as DKIM at an operator-chosen
selector. generator_rule_id, metadata_dependencies, observation_scope,
and evidence make the basis replayable. absence_confirmable remains for
compatibility and must not override the explicit state.
DMARC prompts require the retained record to agree with the derived policy
state. Invalid or ambiguous retained material produces an evidence-backed
review prompt with a distinct basis rather than a record-absence claim. A
missing MTA-STS TXT declaration names only the DNS scope; the HTTP scope appears
only after a valid declaration activated a completed policy request. Simulated
fixes replace the affected proof rows with explicit hypothetical records before
remaining prompts are evaluated.
Exactly four explicit configuration or cache-rewrite tools mutate
caller-visible state in the current server process:
inject_ephemeral_fingerprint, clear_ephemeral_fingerprints, reload_data,
and reevaluate_domain. They do not write to disk or trigger new network calls
on their own. All other lookup and analysis tools are read-only, although a
cache miss may perform the documented public-metadata collection. For
lookup_tenant, structured provenance requires format="json" with
explain=true; analyze_posture(explain=true) returns flat observation
explanations rather than an explanation_dag. Catalog tools (get_fingerprints,
get_signals, and MCP resources) do not call the network. Definitions describe
matching and derivation capabilities, not target evidence.
Domain-analysis tools are cache-first and may resolve the domain when no fresh
cache entry exists. The server includes a bounded TTL cache (120s) and
per-domain rate limiting.
Aligned with the MCP 2025-11-25 specification, the data tools return their
results as navigable structuredContent with a generated per-tool
outputSchema, so a client can consume and validate fields directly rather than
re-parsing a JSON string. For backward compatibility each result also carries the
same payload as serialized-JSON text content, so a text-only consumer still
works. The structured tools are the catalog, posture, graph, ephemeral, and
inference tools (get_fingerprints, get_signals, explain_signal,
assess_exposure, find_hardening_gaps, compare_postures, analyze_posture,
discover_fingerprint_candidates, test_hypothesis, simulate_hardening,
cluster_verification_tokens, get_infrastructure_clusters, export_graph,
get_posteriors, and the ephemeral-fingerprint tools).
The graph, chain, and batch-correlation tools preserve raw access by default.
Passing result_limit, peer_limit_per_domain, member_limit_per_cluster,
node_limit, or edge_limit asks for a compact payload; the response includes
omitted counts, a deterministic selection_rule, and a raw_request pointer
so an agent can decide whether to request the raw result.
Compatibility with the final MCP 2026-07-28 release is tracked in
mcp-2026-07-28-readiness.md. The dated isolated
matrix passes on both stable SDK v1.28.1 and stable SDK v2.0.0 using the same
local stdio server. Production adopted mcp>=2.0.0,<3 on 2026-07-31;
v1.28.1 remains the blocking rollback pin. Stable v2 passes recon's doctor,
discovery, schema, resource, ordering, and compatibility gates. Under v2, recon explicitly
uses conservative ttlMs=0, cacheScope=private hints for all six cacheable
methods rather than promising freshness it cannot establish.
The no-network catalog list tools started the precise-schema Phase 2:
get_fingerprints advertises a FingerprintSummary item schema and
get_signals advertises SignalSummary plus nested SignalMetadataSummary.
explain_signal now advertises static-definition and domain-evaluation variants
with SignalTriggerConditions and SignalEvidenceSummary. The simple
ephemeral-fingerprint process tools now advertise
EphemeralInjectionResult, EphemeralFingerprintSummary, and
EphemeralClearResult. The graph data tools now advertise
VerificationTokenClusterResult, InfrastructureClusterEnvelope, and
GraphExportEnvelope. The compact agent-facing posture helpers now advertise
HypothesisAssessmentResult and HardeningSimulationResult. get_posteriors
now advertises PosteriorBlockResult, PosteriorNodeSummary, and
UnitCounterfactualSummary. The exposure report tools now advertise
ExposureAssessmentResult, GapReportResult, and PostureComparisonResult
with nested evidence and posture record definitions.
discover_fingerprint_candidates now advertises FingerprintCandidate plus
nested FingerprintCandidateSample. analyze_posture now advertises its
list, profiled, explained, and profiled-explained result variants.
reevaluate_domain now advertises the full cache-only lookup record as
LookupResult, with nested definitions for evidence, posteriors, certificate
summaries, infrastructure clusters, surface attributions, chain motifs, and
conflicts. The CLI --json v2.0 schema is a separate locked contract.
The narrative tools render prose or DOT and intentionally return text:
lookup_tenant (its format selects text / json / markdown),
explain_dag (Rich tree or Graphviz DOT), chain_lookup, and reload_data.
For lookup_tenant, format="text" is a compact agent-readable summary and
now includes a Subdomain surface: provider-count line when CNAME-chain
attributions exist. format="json" is the detailed machine path: it carries the
full surface_attributions array, while reevaluate_domain exposes the same
cache-only lookup record with a typed LookupResult output schema.
Error signaling follows the tool's stable return family. Typed data tools raise
ToolError for request-level validation and lookup failures, which the MCP
transport reports with isError: true; multi-domain tools may instead include
typed per-domain errors inside an otherwise successful aggregate result.
Narrative tools preserve text results for handled failures where documented:
lookup_tenant and explain_dag return validation, rate-limit, no-data, and
internal-error text; only a resolver no_data error is rendered as
No information found for .... Timeouts and total source failure retain
truthful failure text. chain_lookup does the same except that a negative
result_limit raises ToolError; unexpected reload_data exceptions propagate
as protocol tool errors. Consumers must handle both ordinary narrative failure
text and isError: true results without treating every handled failure as
absence.
Each tool carries explicit readOnlyHint, destructiveHint, idempotentHint,
and openWorldHint annotations so a consuming agent can reason about what is
safe to auto-approve. The complete 22-tool matrix is enforced in code and kept
in sync with this section by tests/test_mcp_tool_annotations.py.
Explicit state-mutation tools change caller-visible, process-wide in-memory state and are the ones to keep manual (or approve only when you understand the effect):
inject_ephemeral_fingerprint: adds a temporary fingerprint to the running process catalog.clear_ephemeral_fingerprints: removes all ephemeral fingerprints from the process catalog and removes their projections from cached results.reload_data: re-reads the fingerprint, signal, and posture catalogs from disk into the running server and clears the process lookup cache. It preserves the rate limiter and current ephemeral catalog.reevaluate_domain: replaces the cached merged result for one domain after applying the process's current fingerprint catalog. It makes no network request, but later tools observe the refreshed cached result.
clear_ephemeral_fingerprints, reload_data, and reevaluate_domain advertise
destructiveHint=true because they remove or replace current process state;
inject_ephemeral_fingerprint is additive and advertises
destructiveHint=false. All four advertise openWorldHint=false because none
interacts with an external entity.
Every other tool is read-only (readOnlyHint=true): it has no externally
visible side effect beyond returning observations. The server can still update
internal cache, rate-limit, and diagnostic bookkeeping. Read-only also does not
mean "no network": the
domain-analysis tools are cache-first and may make public-metadata DNS, CT,
identity-endpoint, and MTA-STS requests when no fresh cache entry exists (the
"Network calls?" column above says which). DNS can be visible to authoritative
infrastructure, MTA-STS is the one default target-owned HTTP request, and CSE or
BIMI certificate probes require explicit opt-in. Treat annotations as hints and
keep client permission policy explicit after reviewing each tool's network
boundary.
get_posteriors returns a point posterior under the committed manually encoded
network and an 80% evidence-responsive uncertainty band. The band uses the
model mean and hand-set effective display mass to parameterize a Beta shape,
with a mean-centered fallback at boundaries. It is not a Bayesian credible
interval, frequentist confidence interval, or calibrated probability. A
consuming agent must keep both values model-relative and inspect their evidence
path. Report the claim unresolved rather than collapsing it to the point value
when sparse=true, the band straddles the model threshold, or evidence_used
is empty and no unit_counterfactuals entry records observed="absent".
Absence is not disproof: recon
treats a hideable signal that did not fire as no evidence, never as evidence
the technology is absent. This is an explicit conservative policy, not a result
derived from MNAR. A low or sparse model score therefore cannot establish real-
world absence. A declarative node can have an empty evidence_used list while
its counterfactuals record a successfully observed public absence; report only
that defined public fact, not private-state absence.
Read top-level degraded_sources before interpreting a non-fire.
collection_masked_units names the Bayesian dependency units removed because
their collector channel was unavailable. Such a unit contributed neither fired
evidence nor declarative absence; it is not an observed negative.
The injected server instructions carry this same guidance for the agent;
tests/test_posterior_reading_guidance.py keeps it from regressing.
recon exposes five MCP resources so agents can browse "what can this tool detect?", "what shape is the output?", and "what local surfaces exist?" without spending a tool invocation on introspection:
| URI | Content |
|---|---|
recon://fingerprints |
Full SaaS fingerprint catalog (slug, name, category, confidence, match_mode, detection_count, ...) |
recon://signals |
Derived intelligence signals with candidate slugs, min_matches, contradicts/requires relationships, and positive-when-absent inversions |
recon://profiles |
Built-in posture profile lenses (category boosts, signal boosts, focus categories) |
recon://schema |
The JSON-output contract as a JSON Schema (the same document as docs/recon-schema.json), so an agent can self-describe the shape of recon <domain> --json (plus the batch / delta modes in its $defs) without an external fetch. The contract version is in the schema's own description. |
recon://surface-inventory |
Generated, non-contractual local map of the CLI, MCP tools, MCP resources, JSON schema, agent integration surfaces, and maintainer-loop context packet. It is the same inventory as docs/surface-inventory.json. |
The catalog resources return deterministic JSON sourced from the already-loaded YAML catalogs; definitions describe matching and derivation capabilities, not evidence about any target. recon://schema returns the bundled schema document; recon://surface-inventory returns the bundled generated inventory. No network calls. Changes to custom ~/.recon/fingerprints/ or ~/.recon/signals.yaml require calling reload_data to take effect. The surface inventory is for local discovery and drift checks, not compatibility promises; see ADR-0007 for the promotion gate.
How a client exposes resources varies; use its resource browser or resource-read action before spending a domain-analysis tool call when you only need capability context.
Choose a posture profile without guessing.
- Read
recon://profiles. - Compare the target type the operator provided with each profile's
descriptionandfocus_categories. - Pass
profiletoanalyze_postureonly when the target type clearly matches a listed profile. If it does not, omitprofile.
Check whether recon has a published fingerprint for a service.
- For quick browsing, start with
get_fingerprints(limit=20, offset=0). Add acategoryfilter when the relevant category is known. - A first page cannot establish that the catalog has no match. For an
exhaustive check, either read the full
recon://fingerprintsresource or continue with offsets 20, 40, and so on until a page has fewer than 20 entries. Omitcategoryunless the requested absence is category-scoped. - Filter entries by
slug,name,category, ordetection_types. - Only after the exhaustive check may you say that no published fingerprint was found. Do not infer that the service is absent from a target domain.
Explain a derived signal before or after a lookup.
- Read
recon://signals. - Find the signal by
nameor by a slug incandidates. - Use
min_matches,contradicts,requires_signals, andpositive_when_absentto explain what evidence can drive the signal. Keep the language hedged because signals are rule-based observations.
Validate or inspect JSON shape offline.
- Read
recon://schema. - Use the top-level schema for
lookup_tenant(format="json")orrecon <domain> --json. - Use
$defsfor batch, summary, and delta shapes. This is a local resource read; it does not require fetching docs from the network.
Discover local surfaces before choosing a command or tool.
- Read
recon://surface-inventorywhen a client needs a local map of CLI commands, MCP tools, MCP resources, JSON-schema fields, or agent guidance files. - Use
mcp.tools[]for tool names, parameters, annotations, and advertised output schemas. - Use
mcp.resources[]for local resource URIs to read before spending a domain-analysis tool call. Treat the inventory fields as generated discovery context, not as a stable runtime contract.
Every TenantInfo result carries two ISO-8601 UTC fields:
resolved_at: when the live resolution produced this result. Always set.cached_at: when the on-disk cache entry was written. Set only when the result was served from~/.recon/cache/.
Agents can compare the two to decide whether to re-resolve. On a fresh lookup, cached_at is null. On a cache hit, resolved_at is preserved from the original resolution so it reflects when the data was produced, not just when the cache entry was last written.
Ephemeral fingerprints let AI agents inject temporary detection patterns at runtime. They live in memory only, are process-wide within the current server process rather than conversation-scoped, and are validated through the same regex/ReDoS checks as built-in fingerprints.
To keep long-running MCP server processes available under prompt injection or abusive
tool calls, ephemeral storage is quota-bounded: at most 100 ephemeral
fingerprints, at most 20 detections on a single injected fingerprint, and at
most 500 total ephemeral detections per process. Oversized injections return a
MCP tool error (isError: true); use clear_ephemeral_fingerprints or restart
the server to reset the process quota.
- Look up a domain with
lookup_tenant(caches DNS data). - Inject an ephemeral fingerprint with
inject_ephemeral_fingerprint. - Re-evaluate the domain with
reevaluate_domain(zero network calls, uses cached data). - List active ephemeral fingerprints with
list_ephemeral_fingerprints. - Clear all ephemeral fingerprints with
clear_ephemeral_fingerprintswhen done.
Cached re-evaluation is intentionally narrower than fresh collection. It can
replay txt, spf, mx, ns, and apex/root cname rules from retained DNS
observations. It cannot replay owner-qualified cname_target, subdomain_txt,
caa, srv, or dmarc_rua rules; reevaluate_domain returns a tool error when
any active ephemeral fingerprint uses one of those types. To test such a rule,
call reload_data to clear the process lookup cache while retaining
the ephemeral catalog, then run lookup_tenant again. That fresh lookup uses
the normal documented network boundary and is not a zero-network operation.
Agent: "Inject an ephemeral fingerprint for Synthetic Beta's internal platform."
→ inject_ephemeral_fingerprint(
name="Synthetic Beta Platform",
slug="beta-platform",
category="Internal",
confidence="medium",
detections=[{"type": "txt", "pattern": "beta-platform-verify="}]
)
← {"status": "ok", "name": "Synthetic Beta Platform", "slug": "beta-platform", "detections_accepted": 1}
Agent: "Now re-evaluate alpha.invalid to see if they use Synthetic Beta Platform."
→ reevaluate_domain(domain="alpha.invalid")
← Updated TenantInfo JSON (includes Synthetic Beta Platform if TXT record matches)
→ list_ephemeral_fingerprints()
← [{"name": "Synthetic Beta Platform", "slug": "beta-platform", "category": "Internal", "confidence": "medium", "detection_count": 1}]
→ clear_ephemeral_fingerprints()
← {"status": "ok", "removed": 1}
Ephemeral fingerprints are deliberately local-only and process-scoped. They change matching rules for the current process; they do not tune Bayesian priors or persist learning. They support cache-only hypothesis checks without writing to disk or sharing data and disappear when the server exits.
| Client | Config file location |
|---|---|
| Claude Code | Use the bundled plugin at agents/claude-code/; wires up MCP and ships a skill in one install |
| Claude Desktop | ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) |
| Cursor | .cursor/mcp.json in your project or ~/.cursor/mcp.json globally |
| VS Code + Copilot | .vscode/mcp.json in a workspace, or mcp.json in the active user profile |
| Windsurf | ~/.codeium/windsurf/mcp_config.json |
| Kiro (workspace) | .kiro/settings/mcp.json |
| Kiro (global) | ~/.kiro/settings/mcp.json |
Per-agent install scaffolds (config snippets + guidance templates) live under agents/, one folder per client.
One format note: VS Code maps server names under a top-level servers key, not
mcpServers, and its current stdio schema requires "type": "stdio" (see the
VS Code MCP configuration reference,
reviewed 2026-07-13). recon mcp install --client=vscode writes the workspace
form by default. VS Code also supports a user-profile file; open it with MCP:
Open User Configuration and pass that path with --config-path when desired.
VS Code does not define autoApprove in this file format.
GUI MCP clients (Claude Desktop, Windsurf, Cursor, VS Code) typically don't inherit your shell's PATH. Run recon mcp install --client=<name> --force from the Python environment where recon is installed. The installer always writes that interpreter's absolute path plus a sys.path-stripping launcher, so the client uses the same recon installation without depending on shell PATH. If you edit by hand, prefer the absolute path to the recon script (run which recon / where recon to find it):
{
"mcpServers": {
"recon": {
"command": "/absolute/path/to/recon",
"args": ["mcp"]
}
}
}The shorter python -m recon_tool.server form is acceptable only from a trusted working directory. Python imports through cwd before recon can run its server-side guard, which is why the installer uses the -c launcher instead. Approval and trust fields are client-specific; do not copy an autoApprove property into a client schema that does not define it.
Three complementary checks. Run them in this order. The first two validate the installed server; the third validates that the named client was told about it.
recon doctor --mcp: static diagnostic. Confirms the MCP dependencies are installed, reports the exact SDK version and generation, loads the server module, requires server instructions, and enumerates the canonical tool and resource registrations through the public server API. It exits non-zero if any required registration is absent. It also reports PATH state and prints a reference config. Preferrecon mcp install --client=<name>for an actual client because the installer safely merges the recon block without replacing sibling servers or client-specific fields.recon mcp doctor: live local stdio diagnostic. Spawns the recon MCP server through the running interpreter, opens a realstdio_clientplusClientSession, and runs the discovery flow supported by the installed Python MCP SDK. It requires the anchor tools (lookup_tenant,analyze_posture,assess_exposure,find_hardening_gaps,chain_lookup), lists all five canonical local resources, and reads each one. Every read must return one matchingapplication/jsontext item whose payload is a JSON object. Catalog counts, lists, and identifiers must agree; fingerprint detection summaries must be populated; and the schema and surface inventory must retain their identifying contract structure. These resource reads are local and make no target network request. Stable v1 usesinitialize; stable v2 usesserver/discoverand validates complete-result cache metadata for discovery, listing, and reads. A failure retains completed check rows and names the failed protocol phase. Spawned-server stderr is limited to its trailing twelve lines, and resource payload text is never copied into failure detail. The whole run has a 30-second timeout. This command does not inspect any client configuration.recon doctor --client=<name>: reads the config file the named client actually loads (claude-code,claude-desktop,cursor,vscode,windsurf,kiro) and reports whether its recon server entry is present and well-formed. For Claude Code it also looks under the project-nestedprojects[...].mcpServers.reconshape thatclaude mcp addwrites, and notes that a plugin install keeps its config inside the plugin rather than in~/.claude.json. A missingargsfield, an empty list, or a list containing any non-string value is a failing diagnostic. The check exits non-zero when no usable stanza is found, so it is suitable for setup automation.
If all three pass but tools still do not appear, continue with the client-side checks below.
This is a common failure mode, and it usually is not a broken config. A healthy server the client never re-read looks identical to one wired up correctly, right until the tools fail to appear. If the checks above pass but the tools still do not show up:
- Run
/mcpin the client. It lists the connected MCP servers and any startup error. Ifreconis not listed, the config was not picked up or the server crashed on spawn. - Look for the right tool-name prefix. Local stdio tools appear as
mcp__recon__*, notmcp__claude_ai_*. Searching the tool list for the claude.ai naming pattern will not find them, which can read as "the install failed" when it did not. - Restart means a full application quit. Closing a chat window and opening a new one in the same process does not re-spawn MCP servers. Quit the application entirely (Alt+F4 on Windows, Cmd+Q on macOS) and relaunch.
- Check which path you installed by.
recon mcp install --client=claude-codewrites a user-scoped stanza into~/.claude.json. The Claude Code plugin instead keeps its config inside the plugin, and the plugin has to be enabled, not just present. The two paths are independent;recon doctor --clientreads the former, not the latter.
Server registration and tool approval are separate. Claude Code plugin servers
start automatically when the plugin is enabled, but their tool calls follow the
same permission system as user-configured servers. The plugin .mcp.json,
Claude Code config, and VS Code mcp.json do not define a generic
autoApprove field. Use the client's documented permission rules when you want
an allowlist, and keep the four process-state tools listed under
Available Tools subject to deliberate review. Installing
both the plugin and a user-level server creates two registrations; choose one
path unless that duplication is intentional. See the
Claude Code MCP reference, reviewed
2026-07-13.