{"slug": "stop-guessing-localhost-ports-with-portmap-and-typescript", "title": "Stop Guessing localhost Ports with portmap and TypeScript", "summary": "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.", "body_md": "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`.\n\nOne 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.\n\n[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.\n\nThis 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.\n\n```\ngit clone https://github.com/paladini/portmap.git\ncd portmap\nnpm ci\nnpm run build\nnode dist/cli.js declare fixtures/mismatch --json\n```\n\nThe 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.\n\nYou need:\n\n`engines.node` as `>=20`.` declare` command does not need a running service.\nThe 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.\n\nClone the project and install the lockfile exactly:\n\n```\ngit clone https://github.com/paladini/portmap.git\ncd portmap\nnpm ci\nnpm run build\n```\n\nThe 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.\n\nThe 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.\n\nThe safest first check is `declare`. It reads project files without requiring the application's processes to be running:\n\n```\nnode dist/cli.js declare fixtures/mismatch --json\n```\n\nThe 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.\n\nThe important part of the JSON is the relationship between the declared service, references, and findings:\n\n```\n{\n  \"services\": [\n    {\n      \"id\": \"vite\",\n      \"declared\": {\n        \"port\": 5173,\n        \"sources\": [\"vite.config.ts:server.port\"]\n      },\n      \"status\": \"down\"\n    }\n  ],\n  \"findings\": [\n    {\n      \"id\": \"PRT-01\",\n      \"severity\": \"error\"\n    },\n    {\n      \"id\": \"PRT-04\",\n      \"severity\": \"error\",\n      \"file\": \".env.local:1\"\n    }\n  ]\n}\n```\n\nThe 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.\n\nRun `declare` against your application first:\n\n```\nnode /path/to/portmap/dist/cli.js declare /path/to/your-app --json\n```\n\nIf the report is useful as persistent agent context, write it to the project root:\n\n```\nnode /path/to/portmap/dist/cli.js declare /path/to/your-app --write\n```\n\nThis 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.\n\nWhen processes are running, the full command adds an OS listener scan:\n\n```\nnode /path/to/portmap/dist/cli.js scan /path/to/your-app --json\n```\n\nThe 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.\n\nportmap includes a read-only MCP stdio server. After building, the command is:\n\n```\nnode /path/to/portmap/dist/cli.js mcp\n```\n\nFor Cursor or Claude Code, the documented configuration points the MCP command at the built CLI:\n\n```\n{\n  \"mcpServers\": {\n    \"portmap\": {\n      \"command\": \"node\",\n      \"args\": [\"/path/to/portmap/dist/cli.js\", \"mcp\"]\n    }\n  }\n}\n```\n\nThe 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.\n\nThe core design separates three kinds of evidence:\n\nThat 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.\n\nThe CLI can turn findings into a non-zero exit status. For example, this command fails when at least one error remains:\n\n```\nnode dist/cli.js scan . --min-findings 1 --min-severity error --json\n```\n\nUse 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.\n\nThere are several important boundaries:\n\n`PRT-02` warning or remain undiscovered.`ports:` patterns, not every Compose feature.\nThe 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.\n\nFrom the clean checkout, verify the source path and fixture again:\n\n```\nnpm ci\nnpm run build\nnode dist/cli.js declare fixtures/mismatch --json > mismatch-report.json\n```\n\nConfirm 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.\n\nThe 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.\n\nNo. It reports topology. It does not start, stop, or restart services.\n\nNo. 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.\n\nThe README lists npm installation as a future option. The verified current path is cloning the repository, running `npm ci`, and building locally.\n\n`.portmap.json` be committed?\nOnly 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.\n\nThe 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.\n\nIf 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?\n\nAI 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.", "url": "https://wpnews.pro/news/stop-guessing-localhost-ports-with-portmap-and-typescript", "canonical_source": "https://dev.to/paladini/stop-guessing-localhost-ports-with-portmap-and-typescript-4876", "published_at": "2026-10-24 12:31:02+00:00", "updated_at": "2026-10-24 12:44:21.613004+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-tools", "agent-protocols"], "entities": ["portmap", "paladini", "Vite", "Docker", "GitHub", "TypeScript"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/stop-guessing-localhost-ports-with-portmap-and-typescript", "markdown": "https://wpnews.pro/news/stop-guessing-localhost-ports-with-portmap-and-typescript.md", "text": "https://wpnews.pro/news/stop-guessing-localhost-ports-with-portmap-and-typescript.txt", "jsonld": "https://wpnews.pro/news/stop-guessing-localhost-ports-with-portmap-and-typescript.jsonld"}}