Stop Guessing localhost Ports with portmap and TypeScript A developer built portmap, an MIT-licensed TypeScript CLI and read-only MCP server that scans a project's declared configuration, optional OS listener data, and environment references to produce a .portmap.json report flagging stale localhost URLs, such as an env reference pointing at a port with no listener (finding PRT-04). The tool's static `declare` command runs without services running, and the author demonstrates it against a checked-in mismatch fixture where a Vite server on port 5173 coexists with unresolved localhost:3000 and localhost:8080 references. The project is at version 0.1.0 with no GitHub release, so the reproducible path pins the source checkout rather than a published package. AI coding agents are very good at remembering common conventions. That is also why they can be very confident and completely wrong about localhost:3000 . One project may expose a Vite frontend on port 5173. Another may run an API on 8080. A Docker mapping can make the host port different from the container port, while an .env.local file may still point to a service that is not running today. The resulting error often looks like CORS, authentication, or a broken API client when the real problem is a stale URL. portmap https://github.com/paladini/portmap is a small MIT-licensed TypeScript CLI and read-only MCP server for this local-development problem. It combines declared configuration, optional OS listener data, and environment references into a .portmap.json report with findings such as PRT-04 for an environment URL that points at a port with no listener. This tutorial uses the current repository configuration and the checked-in mismatch fixture. You will build portmap from source, run a deterministic static scan, inspect the generated findings, and understand where the tool deliberately stops. git clone https://github.com/paladini/portmap.git cd portmap npm ci npm run build node dist/cli.js declare fixtures/mismatch --json The output identifies a declared Vite port and two unresolved localhost environment URLs. That gives an agent or a developer concrete evidence before changing application code. You need: engines.node as =20 . declare command does not need a running service. The repository is public, non-archived, non-forked, and uses the MIT license. At the time of writing, the repository reports version 0.1.0 in package.json and does not provide a GitHub release, so the reproducible path below pins the source checkout by its current default branch rather than claiming a released package. Clone the project and install the lockfile exactly: git clone https://github.com/paladini/portmap.git cd portmap npm ci npm run build The build runs TypeScript with the repository's tsconfig.json and produces the dist directory. The CLI entry point is dist/cli.js ; the package metadata also exposes a portmap binary when the project is installed as a package. The current README documents npm ci , npm run build , and node dist/cli.js as the source-based installation path. It also documents npx portmap as a future npm option, so do not use the latter as if a published npm package were already verified. The safest first check is declare . It reads project files without requiring the application's processes to be running: node dist/cli.js declare fixtures/mismatch --json The fixture is intentionally broken. Its configuration declares a Vite server on port 5173, while .env.local contains references to http://localhost:3000 and http://localhost:8080 . Because no matching service is declared for those references, the report contains unresolved environment edges. The important part of the JSON is the relationship between the declared service, references, and findings: { "services": { "id": "vite", "declared": { "port": 5173, "sources": "vite.config.ts:server.port" }, "status": "down" } , "findings": { "id": "PRT-01", "severity": "error" }, { "id": "PRT-04", "severity": "error", "file": ".env.local:1" } } The complete report includes the second PRT-04 for the port 8080 reference. The fixture has no listener data because this static command does not inspect the operating system. Run declare against your application first: node /path/to/portmap/dist/cli.js declare /path/to/your-app --json If the report is useful as persistent agent context, write it to the project root: node /path/to/portmap/dist/cli.js declare /path/to/your-app --write This creates .portmap.json . The repository's schema documentation describes it as the artifact agents can read instead of guessing a URL. Whether you commit it is a project decision: committing it can make a known local topology available to future sessions, while ignoring it avoids storing machine-specific runtime data. When processes are running, the full command adds an OS listener scan: node /path/to/portmap/dist/cli.js scan /path/to/your-app --json The full scan can report declared ports that are not listening, listeners without declared configuration, mismatched Docker host and container ports, and environment references that do not resolve. The README also documents workspace for projects in sibling folders and listen for inspecting listeners directly. portmap includes a read-only MCP stdio server. After building, the command is: node /path/to/portmap/dist/cli.js mcp For Cursor or Claude Code, the documented configuration points the MCP command at the built CLI: { "mcpServers": { "portmap": { "command": "node", "args": "/path/to/portmap/dist/cli.js", "mcp" } } } The exposed tools are intentionally narrow: retrieve a full report, get a smaller service graph, resolve a URL, or filter findings. The server does not start or stop services, modify application files, or infer ports with an LLM. The core design separates three kinds of evidence: That separation makes a failure actionable. PRT-04 means an environment URL points at a port with no listener, while PRT-02 warns about a listener without a corresponding declaration. Those are different repair paths, even though both can surface as a browser network error. The CLI can turn findings into a non-zero exit status. For example, this command fails when at least one error remains: node dist/cli.js scan . --min-findings 1 --min-severity error --json Use this as a local-development guard, not as production monitoring. The project is designed for local service topology and explicitly does not provide uptime or production health checks. There are several important boundaries: PRT-02 warning or remain undiscovered. ports: patterns, not every Compose feature. The tool reads local configuration and socket information. It is not a secret manager, a process supervisor, or a security guarantee. Keep .env values out of reports and repositories when they contain credentials; the useful signal is the referenced host and port, not the secret itself. From the clean checkout, verify the source path and fixture again: npm ci npm run build node dist/cli.js declare fixtures/mismatch --json mismatch-report.json Confirm that the JSON parses and contains PRT-01 plus the unresolved PRT-04 findings. For your own project, change one environment URL to the actual declared port, rerun the command, and compare the findings. That gives you a small, reproducible experiment instead of relying on whether an agent happens to guess correctly. The repository also includes npm test , demo:mismatch , and demo:workspace scripts. In a clean Windows checkout, the build and static fixture declaration are useful smoke checks; if a runtime listener check behaves differently on your platform, treat that as an environment-specific result and inspect the generated report rather than assuming the listener model is universal. No. It reports topology. It does not start, stop, or restart services. No. The repository describes the scanner and MCP server as deterministic and zero-LLM. The MCP interface is for letting an agent consume evidence, not for outsourcing discovery to a model. The README lists npm installation as a future option. The verified current path is cloning the repository, running npm ci , and building locally. .portmap.json be committed? Only if the values are stable and safe to share. A committed static report can help future agents, but machine-specific listeners and environment details may be inappropriate for a repository. The practical lesson is simple: make local topology an input to debugging. Build portmap from the documented source path, run declare before starting a long investigation, and let the report show which URLs are declared, which are actually reachable, and which assumptions are unsupported. If you try this in a monorepo, which mismatch would be most valuable to catch first: stale environment URLs, Docker port remapping, or cross-workspace references? AI assistance disclosure: AI assistance was used to organize and edit this tutorial. Repository behavior, commands, metadata, limitations, and verification claims were checked against the current public source and the clean checkout described above.