@tollbase/middleware 0.3.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tollbase contributors
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/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # @tollbase/middleware
2
+
3
+ Let AI agents become paying customers of your API or MCP server: verified agent
4
+ identity, human-approved spending caps, scoped keys, per-call metering, and
5
+ billing through your own Stripe account.
6
+
7
+ This is the vendor middleware. It mounts inside your existing server, serves
8
+ your agent-facing manifest, verifies agent tokens locally (your latency and
9
+ uptime never depend on the Tollbase control plane), enforces spending caps, and
10
+ streams idempotency-keyed usage for exact billing.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install @tollbase/middleware
16
+ ```
17
+
18
+ ## Integrate (Express)
19
+
20
+ ```ts
21
+ import { Tollbase } from "@tollbase/middleware";
22
+ import { tollbaseRouter, protect } from "@tollbase/middleware/express";
23
+
24
+ const tb = new Tollbase({ serviceSlug, serviceKey, controlPlaneUrl });
25
+ await tb.init();
26
+
27
+ app.use(tollbaseRouter(tb)); // the agent front door
28
+ app.post("/v1/enrich", protect(tb, "enrich", "enrich:read"), (req, res) => res.json(doWork(req.body)));
29
+ ```
30
+
31
+ High-value routes can opt into a synchronous ledger check:
32
+ `protect(tb, "bulk", "enrich:read", { strict: true, costCents: 30 })`.
33
+
34
+ ## MCP servers
35
+
36
+ ```ts
37
+ import { paidTool } from "@tollbase/middleware/mcp";
38
+ server.registerTool("enrich", schema, paidTool(tb, "enrich", "enrich:read", handler));
39
+ ```
40
+
41
+ ## How it behaves
42
+
43
+ - Local JWT verification against a cached JWKS; no network on the hot path.
44
+ - Optimistic cap enforcement from token claims, reconciled at every key refresh;
45
+ overspend bounded to one 15-minute token window by design.
46
+ - Usage streams asynchronously with client idempotency keys; at-least-once
47
+ delivery cannot double-bill.
48
+ - Revocation propagates through a pulled feed; in-flight tokens die within one
49
+ poll interval.
50
+
51
+ A Python SDK with identical behavior is available: `pip install tollbase`.
52
+
53
+ MIT.
@@ -0,0 +1,37 @@
1
+ import { Router, type Request, type Response, type NextFunction } from "express";
2
+ import type { Tollbase, VerifiedAgent } from "./index.js";
3
+ declare global {
4
+ namespace Express {
5
+ interface Request {
6
+ tollbase?: {
7
+ agent: VerifiedAgent;
8
+ meter: (tool: string, units?: number, costCents?: number) => void;
9
+ };
10
+ }
11
+ }
12
+ }
13
+ /**
14
+ * Mounts the agent-facing front door on the vendor's app:
15
+ * GET /.well-known/agent.json discovery manifest
16
+ * POST /agent/register proxied to the control plane
17
+ * POST /agent/keys proxied (mint/refresh; carries agent_secret or refresh_token)
18
+ * POST /agent/topup-request proxied
19
+ * GET /agent/accounts/:id/status proxied
20
+ * Agents interact with ONE origin (the vendor); Tollbase stays behind it.
21
+ */
22
+ export declare function tollbaseRouter(dm: Tollbase): Router;
23
+ export interface ProtectOptions {
24
+ /**
25
+ * strict: one synchronous cap check per call against the control plane —
26
+ * for high-value routes where exactness beats latency. Default false
27
+ * (optimistic local enforcement, bounded by one token TTL window).
28
+ */
29
+ strict?: boolean;
30
+ /** Override the per-call price; defaults to the plan's unit_cents. */
31
+ costCents?: number;
32
+ }
33
+ /**
34
+ * Protect a paid route: verify locally, check scope, gate against the cap,
35
+ * and auto-meter one unit. `requiredScope` maps the route to a plan scope.
36
+ */
37
+ export declare function protect(dm: Tollbase, tool: string, requiredScope: string, opts?: ProtectOptions): (req: Request, res: Response, next: NextFunction) => Promise<Response<any, Record<string, any>> | undefined>;
@@ -0,0 +1,68 @@
1
+ import { Router } from "express";
2
+ import express from "express";
3
+ /**
4
+ * Mounts the agent-facing front door on the vendor's app:
5
+ * GET /.well-known/agent.json discovery manifest
6
+ * POST /agent/register proxied to the control plane
7
+ * POST /agent/keys proxied (mint/refresh; carries agent_secret or refresh_token)
8
+ * POST /agent/topup-request proxied
9
+ * GET /agent/accounts/:id/status proxied
10
+ * Agents interact with ONE origin (the vendor); Tollbase stays behind it.
11
+ */
12
+ export function tollbaseRouter(dm) {
13
+ const r = Router();
14
+ r.use(express.json());
15
+ const cp = dm.cfg.controlPlaneUrl;
16
+ const proxy = (method, target) => async (req, res) => {
17
+ const headers = { "content-type": "application/json" };
18
+ const sig = req.header("signature-agent");
19
+ if (sig)
20
+ headers["signature-agent"] = sig; // forward Web Bot Auth identity
21
+ const upstream = await fetch(target(req), {
22
+ method,
23
+ headers,
24
+ body: method === "GET" ? undefined : JSON.stringify(req.body ?? {}),
25
+ });
26
+ res.status(upstream.status).json(await upstream.json());
27
+ };
28
+ r.get("/.well-known/agent.json", (_req, res) => {
29
+ if (!dm.manifest)
30
+ return res.status(503).json({ error: "manifest_not_loaded" });
31
+ res.json(dm.manifest);
32
+ });
33
+ r.post("/agent/register", proxy("POST", () => `${cp}/services/${dm.cfg.serviceSlug}/register`));
34
+ r.post("/agent/keys", proxy("POST", (req) => `${cp}/accounts/${req.body?.account_id}/keys`));
35
+ r.post("/agent/topup-request", proxy("POST", (req) => `${cp}/accounts/${req.body?.account_id}/topup-request`));
36
+ r.get("/agent/accounts/:id/status", proxy("GET", (req) => `${cp}/accounts/${req.params.id}/status`));
37
+ return r;
38
+ }
39
+ /**
40
+ * Protect a paid route: verify locally, check scope, gate against the cap,
41
+ * and auto-meter one unit. `requiredScope` maps the route to a plan scope.
42
+ */
43
+ export function protect(dm, tool, requiredScope, opts = {}) {
44
+ return async (req, res, next) => {
45
+ const agent = await dm.verify(req.header("authorization"));
46
+ if (!agent) {
47
+ return res
48
+ .status(401)
49
+ .json({ error: "missing_or_invalid_bearer_token", hint: "see /.well-known/agent.json" });
50
+ }
51
+ if (!agent.scopes.includes(requiredScope)) {
52
+ return res.status(403).json({ error: "insufficient_scope", required: requiredScope });
53
+ }
54
+ const cost = opts.costCents ?? dm.unitCents();
55
+ const gate = opts.strict ? await dm.gateStrict(agent, tool, cost) : dm.gate(agent, tool, cost);
56
+ if (!gate.ok)
57
+ return res.status(402).json(gate.body);
58
+ req.tollbase = {
59
+ agent,
60
+ meter: (t, units = 1, costCents = dm.unitCents()) => {
61
+ // Additional units beyond the auto-metered one (e.g. per-row pricing).
62
+ for (let i = 0; i < units; i++)
63
+ dm.gate(agent, t, costCents);
64
+ },
65
+ };
66
+ next();
67
+ };
68
+ }
@@ -0,0 +1,80 @@
1
+ import { err402, type Err402Body, type Manifest } from "@tollbase/core/types";
2
+ export interface TollbaseConfig {
3
+ serviceSlug: string;
4
+ serviceKey: string;
5
+ controlPlaneUrl: string;
6
+ /** Flush interval for the usage queue (ms). Default 2000. */
7
+ flushMs?: number;
8
+ /** Poll interval for the revocation feed (ms). Default 30000. 0 disables. */
9
+ revocationPollMs?: number;
10
+ /** Refresh interval for the service manifest (ms), so dashboard price
11
+ * edits reach running vendors without a restart. Default 60000. 0 disables. */
12
+ manifestRefreshMs?: number;
13
+ }
14
+ export interface VerifiedAgent {
15
+ accountId: string;
16
+ scopes: string[];
17
+ capCents: number;
18
+ iat: number;
19
+ jti: string;
20
+ }
21
+ /**
22
+ * Framework-agnostic middleware core. Design principle P1: the vendor's hot
23
+ * path never synchronously depends on the control plane.
24
+ * - Token verification is local (jose caches the JWKS).
25
+ * - Spend caps enforce optimistically from the `cap` claim minus local
26
+ * spend since mint; the control plane reconciles at every key refresh,
27
+ * bounding overspend to one 15-minute TTL window by design.
28
+ * - Revocation is a pull feed: an in-TTL token dies within one poll
29
+ * interval, still without a hot-path network call.
30
+ * - Usage streams asynchronously with client idempotency keys, so
31
+ * at-least-once delivery cannot double-bill.
32
+ */
33
+ export declare class Tollbase {
34
+ readonly cfg: TollbaseConfig;
35
+ private jwks;
36
+ manifest: Manifest | null;
37
+ private localSpend;
38
+ private queue;
39
+ private flushTimer;
40
+ private revokedJtis;
41
+ private revocationCursor;
42
+ private revocationTimer;
43
+ private manifestTimer;
44
+ constructor(cfg: TollbaseConfig);
45
+ /** Load the service manifest and start background loops. Call once at boot. */
46
+ init(): Promise<void>;
47
+ /** Fetch the current manifest; keep the previous one on transient failure. */
48
+ private refreshManifest;
49
+ unitCents(): number;
50
+ /** Local JWT verification; no network on the hot path. */
51
+ verify(authHeader: string | undefined): Promise<VerifiedAgent | null>;
52
+ /**
53
+ * Gate and meter one paid call (optimistic mode). Enforces the cap claim
54
+ * minus local spend since mint.
55
+ */
56
+ gate(agent: VerifiedAgent, tool: string, costCents?: number): {
57
+ ok: true;
58
+ } | {
59
+ ok: false;
60
+ body: Err402Body;
61
+ };
62
+ /**
63
+ * Strict-mode gate for high-value routes: one synchronous cap check against
64
+ * the control plane before allowing the call. Trades ~one round trip for
65
+ * exactness; falls CLOSED on control-plane failure (a strict route would
66
+ * rather refuse than overspend).
67
+ */
68
+ gateStrict(agent: VerifiedAgent, tool: string, costCents?: number): Promise<{
69
+ ok: true;
70
+ } | {
71
+ ok: false;
72
+ body: Err402Body;
73
+ }>;
74
+ challenge(accountId: string, reason: Parameters<typeof err402>[0]): Err402Body;
75
+ private enqueue;
76
+ /** At-least-once delivery; server-side idempotency makes redelivery safe. */
77
+ flush(): Promise<void>;
78
+ private pollRevocations;
79
+ stop(): Promise<void>;
80
+ }
package/dist/index.js ADDED
@@ -0,0 +1,200 @@
1
+ import { createRemoteJWKSet, jwtVerify } from "jose";
2
+ import { randomUUID } from "node:crypto";
3
+ import { err402 } from "@tollbase/core/types";
4
+ /**
5
+ * Framework-agnostic middleware core. Design principle P1: the vendor's hot
6
+ * path never synchronously depends on the control plane.
7
+ * - Token verification is local (jose caches the JWKS).
8
+ * - Spend caps enforce optimistically from the `cap` claim minus local
9
+ * spend since mint; the control plane reconciles at every key refresh,
10
+ * bounding overspend to one 15-minute TTL window by design.
11
+ * - Revocation is a pull feed: an in-TTL token dies within one poll
12
+ * interval, still without a hot-path network call.
13
+ * - Usage streams asynchronously with client idempotency keys, so
14
+ * at-least-once delivery cannot double-bill.
15
+ */
16
+ export class Tollbase {
17
+ cfg;
18
+ jwks;
19
+ manifest = null;
20
+ localSpend = new Map(); // token jti -> cents spent since mint
21
+ queue = [];
22
+ flushTimer = null;
23
+ revokedJtis = new Set();
24
+ revocationCursor = "1970-01-01";
25
+ revocationTimer = null;
26
+ manifestTimer = null;
27
+ constructor(cfg) {
28
+ this.cfg = cfg;
29
+ this.jwks = createRemoteJWKSet(new URL(`${cfg.controlPlaneUrl}/jwks.json`));
30
+ }
31
+ /** Load the service manifest and start background loops. Call once at boot. */
32
+ async init() {
33
+ await this.refreshManifest();
34
+ if (!this.manifest)
35
+ throw new Error(`tollbase: cannot load manifest for ${this.cfg.serviceSlug}`);
36
+ this.flushTimer = setInterval(() => void this.flush(), this.cfg.flushMs ?? 2000);
37
+ this.flushTimer.unref?.();
38
+ const mrMs = this.cfg.manifestRefreshMs ?? 60_000;
39
+ if (mrMs > 0) {
40
+ this.manifestTimer = setInterval(() => void this.refreshManifest(), mrMs);
41
+ this.manifestTimer.unref?.();
42
+ }
43
+ const pollMs = this.cfg.revocationPollMs ?? 30_000;
44
+ if (pollMs > 0) {
45
+ this.revocationTimer = setInterval(() => void this.pollRevocations(), pollMs);
46
+ this.revocationTimer.unref?.();
47
+ await this.pollRevocations(); // pick up pre-existing revocations at boot
48
+ }
49
+ }
50
+ /** Fetch the current manifest; keep the previous one on transient failure. */
51
+ async refreshManifest() {
52
+ try {
53
+ const r = await fetch(`${this.cfg.controlPlaneUrl}/services/${this.cfg.serviceSlug}/manifest`);
54
+ if (r.ok)
55
+ this.manifest = (await r.json());
56
+ }
57
+ catch {
58
+ // Control plane unreachable: the cached manifest keeps serving (P1).
59
+ }
60
+ }
61
+ unitCents() {
62
+ return this.manifest?.plans[0]?.unit_cents ?? 0;
63
+ }
64
+ /** Local JWT verification; no network on the hot path. */
65
+ async verify(authHeader) {
66
+ if (!authHeader?.startsWith("Bearer "))
67
+ return null;
68
+ try {
69
+ const { payload } = await jwtVerify(authHeader.slice(7), this.jwks, {
70
+ issuer: "tollbase",
71
+ algorithms: ["ES256"],
72
+ });
73
+ if (payload.svc !== this.cfg.serviceSlug)
74
+ return null;
75
+ const jti = String(payload.jti ?? "");
76
+ if (this.revokedJtis.has(jti))
77
+ return null;
78
+ return {
79
+ accountId: payload.sub,
80
+ scopes: (typeof payload.scope === "string" ? payload.scope : "").split(" ").filter(Boolean),
81
+ capCents: Number(payload.cap ?? 0),
82
+ iat: Number(payload.iat),
83
+ jti,
84
+ };
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ }
90
+ /**
91
+ * Gate and meter one paid call (optimistic mode). Enforces the cap claim
92
+ * minus local spend since mint.
93
+ */
94
+ gate(agent, tool, costCents = this.unitCents()) {
95
+ // Keyed by jti: each minted token carries its own cap claim and its own
96
+ // local ledger (iat has one-second resolution and can collide on rotation).
97
+ const key = agent.jti;
98
+ const spent = this.localSpend.get(key) ?? 0;
99
+ if (spent + costCents > agent.capCents) {
100
+ return { ok: false, body: this.challenge(agent.accountId, "cap_exceeded") };
101
+ }
102
+ this.localSpend.set(key, spent + costCents);
103
+ this.enqueue(agent.accountId, tool, 1, costCents);
104
+ return { ok: true };
105
+ }
106
+ /**
107
+ * Strict-mode gate for high-value routes: one synchronous cap check against
108
+ * the control plane before allowing the call. Trades ~one round trip for
109
+ * exactness; falls CLOSED on control-plane failure (a strict route would
110
+ * rather refuse than overspend).
111
+ */
112
+ async gateStrict(agent, tool, costCents = this.unitCents()) {
113
+ // Drain this instance's own metering queue first so the ledger the
114
+ // control plane consults reflects everything we already allowed.
115
+ await this.flush();
116
+ try {
117
+ const r = await fetch(`${this.cfg.controlPlaneUrl}/internal/cap-check`, {
118
+ method: "POST",
119
+ headers: { "content-type": "application/json", "x-tollbase-service-key": this.cfg.serviceKey },
120
+ body: JSON.stringify({ account_id: agent.accountId, cost_cents: costCents }),
121
+ });
122
+ const body = (await r.json());
123
+ if (!r.ok || !body.allowed)
124
+ return { ok: false, body: this.challenge(agent.accountId, "cap_exceeded") };
125
+ }
126
+ catch {
127
+ return { ok: false, body: this.challenge(agent.accountId, "cap_exceeded") };
128
+ }
129
+ // Record the spend locally as well, so optimistic gates on the same
130
+ // token account for what strict routes consumed.
131
+ const key = agent.jti;
132
+ this.localSpend.set(key, (this.localSpend.get(key) ?? 0) + costCents);
133
+ this.enqueue(agent.accountId, tool, 1, costCents);
134
+ return { ok: true };
135
+ }
136
+ challenge(accountId, reason) {
137
+ return err402(reason, {
138
+ accountId,
139
+ topupUrl: "/agent/topup-request", // vendor-origin proxy path; agents keep one origin
140
+ plans: this.manifest?.plans ?? [],
141
+ });
142
+ }
143
+ enqueue(accountId, tool, units, costCents) {
144
+ this.queue.push({
145
+ idempotency_key: randomUUID(),
146
+ account_id: accountId,
147
+ tool,
148
+ units,
149
+ cost_cents: costCents,
150
+ occurred_at: new Date().toISOString(),
151
+ });
152
+ if (this.queue.length >= 20)
153
+ void this.flush();
154
+ }
155
+ /** At-least-once delivery; server-side idempotency makes redelivery safe. */
156
+ async flush() {
157
+ if (this.queue.length === 0)
158
+ return;
159
+ const batch = this.queue.splice(0, this.queue.length);
160
+ try {
161
+ const r = await fetch(`${this.cfg.controlPlaneUrl}/usage`, {
162
+ method: "POST",
163
+ headers: { "content-type": "application/json", "x-tollbase-service-key": this.cfg.serviceKey },
164
+ body: JSON.stringify({ events: batch }),
165
+ });
166
+ if (!r.ok)
167
+ throw new Error(`usage ingest ${r.status}`);
168
+ }
169
+ catch {
170
+ this.queue.unshift(...batch); // retry on the next tick; dedup on the server
171
+ }
172
+ }
173
+ async pollRevocations() {
174
+ try {
175
+ const r = await fetch(`${this.cfg.controlPlaneUrl}/revocations?since=${encodeURIComponent(this.revocationCursor)}`, { headers: { "x-tollbase-service-key": this.cfg.serviceKey } });
176
+ if (!r.ok)
177
+ return;
178
+ const body = (await r.json());
179
+ for (const rec of body.revocations)
180
+ this.revokedJtis.add(rec.jti);
181
+ if (body.revocations.length) {
182
+ const last = body.revocations[body.revocations.length - 1];
183
+ if (last)
184
+ this.revocationCursor = last.revoked_at;
185
+ }
186
+ }
187
+ catch {
188
+ // Transient failure: keep the previous cursor; the next poll catches up.
189
+ }
190
+ }
191
+ async stop() {
192
+ if (this.flushTimer)
193
+ clearInterval(this.flushTimer);
194
+ if (this.revocationTimer)
195
+ clearInterval(this.revocationTimer);
196
+ if (this.manifestTimer)
197
+ clearInterval(this.manifestTimer);
198
+ await this.flush();
199
+ }
200
+ }
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ import type { Tollbase } from "./index.js";
2
+ /**
3
+ * MCP integration without a hard dependency on any MCP SDK version.
4
+ *
5
+ * MCP servers speak JSON-RPC over a transport that carries HTTP headers
6
+ * (Streamable HTTP). This wrapper factors the Tollbase concerns — verify,
7
+ * scope, gate, meter — into a single function the vendor applies to each
8
+ * paid tool handler, whatever SDK they use:
9
+ *
10
+ * server.registerTool("enrich", schema,
11
+ * paidTool(dm, "enrich", "enrich:read", async (args) => doEnrich(args)));
12
+ *
13
+ * The bearer token is read from the request's Authorization header (exposed
14
+ * by MCP SDKs via the per-request extra/context object; pass an accessor if
15
+ * yours differs). On a cap violation the tool returns an MCP tool error
16
+ * whose text is the machine-readable 402 JSON, so agent frameworks can parse
17
+ * the same payload they would receive over plain HTTP.
18
+ */
19
+ export interface McpToolResult {
20
+ content: {
21
+ type: "text";
22
+ text: string;
23
+ }[];
24
+ isError?: boolean;
25
+ }
26
+ export declare function paidTool<Args>(dm: Tollbase, tool: string, requiredScope: string, handler: (args: Args) => Promise<McpToolResult | {
27
+ [k: string]: unknown;
28
+ }>, getAuthHeader?: (extra: unknown) => string | undefined): (args: Args, extra?: unknown) => Promise<McpToolResult>;
package/dist/mcp.js ADDED
@@ -0,0 +1,38 @@
1
+ import { err402 } from "@tollbase/core/types";
2
+ export function paidTool(dm, tool, requiredScope, handler, getAuthHeader = defaultAuthAccessor) {
3
+ return async (args, extra) => {
4
+ const agent = await dm.verify(getAuthHeader(extra));
5
+ if (!agent) {
6
+ return mcpError({
7
+ ...err402("not_claimed", { plans: dm.manifest?.plans ?? [] }),
8
+ message: "Missing or invalid bearer token. Register via the service manifest.",
9
+ });
10
+ }
11
+ if (!agent.scopes.includes(requiredScope)) {
12
+ return mcpError({
13
+ ...dm.challenge(agent.accountId, "no_plan"),
14
+ message: `Scope ${requiredScope} not granted.`,
15
+ });
16
+ }
17
+ const gate = dm.gate(agent, tool);
18
+ if (!gate.ok)
19
+ return mcpError(gate.body);
20
+ const out = await handler(args);
21
+ // Pass through well-formed MCP results; wrap plain objects as text.
22
+ if (out && typeof out === "object" && "content" in out)
23
+ return out;
24
+ return { content: [{ type: "text", text: JSON.stringify(out) }] };
25
+ };
26
+ }
27
+ function mcpError(body) {
28
+ return { content: [{ type: "text", text: JSON.stringify(body) }], isError: true };
29
+ }
30
+ /**
31
+ * Default accessor for the Authorization header across common MCP SDK shapes
32
+ * ({requestInfo:{headers}} in the TypeScript SDK's Streamable HTTP transport).
33
+ */
34
+ function defaultAuthAccessor(extra) {
35
+ const e = extra;
36
+ const h = e?.requestInfo?.headers?.authorization ?? e?.requestInfo?.headers?.Authorization;
37
+ return Array.isArray(h) ? h[0] : h;
38
+ }
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@tollbase/middleware",
3
+ "version": "0.3.0",
4
+ "description": "Tollbase vendor middleware: let AI agents become paying customers of your API or MCP server.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "exports": {
8
+ ".": {
9
+ "source": "./src/index.ts",
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./express": {
14
+ "source": "./src/express.ts",
15
+ "types": "./dist/express.d.ts",
16
+ "default": "./dist/express.js"
17
+ },
18
+ "./mcp": {
19
+ "source": "./src/mcp.ts",
20
+ "types": "./dist/mcp.d.ts",
21
+ "default": "./dist/mcp.js"
22
+ }
23
+ },
24
+ "dependencies": {
25
+ "@tollbase/core": "0.3.0",
26
+ "jose": "^5.9.2"
27
+ },
28
+ "peerDependencies": {
29
+ "express": "^4.19.2"
30
+ },
31
+ "peerDependenciesMeta": {
32
+ "express": {
33
+ "optional": true
34
+ }
35
+ },
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/tollbase/tollbase.git"
39
+ },
40
+ "homepage": "https://tollbase.dev",
41
+ "keywords": [
42
+ "ai-agents",
43
+ "agents",
44
+ "billing",
45
+ "metering",
46
+ "mcp",
47
+ "payments",
48
+ "402",
49
+ "usage-based"
50
+ ],
51
+ "engines": {
52
+ "node": ">=20"
53
+ },
54
+ "publishConfig": {
55
+ "access": "public"
56
+ },
57
+ "files": [
58
+ "dist",
59
+ "README.md",
60
+ "LICENSE"
61
+ ],
62
+ "scripts": {
63
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
64
+ "prepublishOnly": "npm run build"
65
+ }
66
+ }