Koa-based proxy server with dynamic routing, WebSocket and MCP support allowing quick integration.
3.0K
This project is a proxy server built using Koa. It is designed to forward, filter, and log HTTP and WebSocket requests between clients and backend services, providing a flexible middleware architecture for custom logic and integrations.
WebSocket support is included, which is especially useful when proxying the Neo4j server or other services that require real-time, bidirectional communication.
connectors/plugins (for example dist/connectors/plugins), and add custom authorization logic by dropping policies in pep/policies (for example dist/pep/policies).Install dependencies:
npm install
Start the proxy server:
npm start
Configure your client or application to send requests through this proxy.
src/app.ts - Main application entry pointsrc/middleware/ - Koa middleware (context, logger, policy, user)src/routes/ - Route handlerssrc/utils/ - Utility functionssrc/connectors/ - Example connectorssrc/config/defaultEnv.ts - Default dynamic route configurationDYNAMIC_ROUTES:
JSON string for dynamic route configuration (DynamicRoute[]).
If DYNAMIC_ROUTES is not set, the app uses DEFAULT_DYNAMIC_ROUTES from src/config/defaultEnv.ts.
Parsing and interpolation behavior:
{ENV_NAME}.{DEFAULT_DYNAMIC_ROUTES_INVENTORY_PREFIX} -> value of DYNAMIC_ROUTES_INVENTORY_PREFIX{MCP_NEO4J_AUTH_HEADER} -> value of MCP_NEO4J_AUTH_HEADERprocess.env[PLACEHOLDER].Route registration precedence (per route object):
redirect exists, register a redirect route.splashPage is true, register the services inventory page route.relativeFilePath exists, register a static file route.target exists, register a proxied route.Notes:
src/routes/proxy-ws.ts; HTTP redirect/proxy handlers skip them.splashPage route is served at DYNAMIC_ROUTES_INVENTORY_PREFIX.Supported DynamicRoute fields:
| Field | Type | Behavior |
|---|---|---|
name | string | Display/logging name; also used for services page button text. |
route | string | Koa route path/pattern (for example /analytics/(.*)). |
target | string | Upstream target URL for proxied HTTP routes. |
protocol | 'http' | 'mcp-streamable-http' | Explicit protocol marker for proxied routes; use 'mcp-streamable-http' for MCP endpoints. |
rewritebase | boolean | For HTML responses, injects <base href="..."> and patches CSP base-uri. |
redirect | string | { default, conditionalRedirects[] } | Redirects request instead of proxying; can be header-conditional. |
conditionalReturns | Array<{ condition, headerName, includes, return }> | Returns a local JSON payload when header condition matches. |
subpathReturns | Array<{ path, return }> | Returns a local JSON payload when ctx.path starts with path. |
requestHeaderRules | RequestHeaderRule[] | Rewrites outbound proxy request headers before forwarding. |
splashPage | boolean | Registers the services inventory HTML page (DYNAMIC_ROUTES_INVENTORY_PREFIX). |
relativeFilePath | string | Serves local file content for the route. |
params | string | Query/path suffix appended to the services page button link. |
policyName | string | Policy metadata used by policy mapping/execution. |
connectorName | string | Connector plugin metadata used by connector mapping. |
icon | string | Inline SVG/HTML snippet rendered in the services page button. |
doNotRenderButton | boolean | Excludes route from services inventory button rendering. |
hideIfNoAccess | boolean | If unauthorized, hides button instead of rendering a disabled one. |
websocket.handler | 'neo4j-bolt' | 'attu' | Enables WebSocket handling for matching route path. |
websocket.target | string | Upstream WebSocket target URL. |
websocket.authHeader | string | Optional auth header used by attu WebSocket handler. |
websocket.preserveQueryString | boolean | When true, forwards query string in attu WebSocket handler. |
Return-key based responses (redirect.conditionalRedirects[].return, conditionalReturns[].return, subpathReturns[].return) currently support:
NEO4J_BROWSER_MANIFESTrequestHeaderRules details:
operation: "create" -> sets header only if missing.operation: "update" -> always sets/replaces header.operation: "patch" -> regex replace on existing header value.operation: "delete" -> removes header.when supports header conditions using includes, equals, matches (+ regex flags), or exists.Proxied HTTP runtime behavior:
requestHeaderRules and updates host to the target host.Location headers (3xx) back under the proxied route prefix.rewritebase: true + HTML responses, removes existing <base>, injects a new <base href="...">, patches CSP base-uri, and omits stale content-length.text/event-stream responses directly and closes streams gracefully on upstream/downstream failures.protocol: "mcp-streamable-http" failures, returns gentle 503 errors (jsonrpc payload for POST, plain text for GET).IGNORE_URLS_FOR_LOGGING_BY_PREFIX:
Comma-separated list or JSON array of URL path prefixes to ignore in request logging. Useful for suppressing logs for health checks, static assets, or other non-essential endpoints.
Default: ['/_app', '/health', '/metrics', '/favicon.ico', '/robots.txt', '/static', '/public']
DYNAMIC_ROUTES_INVENTORY_PREFIX:
Defines the services inventory route path used by splash pages and templates.
Default: /services
MCP_NEO4J_AUTH_HEADER:
Default auth header value that can be injected via {MCP_NEO4J_AUTH_HEADER} placeholder in DYNAMIC_ROUTES.
UPSTREAM_ERROR_MSG:
HTML shown for non-MCP upstream proxy failures.
ACCESS_DENY_ERROR_MSG:
HTML shown by the policy middleware when access is denied.
DYNAMIC_ROUTES_SERVICES_HTML:
HTML template for the services inventory page, where buttons are injected at <!--SERVICES_BUTTONS-->.
DEFAULT_DYNAMIC_ROUTESDEFAULT_DYNAMIC_ROUTES in src/config/defaultEnv.ts is the built-in route set when DYNAMIC_ROUTES is not provided.
The examples below are copied from that default and can be reused as a starting point for custom DYNAMIC_ROUTES values.
{
"name": "root",
"route": "/",
"policyName": "mock-always-allow",
"connectorName": "simple",
"websocket": {
"handler": "neo4j-bolt",
"target": "ws://10.29.1.86:7687"
},
"redirect": {
"default": "/services",
"conditionalRedirects": [
{
"condition": "header",
"headerName": "accept",
"includes": "application/json",
"return": "NEO4J_BROWSER_MANIFEST"
}
]
}
}
{
"name": "static-files-browser-config-json",
"route": "/browser/:neo4j.browser.config.json",
"policyName": "mock-always-allow",
"connectorName": "simple",
"relativeFilePath": "src/config/neo4j.browser.config.json"
}
{
"name": "Data Browser",
"route": "/analytics/(.*)",
"target": "http://10.29.1.86:3001",
"rewritebase": true,
"policyName": "mock-always-allow",
"connectorName": "simple"
}
{
"name": "Link Analytics AI",
"route": "/mcp",
"target": "http://10.29.1.86:7475/mcp",
"protocol": "mcp-streamable-http",
"requestHeaderRules": [
{
"operation": "create",
"headerName": "Authorization",
"value": "{MCP_NEO4J_AUTH_HEADER}"
}
],
"policyName": "mock-always-allow",
"connectorName": "mock",
"doNotRenderButton": true
}
Todo.md.This repo includes a GitHub Actions workflow that runs Trivy on every PR and on pushes to main, and uploads results to GitHub Code Scanning:
.github/workflows/trivy.ymltrivy.sarif uploaded to the repo Security tabMIT
In order to work with neo4j authentication for read-only databases use following URL structure:
Content type
Image
Digest
sha256:09136c238…
Size
97.5 MB
Last updated
5 months ago
docker pull agilebeat/proxy-plus-koa-based