@askrjs/node 0.0.9 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/index.d.ts +84 -1
- package/dist/index.js +63 -1
- package/dist/mcp.d.ts +26 -0
- package/dist/mcp.js +13 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,6 +31,13 @@ callback. It preserves streaming bodies, repeated `Set-Cookie` headers, aborts,
|
|
|
31
31
|
status text, and HEAD responses. `next` receives adapter failures; application responses, including
|
|
32
32
|
`404`, remain owned by the `ServerApp` and do not fall through.
|
|
33
33
|
|
|
34
|
+
Every dispatched request includes `CLIENT_ADDRESS_HEADER` (`x-askr-client-address`) set from the
|
|
35
|
+
TCP socket peer. The adapter overwrites a client-supplied value and does not interpret
|
|
36
|
+
`X-Forwarded-For`, so applications can use this header for direct-listener IP controls without
|
|
37
|
+
trusting attacker-controlled forwarding metadata. Deployments behind a reverse proxy see the
|
|
38
|
+
proxy peer by default. Supporting original client addresses requires an explicit trusted-proxy
|
|
39
|
+
boundary; do not read `X-Forwarded-For` directly in application code.
|
|
40
|
+
|
|
34
41
|
Every handler must have a trusted URL boundary. Pass `baseUrl` when the external origin is fixed,
|
|
35
42
|
or `allowedHosts` when the request `Host` determines the origin. Host names are canonicalized and
|
|
36
43
|
compared case-insensitively; entries without a port allow that host on any port, while entries with
|
package/dist/index.d.ts
CHANGED
|
@@ -2,55 +2,138 @@ import { AddressInfo } from "node:net";
|
|
|
2
2
|
import { IncomingMessage, Server, ServerResponse } from "node:http";
|
|
3
3
|
import { PerMessageDeflateOptions } from "ws";
|
|
4
4
|
import { ServerApp } from "@askrjs/server";
|
|
5
|
+
//#region src/client-address.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Reserved request header containing the TCP peer address authenticated by the Node adapter.
|
|
8
|
+
* Any value supplied by the HTTP client is overwritten before application dispatch.
|
|
9
|
+
*/
|
|
10
|
+
declare const CLIENT_ADDRESS_HEADER = "x-askr-client-address";
|
|
11
|
+
/** Normalizes the socket peer address used for the adapter-authenticated request header. */
|
|
12
|
+
declare function normalizeClientAddress(address: string | undefined): string;
|
|
13
|
+
//#endregion
|
|
5
14
|
//#region src/contracts.d.ts
|
|
15
|
+
/** Options controlling how WebSocket upgrades are handled on a Node server. */
|
|
6
16
|
interface NodeWebSocketOptions {
|
|
17
|
+
/** Milliseconds to wait for a peer to acknowledge a close handshake before the socket is force-closed. */
|
|
7
18
|
readonly closeTimeout?: number;
|
|
19
|
+
/** Maximum allowed size, in bytes, of a single WebSocket message. */
|
|
8
20
|
readonly maxPayload?: number;
|
|
21
|
+
/** Maximum number of body bytes read from a rejected upgrade request before the connection is destroyed. */
|
|
9
22
|
readonly maxRejectionBodyBytes?: number;
|
|
23
|
+
/** Enables or configures the permessage-deflate WebSocket extension. */
|
|
10
24
|
readonly perMessageDeflate?: boolean | PerMessageDeflateOptions;
|
|
25
|
+
/** Origins allowed to open a WebSocket connection; when omitted, all origins are allowed. */
|
|
11
26
|
readonly allowedOrigins?: readonly string[];
|
|
12
27
|
}
|
|
28
|
+
/** Options shared by anything that turns Node HTTP requests into `@askrjs/server` fetch calls. */
|
|
13
29
|
interface NodeHandlerOptions {
|
|
30
|
+
/** Base URL used to resolve request paths into absolute URLs. */
|
|
14
31
|
readonly baseUrl?: string;
|
|
32
|
+
/** Hosts allowed in the request's `Host` header; requests for other hosts are rejected. */
|
|
15
33
|
readonly allowedHosts?: readonly string[];
|
|
16
34
|
}
|
|
35
|
+
/** Options for {@link listen}, controlling how the Node HTTP server binds and behaves. */
|
|
17
36
|
interface ListenOptions extends NodeHandlerOptions {
|
|
37
|
+
/** Port to listen on; defaults to an ephemeral port when omitted. */
|
|
18
38
|
port?: number;
|
|
39
|
+
/** Host/address to bind to. */
|
|
19
40
|
host?: string;
|
|
41
|
+
/** Allows binding to a non-loopback host without the usual safety check. */
|
|
20
42
|
allowPublicBind?: boolean;
|
|
43
|
+
/** Maximum length of the queue of pending connections. */
|
|
21
44
|
backlog?: number;
|
|
45
|
+
/** Aborting this signal stops the server. */
|
|
22
46
|
signal?: AbortSignal;
|
|
47
|
+
/** Node HTTP server `requestTimeout`, in milliseconds. */
|
|
23
48
|
requestTimeout?: number;
|
|
49
|
+
/** Node HTTP server `headersTimeout`, in milliseconds. */
|
|
24
50
|
headersTimeout?: number;
|
|
51
|
+
/** Node HTTP server `keepAliveTimeout`, in milliseconds. */
|
|
25
52
|
keepAliveTimeout?: number;
|
|
53
|
+
/** Enables WebSocket support, optionally with detailed options. */
|
|
26
54
|
websocket?: boolean | NodeWebSocketOptions;
|
|
27
55
|
}
|
|
56
|
+
/** Options for {@link serve}, extending {@link ListenOptions} with static asset serving and shutdown behavior. */
|
|
28
57
|
interface ServeOptions extends ListenOptions {
|
|
58
|
+
/** Serves static files from this directory before falling back to the application. */
|
|
29
59
|
readonly assets?: {
|
|
30
60
|
readonly root: string;
|
|
31
61
|
};
|
|
62
|
+
/** OS signals that trigger a graceful shutdown; pass `false` to disable automatic shutdown handling. */
|
|
32
63
|
readonly signals?: false | readonly NodeJS.Signals[];
|
|
33
64
|
}
|
|
65
|
+
/** A running application returned by {@link serve}. */
|
|
34
66
|
interface ServedApplication {
|
|
67
|
+
/** The underlying Node HTTP server. */
|
|
35
68
|
readonly server: import("node:http").Server;
|
|
69
|
+
/** The base URL the server is listening on. */
|
|
36
70
|
readonly url: string;
|
|
71
|
+
/** Gracefully shuts down the server, any WebSocket connections, and the application. */
|
|
37
72
|
close(): Promise<void>;
|
|
38
73
|
}
|
|
74
|
+
/** Connect/Express-style `next` callback used to hand off unhandled requests. */
|
|
39
75
|
type ConnectNext = (error?: unknown) => void;
|
|
76
|
+
/** A Node-style request handler compatible with `http.Server` and Connect-style middleware chains. */
|
|
40
77
|
type NodeHandler = (request: IncomingMessage, response: ServerResponse, next?: ConnectNext) => void;
|
|
41
78
|
//#endregion
|
|
42
79
|
//#region src/handler.d.ts
|
|
80
|
+
/**
|
|
81
|
+
* Wraps an `@askrjs/server` application as a Node-style request handler.
|
|
82
|
+
*
|
|
83
|
+
* Converts each incoming `IncomingMessage`/`ServerResponse` pair into a web
|
|
84
|
+
* `Request`, dispatches it through `app.fetch`, and writes the resulting web
|
|
85
|
+
* `Response` back to Node. Errors are reported to `next` when provided,
|
|
86
|
+
* otherwise a minimal 400/500 response is written directly.
|
|
87
|
+
*
|
|
88
|
+
* @param app - The application to dispatch requests to.
|
|
89
|
+
* @param options - Options controlling base URL resolution and host validation.
|
|
90
|
+
* @returns A handler usable with `http.createServer` or Connect-style middleware.
|
|
91
|
+
*/
|
|
43
92
|
declare function createNodeHandler(app: ServerApp, options: NodeHandlerOptions): NodeHandler;
|
|
44
93
|
//#endregion
|
|
45
94
|
//#region src/listen.d.ts
|
|
95
|
+
/** A Node HTTP server that is guaranteed to be listening for connections. */
|
|
46
96
|
type ListeningServer = Server & {
|
|
47
97
|
address(): AddressInfo | string | null;
|
|
48
98
|
};
|
|
99
|
+
/**
|
|
100
|
+
* Starts a Node HTTP server for an `@askrjs/server` application and resolves once it is listening.
|
|
101
|
+
*
|
|
102
|
+
* Optionally installs WebSocket support and wires up graceful shutdown on
|
|
103
|
+
* `options.signal`. Unlike {@link serve}, this does not serve static assets
|
|
104
|
+
* or install OS signal handlers.
|
|
105
|
+
*
|
|
106
|
+
* @param app - The application to serve.
|
|
107
|
+
* @param options - Listen options such as port, host, timeouts, and WebSocket support.
|
|
108
|
+
* @returns A promise resolving to the listening server once it has bound successfully.
|
|
109
|
+
* @example
|
|
110
|
+
* const server = await listen(app, { port: 3000 });
|
|
111
|
+
*/
|
|
49
112
|
declare function listen(app: ServerApp, options?: ListenOptions): Promise<ListeningServer>;
|
|
50
113
|
//#endregion
|
|
51
114
|
//#region src/serve.d.ts
|
|
115
|
+
/**
|
|
116
|
+
* Serves an `@askrjs/server` application over Node HTTP, with optional static
|
|
117
|
+
* asset serving, WebSocket support, and graceful shutdown on OS signals or an
|
|
118
|
+
* abort signal.
|
|
119
|
+
*
|
|
120
|
+
* Requests for paths with a file extension are first checked against
|
|
121
|
+
* `options.assets.root` (path-traversal safe, following symlinks) and served
|
|
122
|
+
* directly with appropriate `content-type`/`cache-control` headers before
|
|
123
|
+
* falling back to the application handler. HTML responses from the
|
|
124
|
+
* application get a `no-cache` header when they don't already set
|
|
125
|
+
* `cache-control`.
|
|
126
|
+
*
|
|
127
|
+
* @param app - The application to serve; may expose an optional `close()` for cleanup.
|
|
128
|
+
* @param options - Serve options such as port, host, static assets, and shutdown signals.
|
|
129
|
+
* @returns The served application, including its bound `url` and a `close()` for shutdown.
|
|
130
|
+
* @example
|
|
131
|
+
* const app = await serve(myApp, { port: 3000, assets: { root: "./public" } });
|
|
132
|
+
* // ...
|
|
133
|
+
* await app.close();
|
|
134
|
+
*/
|
|
52
135
|
declare function serve(app: ServerApp & {
|
|
53
136
|
close?: () => void | Promise<void>;
|
|
54
137
|
}, options?: ServeOptions): Promise<ServedApplication>;
|
|
55
138
|
//#endregion
|
|
56
|
-
export { ConnectNext, ListenOptions, ListeningServer, NodeHandler, NodeHandlerOptions, NodeWebSocketOptions, ServeOptions, ServedApplication, createNodeHandler, listen, serve };
|
|
139
|
+
export { CLIENT_ADDRESS_HEADER, ConnectNext, ListenOptions, ListeningServer, NodeHandler, NodeHandlerOptions, NodeWebSocketOptions, ServeOptions, ServedApplication, createNodeHandler, listen, normalizeClientAddress, serve };
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,22 @@ import { createReadStream } from "node:fs";
|
|
|
6
6
|
import { realpath, stat } from "node:fs/promises";
|
|
7
7
|
import { extname, resolve, sep } from "node:path";
|
|
8
8
|
import { pipeline } from "node:stream";
|
|
9
|
+
//#region src/client-address.ts
|
|
10
|
+
/**
|
|
11
|
+
* Reserved request header containing the TCP peer address authenticated by the Node adapter.
|
|
12
|
+
* Any value supplied by the HTTP client is overwritten before application dispatch.
|
|
13
|
+
*/
|
|
14
|
+
const CLIENT_ADDRESS_HEADER = "x-askr-client-address";
|
|
15
|
+
/** Normalizes the socket peer address used for the adapter-authenticated request header. */
|
|
16
|
+
function normalizeClientAddress(address) {
|
|
17
|
+
if (!address) return "unknown";
|
|
18
|
+
if (address.toLowerCase().startsWith("::ffff:")) {
|
|
19
|
+
const mapped = address.slice(7);
|
|
20
|
+
if (isIP(mapped) === 4) return mapped;
|
|
21
|
+
}
|
|
22
|
+
return address;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
9
25
|
//#region src/request.ts
|
|
10
26
|
var NodeRequestError = class extends TypeError {};
|
|
11
27
|
function explicitPort(authority) {
|
|
@@ -69,6 +85,7 @@ function requestHeaders(request) {
|
|
|
69
85
|
if (Array.isArray(value)) for (const item of value) headers.append(key, item);
|
|
70
86
|
else headers.set(key, value);
|
|
71
87
|
}
|
|
88
|
+
headers.set(CLIENT_ADDRESS_HEADER, normalizeClientAddress(request.socket.remoteAddress));
|
|
72
89
|
return headers;
|
|
73
90
|
}
|
|
74
91
|
function resolveNodeRequestHref(request, options) {
|
|
@@ -255,6 +272,18 @@ async function handleNodeRequest(app, preparedOptions, request, response, signal
|
|
|
255
272
|
cleanup();
|
|
256
273
|
}
|
|
257
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* Wraps an `@askrjs/server` application as a Node-style request handler.
|
|
277
|
+
*
|
|
278
|
+
* Converts each incoming `IncomingMessage`/`ServerResponse` pair into a web
|
|
279
|
+
* `Request`, dispatches it through `app.fetch`, and writes the resulting web
|
|
280
|
+
* `Response` back to Node. Errors are reported to `next` when provided,
|
|
281
|
+
* otherwise a minimal 400/500 response is written directly.
|
|
282
|
+
*
|
|
283
|
+
* @param app - The application to dispatch requests to.
|
|
284
|
+
* @param options - Options controlling base URL resolution and host validation.
|
|
285
|
+
* @returns A handler usable with `http.createServer` or Connect-style middleware.
|
|
286
|
+
*/
|
|
258
287
|
function createNodeHandler(app, options) {
|
|
259
288
|
const preparedOptions = prepareNodeHandlerOptions(options);
|
|
260
289
|
return (request, response, next) => {
|
|
@@ -499,6 +528,19 @@ function installWebSockets(server, app, options, handlerOptions) {
|
|
|
499
528
|
}
|
|
500
529
|
//#endregion
|
|
501
530
|
//#region src/listen.ts
|
|
531
|
+
/**
|
|
532
|
+
* Starts a Node HTTP server for an `@askrjs/server` application and resolves once it is listening.
|
|
533
|
+
*
|
|
534
|
+
* Optionally installs WebSocket support and wires up graceful shutdown on
|
|
535
|
+
* `options.signal`. Unlike {@link serve}, this does not serve static assets
|
|
536
|
+
* or install OS signal handlers.
|
|
537
|
+
*
|
|
538
|
+
* @param app - The application to serve.
|
|
539
|
+
* @param options - Listen options such as port, host, timeouts, and WebSocket support.
|
|
540
|
+
* @returns A promise resolving to the listening server once it has bound successfully.
|
|
541
|
+
* @example
|
|
542
|
+
* const server = await listen(app, { port: 3000 });
|
|
543
|
+
*/
|
|
502
544
|
function listen(app, options = {}) {
|
|
503
545
|
options.signal?.throwIfAborted();
|
|
504
546
|
const host = resolveBindHost(options);
|
|
@@ -576,6 +618,26 @@ function isWithinRoot(root, candidate) {
|
|
|
576
618
|
const prefix = root.endsWith(sep) ? root : `${root}${sep}`;
|
|
577
619
|
return candidate === root || candidate.startsWith(prefix);
|
|
578
620
|
}
|
|
621
|
+
/**
|
|
622
|
+
* Serves an `@askrjs/server` application over Node HTTP, with optional static
|
|
623
|
+
* asset serving, WebSocket support, and graceful shutdown on OS signals or an
|
|
624
|
+
* abort signal.
|
|
625
|
+
*
|
|
626
|
+
* Requests for paths with a file extension are first checked against
|
|
627
|
+
* `options.assets.root` (path-traversal safe, following symlinks) and served
|
|
628
|
+
* directly with appropriate `content-type`/`cache-control` headers before
|
|
629
|
+
* falling back to the application handler. HTML responses from the
|
|
630
|
+
* application get a `no-cache` header when they don't already set
|
|
631
|
+
* `cache-control`.
|
|
632
|
+
*
|
|
633
|
+
* @param app - The application to serve; may expose an optional `close()` for cleanup.
|
|
634
|
+
* @param options - Serve options such as port, host, static assets, and shutdown signals.
|
|
635
|
+
* @returns The served application, including its bound `url` and a `close()` for shutdown.
|
|
636
|
+
* @example
|
|
637
|
+
* const app = await serve(myApp, { port: 3000, assets: { root: "./public" } });
|
|
638
|
+
* // ...
|
|
639
|
+
* await app.close();
|
|
640
|
+
*/
|
|
579
641
|
async function serve(app, options = {}) {
|
|
580
642
|
options.signal?.throwIfAborted();
|
|
581
643
|
const host = resolveBindHost(options);
|
|
@@ -711,4 +773,4 @@ async function serve(app, options = {}) {
|
|
|
711
773
|
});
|
|
712
774
|
}
|
|
713
775
|
//#endregion
|
|
714
|
-
export { createNodeHandler, listen, serve };
|
|
776
|
+
export { CLIENT_ADDRESS_HEADER, createNodeHandler, listen, normalizeClientAddress, serve };
|
package/dist/mcp.d.ts
CHANGED
|
@@ -2,21 +2,47 @@ import { Readable, Writable } from "node:stream";
|
|
|
2
2
|
import { AuthContext } from "@askrjs/auth";
|
|
3
3
|
import { McpServer } from "@askrjs/server/mcp";
|
|
4
4
|
//#region src/mcp.d.ts
|
|
5
|
+
/** Options for {@link connectMcpStdio}. */
|
|
5
6
|
interface McpStdioOptions<Dependencies = undefined> {
|
|
7
|
+
/** Application dependencies passed through to each MCP request. */
|
|
6
8
|
dependencies: Dependencies;
|
|
9
|
+
/** Stream to read newline-delimited JSON-RPC requests from; defaults to `process.stdin`. */
|
|
7
10
|
input?: Readable;
|
|
11
|
+
/** Stream to write newline-delimited JSON-RPC responses to; defaults to `process.stdout`. */
|
|
8
12
|
output?: Writable;
|
|
13
|
+
/** Stream that non-protocol errors are reported to; defaults to `process.stderr`. */
|
|
9
14
|
diagnostics?: Writable;
|
|
15
|
+
/** Aborting this signal closes the connection. */
|
|
10
16
|
signal?: AbortSignal;
|
|
17
|
+
/** Auth context to use for requests, or a function that derives one from the environment. */
|
|
11
18
|
auth?: AuthContext | ((environment: NodeJS.ProcessEnv) => AuthContext | Promise<AuthContext>);
|
|
19
|
+
/** Environment passed to the `auth` function; defaults to `process.env`. */
|
|
12
20
|
environment?: NodeJS.ProcessEnv;
|
|
21
|
+
/** Maximum size, in bytes, of a single input line before it is rejected; defaults to 1 MiB. */
|
|
13
22
|
maxLineBytes?: number;
|
|
23
|
+
/** Maximum number of requests handled concurrently; defaults to 16. */
|
|
14
24
|
maxConcurrency?: number;
|
|
15
25
|
}
|
|
26
|
+
/** A live MCP stdio connection returned by {@link connectMcpStdio}. */
|
|
16
27
|
interface McpStdioConnection {
|
|
28
|
+
/** Resolves once the connection has fully closed. */
|
|
17
29
|
readonly closed: Promise<void>;
|
|
30
|
+
/** Closes the connection, aborting in-flight requests and terminating the MCP session. */
|
|
18
31
|
close(): Promise<void>;
|
|
19
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Connects an MCP server to newline-delimited JSON-RPC over stdio (or any
|
|
35
|
+
* pair of readable/writable streams).
|
|
36
|
+
*
|
|
37
|
+
* Reads one JSON-RPC message per line, dispatches it to `mcp.handle`, and
|
|
38
|
+
* writes the response back as a line of JSON. Handles request cancellation
|
|
39
|
+
* notifications, enforces `maxConcurrency` and `maxLineBytes`, and cleans up
|
|
40
|
+
* the MCP session when the connection closes.
|
|
41
|
+
*
|
|
42
|
+
* @param mcp - The MCP server to dispatch requests to.
|
|
43
|
+
* @param options - Stdio connection options, including dependencies and stream overrides.
|
|
44
|
+
* @returns A handle exposing `closed` and `close()` for the connection's lifecycle.
|
|
45
|
+
*/
|
|
20
46
|
declare function connectMcpStdio<Dependencies>(mcp: McpServer<Dependencies>, options: McpStdioOptions<Dependencies>): McpStdioConnection;
|
|
21
47
|
//#endregion
|
|
22
48
|
export { McpStdioConnection, McpStdioOptions, connectMcpStdio };
|
package/dist/mcp.js
CHANGED
|
@@ -7,6 +7,19 @@ const anonymous = Object.freeze({
|
|
|
7
7
|
session: null,
|
|
8
8
|
tenant: null
|
|
9
9
|
});
|
|
10
|
+
/**
|
|
11
|
+
* Connects an MCP server to newline-delimited JSON-RPC over stdio (or any
|
|
12
|
+
* pair of readable/writable streams).
|
|
13
|
+
*
|
|
14
|
+
* Reads one JSON-RPC message per line, dispatches it to `mcp.handle`, and
|
|
15
|
+
* writes the response back as a line of JSON. Handles request cancellation
|
|
16
|
+
* notifications, enforces `maxConcurrency` and `maxLineBytes`, and cleans up
|
|
17
|
+
* the MCP session when the connection closes.
|
|
18
|
+
*
|
|
19
|
+
* @param mcp - The MCP server to dispatch requests to.
|
|
20
|
+
* @param options - Stdio connection options, including dependencies and stream overrides.
|
|
21
|
+
* @returns A handle exposing `closed` and `close()` for the connection's lifecycle.
|
|
22
|
+
*/
|
|
10
23
|
function connectMcpStdio(mcp, options) {
|
|
11
24
|
const input = options.input ?? process.stdin;
|
|
12
25
|
const output = options.output ?? process.stdout;
|