An AI coding agent can write a perfectly reasonable request to http://localhost:3000 and still be completely wrong. Next.js often uses that port, but Vite commonly uses 5173, an API might use 8080, and Docker can expose a host port that differs from the container port.
The resulting failure is deceptively expensive. You investigate CORS, authentication, or a broken endpoint when the actual problem is that no process is listening at the URL the agent invented.
This tutorial uses portmap to make that local topology explicit. You will build the tool from its stable v0.1.0 tag, run a static scan against a reproducible fixture, inspect the machine-readable .portmap.json contract, and then see where runtime and MCP modes fit.
TL;DR
portmap is a deterministic CLI and read-only MCP server for local development. It compares ports declared in project files with operating-system listeners and localhost URLs found in environment files. The result is a .portmap.json report plus findings such as PRT-01 for a declared port that is down and PRT-04 for an environment URL with no listener.
It does not start processes, manage containers, monitor production, or ask an LLM to infer the answer.
Prerequisites
You need:
- Node.js 20 or newer. The package declares
node >=20. - npm and Git.
- A local project, or the repository fixtures used below.
- A terminal that can read the project files and local socket table.
The project is MIT licensed. The commands below target the tagged v0.1.0 source rather than an unreleased default-branch change. Check the package metadata and license if you are packaging it for a team.
Install the stable source
The README currently documents a source checkout. Pinning the tag makes this tutorial reproducible:
git clone --branch v0.1.0 https://github.com/paladini/portmap.git
cd portmap
npm ci
npm run build
The package exposes a portmap binary after publishing, but the repository workflow can run the compiled CLI directly:
node dist/cli.js --help
The main commands are:
scan [path] Static configuration plus OS listeners
declare [path] Static configuration only
listen List OS listeners
workspace [dir] Reconcile sibling projects
mcp Start the read-only MCP server
Use declare when you want a safe configuration check before starting services. Use scan when you also want to compare the declaration with processes currently listening on the machine.
Run a reproducible mismatch check
The repository includes a fixture where a Vite frontend declares port 5173, while environment variables point at 3000 and 8080. No runtime process is required for this first check:
node dist/cli.js declare fixtures/mismatch --json
The report has schema portmap-v1. Its important parts look like this:
{
"services": [
{
"id": "vite",
"declared": { "port": 5173 },
"actual": null,
"status": "down"
}
],
"references": [
{
"envKey": "VITE_API_URL",
"value": "http://localhost:8080",
"targetPort": 8080,
"status": "unresolved"
}
]
}
The fixture reports three errors:
-
PRT-01: the declared Vite port is not listening. -
PRT-04:NEXT_PUBLIC_API_URLpoints atlocalhost:3000with no listener. -
PRT-04:VITE_API_URLpoints atlocalhost:8080with no listener.
That is a more useful answer than asking an agent to try ports until one responds. The mismatch fixture and examples guide contain the source configuration behind this result.
Write an artifact for the next agent session
Once the report is useful, write it at the project root:
node dist/cli.js scan . --write
This creates .portmap.json. A coding agent or a human can read references[].value instead of assuming that the frontend API lives at localhost:3000.
For a check that should fail CI when errors exist, use the threshold flags:
node dist/cli.js declare . --min-findings 1 --min-severity error --quiet
The command exits with status 1 when at least one error finding meets the threshold. This turns local topology drift into a visible check without pretending that portmap is a production monitor.
Understand the report
The report deliberately keeps three facts separate:
-
declaredrecords what project files say should run. -
actualrecords what the runtime scan can attribute to a local listener. -
referencesrecords environment variables such asVITE_API_URLand the port they target.
The schema documentation also defines edges, which connect a frontend reference to a service, and findings, which provide stable PRT-* IDs, severity, a message, and a suggested fix.
This separation matters when a dev server changes ports. PRT-05 means something is listening, but not where configuration says it should be. That is different from PRT-01, where the declared service has no listener at all. Docker mappings receive their own PRT-06 warning because 8080:3000 means the host and container sides have different responsibilities.
Scan sibling projects as a workspace
A single repository scan cannot always resolve a frontend that calls an API in a neighboring folder. The fixture in fixtures/workspace models that situation:
workspace/
api/ -> PORT=8080
web/ -> Vite on 5173, VITE_API_URL=http://localhost:8080
Run the workspace command from the repository root:
node dist/cli.js workspace fixtures/workspace
The workspace report merges project reports and can resolve the web project reference against the API project declaration. This is the right scope when your local development folder contains multiple repositories that form one application.
Connect an AI client through MCP
The repository also includes a read-only MCP server. After building, configure a client with the compiled CLI and the mcp command:
{
"mcpServers": {
"portmap": {
"command": "node",
"args": ["/absolute/path/to/portmap/dist/cli.js", "mcp"]
}
}
}
The documented tools are portmap_scan, portmap_graph, portmap_resolve_url, and portmap_findings. A useful agent instruction is simple: resolve a project URL from portmap before writing a curl or fetch call.
MCP does not make the result magical. The server still reads local files and the local process table. Give the client access only to directories that the user expects it to inspect.
Failure modes and security boundaries
Portmap is a diagnostic input, not a repair engine. It will not start or stop dev servers, change environment files, or alter Docker configuration.
Its runtime attribution is heuristic. A process may be listening for another project, and PID-to-repository attribution can be low confidence. WSL and Docker networking can also make a listener inside a container differ from what the host sees.
Static discovery has limits too. Runtime-only ports hardcoded in JavaScript may not be declared, and the current YAML compose support handles common ports patterns rather than every Compose feature. The project documents a preference for false negatives over noisy false positives.
The security policy is worth reading before scanning untrusted directories. Portmap reads the filesystem and OS process information and says it does not send data over the network, but malicious project layouts remain part of its threat model. Keep dependencies pinned, review the source you run, and do not treat a local topology report as a security guarantee.
FAQ
Does portmap require an LLM?
No. Its README describes filesystem and socket-table discovery with deterministic reconciliation. MCP is only the interface an agent can use to request the report.
Should I use scan or declare?
Use declare before services are running or in a static CI check. Use scan when the comparison with current OS listeners is the point of the check.
Does it replace a process manager?
No. It reports topology. The project explicitly points to separate lifecycle tools for starting and stopping services.
Can I use it for production uptime?
No. The scope is local development topology, not monitoring or availability measurement.
Takeaway
The most important design choice in portmap is refusing to guess. A project declaration, a local listener, and an environment URL are different observations. Reconciling them into a small, machine-readable report gives both developers and AI agents a concrete next step before they debug the wrong endpoint.
If your project regularly suffers from localhost drift, try declare . --json first. Then decide whether a committed .portmap.json, a runtime scan, a workspace check, or an MCP connection best fits your workflow.
Disclosure: AI assistance was used to organize and edit this tutorial. The commands, versions, examples, limitations, and security notes were checked against the public paladini/portmap v0.1.0 source, documentation, and license.
What is the most useful place in your workflow for a topology check: pre-commit, CI, an MCP tool for agents, or a committed .portmap.json artifact?
Top comments (0)