DEV Community

Fernando Paladini
Fernando Paladini

Posted on

Stop Guessing localhost Ports with portmap and TypeScript

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 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.

TL;DR

git clone https://github.com/paladini/portmap.git
cd portmap
npm ci
npm run build
node dist/cli.js declare fixtures/mismatch --json
Enter fullscreen mode Exit fullscreen mode

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.

Prerequisites

You need:

  • Node.js 20 or newer. The repository declares engines.node as >=20.
  • npm and Git.
  • A Windows, macOS, or Linux development machine for the documented runtime listener support. The static 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.

Build portmap from the repository

Clone the project and install the lockfile exactly:

git clone https://github.com/paladini/portmap.git
cd portmap
npm ci
npm run build
Enter fullscreen mode Exit fullscreen mode

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.

Run a static topology check

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
Enter fullscreen mode Exit fullscreen mode

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"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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.

Use the result in a real project

Run declare against your application first:

node /path/to/portmap/dist/cli.js declare /path/to/your-app --json
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Add a read-only MCP server

portmap includes a read-only MCP stdio server. After building, the command is:

node /path/to/portmap/dist/cli.js mcp
Enter fullscreen mode Exit fullscreen mode

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"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

Why the reconciliation is useful

The core design separates three kinds of evidence:

  1. Declared configuration tells you what the project says should run. portmap looks at package scripts, Vite configuration, environment URLs, and common Docker Compose port mappings.
  2. Runtime discovery tells you what the operating system says is listening. This is optional because a static check is still valuable before startup.
  3. Reconciliation connects declarations, listeners, and environment references into services, edges, and findings.

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.

CI verification

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
Enter fullscreen mode Exit fullscreen mode

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.

Failure modes and security boundaries

There are several important boundaries:

  • Runtime-only ports that are hardcoded in JavaScript without a discoverable configuration can produce a PRT-02 warning or remain undiscovered.
  • WSL and Docker networking can make a container listener invisible from the host perspective.
  • The current YAML Compose support handles common ports: patterns, not every Compose feature.
  • PID-to-repository attribution is heuristic. Low-confidence matches are flagged rather than presented as certain.
  • The MCP server is read-only, but a local MCP client still decides which project path to pass to it. Review client configuration and avoid exposing sensitive directories unnecessarily.

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.

Reproducible verification

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
Enter fullscreen mode Exit fullscreen mode

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.

FAQ

Does portmap replace Docker or a process manager?

No. It reports topology. It does not start, stop, or restart services.

Does it require an AI model?

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.

Can I install it from npm today?

The README lists npm installation as a future option. The verified current path is cloning the repository, running npm ci, and building locally.

Should .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.

Takeaway

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.

Top comments (1)

Collapse
 
davekurian profile image
Dave Kurian •

The note on whether to commit .portmap.json is fair. One detail I'd want to know: even with .env values kept out, a committed report can still expose internal service names or private hostnames. Does portmap redact those before writing, or is that left to the project owner?