ZonerMCP is a Node.js Model Context Protocol server for Lebanon, NH zoning and GIS queries. It fronts an ArcGIS FeatureServer with a Streamable HTTP MCP endpoint, a layer allowlist, field profiles, bearer authentication, CORS controls, rate limiting, response caching, and bounded session lifecycle management.
config.js Runtime configuration, limits, auth, and deployment checks
layer-registry.js Queryable layer allowlist and field policies
mcp-server.js Express server, MCP transport, tools, and handlers
tests/ Node test runner coverage for auth, CORS, and stateless calls
README.md Public setup and operations documentation
.env.example Environment variable reference
- Node.js 20 with ES modules
- Express HTTP server
@modelcontextprotocol/sdkStreamable HTTP transport- Default port:
5000 - Replit deployment target: Autoscale
- Health check:
GET /health
POST /mcp: main Streamable HTTP MCP endpointGET /mcp: server info without a session, or stream handling with a session IDDELETE /mcp: session cleanup- Tools:
list_layers,describe_layer,query_features,lookup_zoning_by_address
Add these Replit Secrets before starting the deployment:
ARCGIS_BASE_URL: ArcGIS FeatureServer base URLMCP_BEARER_TOKEN: bearer token for MCP clients when auth is enabledCORS_ALLOW_ORIGINS: comma-separated trusted origins for production browser access
The checked-in .replit file sets REQUIRE_AUTH=true, matching the server default. For temporary unauthenticated local testing, override with REQUIRE_AUTH=false; do not use that setting for production.
If a client cannot preserve MCP session IDs, set ALLOW_STATELESS=true explicitly. In development it defaults to enabled; in production it defaults to disabled.
| Variable | Default | Required | Description |
|---|---|---|---|
ARCGIS_BASE_URL |
none | yes | ArcGIS FeatureServer base URL. |
MCP_BEARER_TOKEN |
none | when REQUIRE_AUTH=true |
Bearer token accepted by /mcp. |
CORS_ALLOW_ORIGINS |
none | when NODE_ENV=production |
Comma-separated trusted browser origins. |
NODE_ENV |
development |
no | Enables production startup checks when set to production. |
PORT |
5000 |
no | HTTP port. |
REQUIRE_AUTH |
true |
no | Set to false only for local unauthenticated development. |
ALLOW_STATELESS |
true in development, false in production |
no | Allows direct one-shot tools/list and tools/call requests without creating a session. |
ZONING_LAYER |
24 |
no | ArcGIS layer ID for official zoning. |
ADDRESS_LAYER_NAME |
LebanonNHMATMassGeoExport_Layer |
no | ArcGIS layer/table name resolved from the FeatureServer catalog for address lookup. |
MAX_SESSIONS |
100 |
no | Maximum concurrent MCP sessions. |
SESSION_TIMEOUT_MS |
1800000 |
no | Idle session timeout. |
SESSION_REAPER_INTERVAL_MS |
300000 |
no | Session cleanup interval. |
RATE_LIMIT_WINDOW_MS |
60000 |
no | Rate-limit window. |
RATE_LIMIT_MAX_REQUESTS |
60 |
no | Base request limit per window. |
RATE_LIMIT_AUTH_MULTIPLIER |
2 |
no | Multiplier for authenticated request limits. |
CACHE_TTL_SECONDS |
300 |
no | Query cache TTL. |
CACHE_MAX_ENTRIES |
500 |
no | Maximum cached query results. |
MAX_FIELDS |
40 |
no | Maximum explicit fields per query. |
npm install
npm start
npm test
npm run audit
node --check mcp-server.js- Keep data access changes centralized in
layer-registry.js. - Add new MCP tools by defining the tool schema in
TOOLS, adding a handler toTOOL_HANDLERS, and covering the behavior intests/server.test.js. - Production startup intentionally fails without
CORS_ALLOW_ORIGINS. - Authentication intentionally fails closed: if
REQUIRE_AUTH=true,MCP_BEARER_TOKENmust be set.