@ractive-ch/tab-mcp 0.0.0-stage → 0.1.0-alpha.0

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/AI_NOTICE ADDED
@@ -0,0 +1,9 @@
1
+ AI-GENERATED CODE NOTICE
2
+
3
+ This repository contains code that was generated in whole or in part by AI systems under human supervision.
4
+
5
+ To the extent that any portions of this code are not subject to copyright protection under applicable law, those portions are provided without restriction. Where copyright does subsist, it is licensed under the terms below.
6
+
7
+ No representation is made regarding the copyright status of any individual contribution.
8
+
9
+ See LICENSE for the full license text.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jean-Pierre Bergamin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/address.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Stable addresses (DEC-027): the page keeps a random secret, the address id is
3
+ * `base32(sha256(secret))`. The relay stores nothing to recognise an address.
4
+ */
5
+ /** RFC 4648 base32, lower case, without padding. */
6
+ export declare function base32(bytes: Uint8Array): string;
7
+ export declare function isWellFormedSecret(secret: string): boolean;
8
+ export declare function isAddressId(id: string): boolean;
9
+ /** The address id for a page secret: base32(sha256(UTF-8 secret)). */
10
+ export declare function addressIdFromSecret(secret: string): string;
11
+ /** A new page secret (what the browser library does; used by tests and tools). */
12
+ export declare function newSecret(): string;
13
+ /** Shortened address id for logs: enough to correlate, too short to use. */
14
+ export declare function shortAddress(id: string): string;
package/config.d.ts ADDED
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Relay configuration: defaults (DEC-034), validation, and reading `TAB_MCP_*`
3
+ * environment variables. The table lives in `kb/design/architecture.md`.
4
+ */
5
+ export type TrustProxy = "none" | "x-real-ip" | "x-forwarded-for";
6
+ export type LogLevel = "error" | "warn" | "info" | "debug";
7
+ export type StoreKind = "sqlite" | "memory";
8
+ /**
9
+ * How a browser at `/authorize` proves it belongs to the tab's user (DEC-042):
10
+ * the 6-digit code shown in the tab (DEC-028), or, later, identity binding.
11
+ */
12
+ export type PairingKind = "code" | "oidc";
13
+ /** The limits a site may set for its own tab sessions (DEC-045). */
14
+ export interface SiteLimits {
15
+ maxMessageBytes: number;
16
+ maxConcurrentRequests: number;
17
+ requestTimeoutMs: number;
18
+ keepAliveMs: number;
19
+ idleTimeoutMs: number;
20
+ sessionLifetimeMs: number;
21
+ heartbeatMs: number;
22
+ }
23
+ /** One site (page origin) the relay serves, as configured (DEC-045). */
24
+ export interface SiteConfig {
25
+ /** The page origin, e.g. `https://ractive.ch`. */
26
+ origin: string;
27
+ /** The app name `/authorize` shows; wins over the name the page sends. */
28
+ name?: string;
29
+ /** Pairing method; `code` (the default) is the only one so far. */
30
+ pairing?: PairingKind;
31
+ /** Overrides of the instance-wide limits for this site's sessions. */
32
+ limits?: Partial<SiteLimits>;
33
+ }
34
+ /** A site with everything filled in. */
35
+ export interface Site {
36
+ /** The origin, or `*` for the open relay's default site. */
37
+ origin: string;
38
+ name?: string;
39
+ pairing: PairingKind;
40
+ limits: SiteLimits;
41
+ }
42
+ /**
43
+ * The smallest `maxMessageBytes` the relay accepts: MCP requests need room,
44
+ * and the browser library refuses a session that cannot frame a response
45
+ * chunk (below 896 bytes).
46
+ */
47
+ export declare const MIN_MESSAGE_BYTES = 16384;
48
+ /** Longest app name (configured or sent by the page). */
49
+ export declare const MAX_APP_NAME = 64;
50
+ export interface RelayConfig {
51
+ /** External origin, e.g. `https://relay.ractive.ch`. Optional only in development. */
52
+ publicUrl?: string;
53
+ port: number;
54
+ host: string;
55
+ /**
56
+ * Page origins allowed to open tab sessions with the instance-wide limits;
57
+ * `"*"` admits any (open relay). Shorthand for `sites` without overrides.
58
+ */
59
+ allowedOrigins: string[] | "*";
60
+ /** Sites with their own name, pairing method or limits (DEC-045). */
61
+ sites: SiteConfig[];
62
+ /** Hosts whose Client ID Metadata Documents are accepted; empty = any. */
63
+ clientIdHosts: string[];
64
+ trustProxy: TrustProxy;
65
+ /** CORS without credentials on the MCP endpoint (DEC-036). */
66
+ cors: boolean;
67
+ store: StoreKind;
68
+ sqlitePath: string;
69
+ maxSessions: number;
70
+ maxSessionsPerIp: number;
71
+ maxConcurrentRequests: number;
72
+ requestTimeoutMs: number;
73
+ keepAliveMs: number;
74
+ idleTimeoutMs: number;
75
+ sessionLifetimeMs: number;
76
+ heartbeatMs: number;
77
+ helloTimeoutMs: number;
78
+ /** Deadline for receiving an MCP request body (Bun's idle timer does not cover it). */
79
+ bodyTimeoutMs: number;
80
+ /** Limit for MCP request bodies and for messages from the tab. */
81
+ maxMessageBytes: number;
82
+ accessTokenTtlMs: number;
83
+ refreshTokenTtlMs: number;
84
+ /**
85
+ * Absolute end of a grant after pairing: refreshes slide by
86
+ * `refreshTokenTtlMs` but never beyond this (DEC-062).
87
+ */
88
+ grantLifetimeMs: number;
89
+ /** How often expired tokens are deleted from the store. */
90
+ sweepIntervalMs: number;
91
+ /** Per-socket WebSocket send buffer; beyond it sends are dropped (DEC-021). */
92
+ backpressureLimitBytes: number;
93
+ /** Bun's HTTP idle timeout in seconds; MCP requests switch it off per request. */
94
+ httpIdleTimeoutS: number;
95
+ logLevel: LogLevel;
96
+ /** Allows `http:` public URLs and a derived public URL. */
97
+ development: boolean;
98
+ }
99
+ export declare const DEFAULT_CONFIG: Readonly<RelayConfig>;
100
+ export declare class ConfigError extends Error {
101
+ name: string;
102
+ }
103
+ /** Normalise an origin (`scheme://host[:port]`), or throw. */
104
+ export declare function normalizeOrigin(value: string): string;
105
+ /** Fill in defaults and validate. Throws `ConfigError` on bad values. */
106
+ export declare function resolveConfig(input?: Partial<RelayConfig>): RelayConfig;
107
+ /** An app name: 1-64 characters, no control characters. */
108
+ export declare function isAppName(value: unknown): value is string;
109
+ /** The instance-wide limits, the defaults of every site. */
110
+ export declare function defaultLimits(config: RelayConfig): SiteLimits;
111
+ /**
112
+ * The site for a page origin, or `undefined` if the relay does not serve it.
113
+ * Configured sites win; `allowedOrigins` origins and, on an open relay, any
114
+ * other origin get the instance-wide defaults.
115
+ */
116
+ export declare function siteFor(config: RelayConfig, origin: string | null): Site | undefined;
117
+ /** The largest message limit of any site (Bun's server-wide limits). */
118
+ export declare function largestMessageBytes(config: RelayConfig): number;
119
+ type Env = Record<string, string | undefined>;
120
+ /** Read `TAB_MCP_*` variables (and `NODE_ENV`) into a partial config. */
121
+ export declare function configFromEnv(env: Env): Partial<RelayConfig>;
122
+ export {};
package/exchange.d.ts ADDED
@@ -0,0 +1,69 @@
1
+ import type { HttpResponseMessage, HttpResponseStartMessage } from "./protocol.js";
2
+ /**
3
+ * One tunnelled MCP request: the AI app's HTTP response, built from the tab's
4
+ * `http.response*` messages, with the request timer, SSE keep-alives (DEC-020)
5
+ * and the relay-started event stream (DEC-039).
6
+ */
7
+ export type RpcId = string | number | null;
8
+ /** JSON-RPC error codes the relay itself uses (outside the reserved range). */
9
+ export declare const RelayErrorCode: {
10
+ readonly pageNotOpen: -31001;
11
+ readonly timeout: -31002;
12
+ readonly tabGone: -31003;
13
+ readonly overloaded: -31004;
14
+ readonly sendFailed: -31005;
15
+ readonly tooLarge: -31006;
16
+ readonly badResponse: -31007;
17
+ readonly storeUnavailable: -31008;
18
+ };
19
+ export type CancelReason = "client_closed" | "timeout" | "error";
20
+ export interface ExchangeOptions {
21
+ id: string;
22
+ /** JSON-RPC id of the request; `undefined` for a notification. */
23
+ rpcId: RpcId | undefined;
24
+ /** `subscriptions/listen`: exempt from the request timer. */
25
+ isListen: boolean;
26
+ requestTimeoutMs: number;
27
+ keepAliveMs: number;
28
+ /**
29
+ * Limit for the response body the tab sends (not for listen streams), and
30
+ * the bound for bytes queued towards an AI app that does not read.
31
+ */
32
+ maxBodyBytes: number;
33
+ /** Extra headers for every response (CORS). */
34
+ extraHeaders: Record<string, string>;
35
+ /** Tell the tab to stop working on this request. */
36
+ cancel: (reason: CancelReason) => void;
37
+ /** Called once when the exchange is over. */
38
+ onDone: (exchange: Exchange) => void;
39
+ }
40
+ /** One SSE `message` event carrying `data` (split into `data:` lines). */
41
+ export declare function sseEvent(data: string): string;
42
+ export declare function rpcErrorBody(rpcId: RpcId | undefined, code: number, message: string, data?: unknown): string;
43
+ export declare class Exchange {
44
+ #private;
45
+ readonly id: string;
46
+ readonly isListen: boolean;
47
+ readonly response: Promise<Response>;
48
+ /** HTTP status sent to the AI app (for logs); 0 while waiting. */
49
+ status: number;
50
+ /** How the exchange ended (for logs). */
51
+ outcome: string;
52
+ /** Why the tab's response was refused, for the tab's `error` message. */
53
+ badResponseReason: string | undefined;
54
+ constructor(options: ExchangeOptions);
55
+ get done(): boolean;
56
+ /** A complete response from the tab. */
57
+ onResponse(msg: HttpResponseMessage): void;
58
+ onStart(msg: HttpResponseStartMessage): void;
59
+ onChunk(data: string): void;
60
+ onEnd(): void;
61
+ /** The AI app went away: tell the tab, send nothing more. */
62
+ clientClosed(): void;
63
+ /**
64
+ * End the exchange with an error the AI app can read: an HTTP error while
65
+ * nothing was sent, else a JSON-RPC error event if the stream is at an
66
+ * event boundary.
67
+ */
68
+ fail(status: number, code: number, message: string, cancelTab: boolean): void;
69
+ }
package/index.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `@ractive-ch/tab-mcp`: the relay that lets AI apps call MCP tools offered by
3
+ * a web page open in a browser tab.
4
+ */
5
+ export { addressIdFromSecret, newSecret } from "./address.js";
6
+ export { ConfigError, configFromEnv, DEFAULT_CONFIG, type LogLevel, type RelayConfig, resolveConfig, type StoreKind, type TrustProxy, } from "./config.js";
7
+ export { createLogger, type LogFields, type Logger } from "./log.js";
8
+ export { SUBPROTOCOL } from "./protocol.js";
9
+ export { type CreateRelayOptions, createRelay, type Relay, VERSION, } from "./relay.js";
10
+ export { MemoryTokenStore } from "./store/memory.js";
11
+ export { SqliteTokenStore } from "./store/sqlite.js";
12
+ export type { GrantSummary, RevokeSelector, TokenKind, TokenRecord, TokenStore, } from "./store/types.js";