typed-bridge 4.0.3 → 4.0.4
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/dist/bridge/index.js +0 -4
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1 -4
- package/dist/mcp/index.js +3 -5
- package/dist/middleware/index.js +11 -14
- package/package.json +1 -1
- package/readme.md +3 -3
- package/dist/httpError.d.ts +0 -5
- package/dist/httpError.js +0 -18
package/dist/bridge/index.js
CHANGED
|
@@ -138,10 +138,6 @@ const bridgeHandler = (bridge) => async (req, res) => {
|
|
|
138
138
|
const errorMessage = (keyPath ? keyPath + ': ' : '') + error.issues[0].message;
|
|
139
139
|
return res.status(400).send(errorMessage);
|
|
140
140
|
}
|
|
141
|
-
// Errors carrying an explicit HTTP status (e.g. HttpError for auth/permission
|
|
142
|
-
// failures) are expected, so respond with that status without logging as an error.
|
|
143
|
-
if (typeof error?.status === 'number')
|
|
144
|
-
return res.status(error.status).json({ error: error.message });
|
|
145
141
|
if (__1.tbConfig.logs.error)
|
|
146
142
|
console.error(`ERROR | ${id} ::`, error);
|
|
147
143
|
return res.status(500).json({ error: error.message });
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export { Application, default as express, Express, NextFunction, Request, Response, Router } from 'express';
|
|
2
2
|
export { createBridge, onShutdown } from './bridge';
|
|
3
3
|
export { config as tbConfig } from './config';
|
|
4
|
-
export { HttpError, httpError } from './httpError';
|
|
5
4
|
export { createMiddleware } from './middleware';
|
|
6
5
|
export type { Middleware } from './middleware';
|
|
7
6
|
export { mountMCP } from './mcp';
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.z = exports.TOOL_MODES = exports.isToolMode = exports.handleToolCall = exports.getTools = exports.defineEntry = exports.defineBridge = exports.mountMCP = exports.createMiddleware = exports.
|
|
6
|
+
exports.z = exports.TOOL_MODES = exports.isToolMode = exports.handleToolCall = exports.getTools = exports.defineEntry = exports.defineBridge = exports.mountMCP = exports.createMiddleware = exports.tbConfig = exports.onShutdown = exports.createBridge = exports.Router = exports.express = void 0;
|
|
7
7
|
var express_1 = require("express");
|
|
8
8
|
Object.defineProperty(exports, "express", { enumerable: true, get: function () { return __importDefault(express_1).default; } });
|
|
9
9
|
Object.defineProperty(exports, "Router", { enumerable: true, get: function () { return express_1.Router; } });
|
|
@@ -12,9 +12,6 @@ Object.defineProperty(exports, "createBridge", { enumerable: true, get: function
|
|
|
12
12
|
Object.defineProperty(exports, "onShutdown", { enumerable: true, get: function () { return bridge_1.onShutdown; } });
|
|
13
13
|
var config_1 = require("./config");
|
|
14
14
|
Object.defineProperty(exports, "tbConfig", { enumerable: true, get: function () { return config_1.config; } });
|
|
15
|
-
var httpError_1 = require("./httpError");
|
|
16
|
-
Object.defineProperty(exports, "HttpError", { enumerable: true, get: function () { return httpError_1.HttpError; } });
|
|
17
|
-
Object.defineProperty(exports, "httpError", { enumerable: true, get: function () { return httpError_1.httpError; } });
|
|
18
15
|
var middleware_1 = require("./middleware");
|
|
19
16
|
Object.defineProperty(exports, "createMiddleware", { enumerable: true, get: function () { return middleware_1.createMiddleware; } });
|
|
20
17
|
var mcp_1 = require("./mcp");
|
package/dist/mcp/index.js
CHANGED
|
@@ -38,12 +38,10 @@ function createMCPServer(bridge, entries, headersRef, toolMode = 'on_demand') {
|
|
|
38
38
|
return { content: [{ type: 'text', text: JSON.stringify(result) ?? '' }] };
|
|
39
39
|
}
|
|
40
40
|
catch (error) {
|
|
41
|
-
// Surface the
|
|
42
|
-
// (e.g.
|
|
41
|
+
// Surface the error message as JSON so the model knows *why* a call was denied
|
|
42
|
+
// (e.g. an auth middleware's message) instead of seeing an opaque stop.
|
|
43
43
|
const message = error instanceof Error ? error.message : String(error);
|
|
44
|
-
|
|
45
|
-
const payload = status ? { status, error: message } : { error: message };
|
|
46
|
-
return { content: [{ type: 'text', text: JSON.stringify(payload) }], isError: true };
|
|
44
|
+
return { content: [{ type: 'text', text: JSON.stringify({ error: message }) }], isError: true };
|
|
47
45
|
}
|
|
48
46
|
});
|
|
49
47
|
return server;
|
package/dist/middleware/index.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runMiddlewaresForTool = exports.runMiddlewares = exports.createMiddleware = void 0;
|
|
4
4
|
const helpers_1 = require("../helpers");
|
|
5
|
-
const httpError_1 = require("../httpError");
|
|
6
5
|
const middlewares = [];
|
|
7
6
|
const createMiddleware = (pattern, handler) => middlewares.push({ pattern, handler });
|
|
8
7
|
exports.createMiddleware = createMiddleware;
|
|
@@ -28,19 +27,17 @@ const runMiddlewares = async (name, req, res) => {
|
|
|
28
27
|
exports.runMiddlewares = runMiddlewares;
|
|
29
28
|
// A stand-in for the Express response on tool surfaces (MCP / LLM), which have no real
|
|
30
29
|
// HTTP response. A middleware that blocks with `res.status(code).send(msg)` (or `.json`)
|
|
31
|
-
// throws
|
|
32
|
-
//
|
|
30
|
+
// throws a plain Error carrying that message — so the surface can report *why* access was
|
|
31
|
+
// denied (the message) back to the model, instead of a meaningless silent stop. The status
|
|
32
|
+
// code is irrelevant off HTTP, so it's ignored.
|
|
33
33
|
const createToolRes = () => {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
},
|
|
38
|
-
json: (body) => {
|
|
39
|
-
throw new httpError_1.HttpError(status, typeof body === 'string' ? body : JSON.stringify(body));
|
|
40
|
-
}
|
|
41
|
-
});
|
|
34
|
+
const fail = (body) => {
|
|
35
|
+
throw new Error(typeof body === 'string' ? body : JSON.stringify(body));
|
|
36
|
+
};
|
|
42
37
|
const res = {
|
|
43
|
-
status: (
|
|
38
|
+
status: () => res,
|
|
39
|
+
send: fail,
|
|
40
|
+
json: fail,
|
|
44
41
|
setHeader: () => res,
|
|
45
42
|
set: () => res
|
|
46
43
|
};
|
|
@@ -51,7 +48,7 @@ const createToolRes = () => {
|
|
|
51
48
|
// (e.g. `req.path.split('/').pop()`) work identically to HTTP. There is no request body:
|
|
52
49
|
// MCP can forward nothing else, so middlewares must rely on headers/path alone.
|
|
53
50
|
// Blocking via `res.status().send()` throws (caught by the caller and returned as JSON);
|
|
54
|
-
// a bare `{ next: false }` with no response throws a generic
|
|
51
|
+
// a bare `{ next: false }` with no response throws a generic error so access is still denied.
|
|
55
52
|
const runMiddlewaresForTool = async (name, headers) => {
|
|
56
53
|
// `path` mirrors HTTP's last-segment convention: on HTTP `req.path` is `/bridge/user.fetch`
|
|
57
54
|
// and handlers do `.split('/').pop()`; here it's the bare entry name, which pops to itself.
|
|
@@ -59,7 +56,7 @@ const runMiddlewaresForTool = async (name, headers) => {
|
|
|
59
56
|
const res = createToolRes();
|
|
60
57
|
const { blocked, context } = await (0, exports.runMiddlewares)(name, req, res);
|
|
61
58
|
if (blocked)
|
|
62
|
-
throw new
|
|
59
|
+
throw new Error('Access denied');
|
|
63
60
|
return context;
|
|
64
61
|
};
|
|
65
62
|
exports.runMiddlewaresForTool = runMiddlewaresForTool;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "typed-bridge",
|
|
3
3
|
"description": "Type-safe server functions for TypeScript that also expose your backend as an MCP server and LLM tools for AI agents",
|
|
4
|
-
"version": "4.0.
|
|
4
|
+
"version": "4.0.4",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"author": "neilveil",
|
package/readme.md
CHANGED
|
@@ -223,7 +223,7 @@ createMiddleware('user.*', async (req, res) => {
|
|
|
223
223
|
createBridge(bridge, 8080, '/bridge', { entries, mcp: true })
|
|
224
224
|
```
|
|
225
225
|
|
|
226
|
-
The resolved context lands in every handler as the second argument, identically for a browser request and an agent's tool call — one security model for humans and AI. When a middleware blocks a tool call, the model receives the **
|
|
226
|
+
The resolved context lands in every handler as the second argument, identically for a browser request and an agent's tool call — one security model for humans and AI. When a middleware blocks a tool call, the model receives the **message as JSON** (e.g. `{ "error": "Unauthorized" }`) so it knows *why* it was denied. On tool surfaces a middleware sees the request **headers** and the matched entry name as **`req.path`** (so path-based key derivation like `req.path.split('/').pop()` works everywhere) — but no request **body**, since MCP can forward nothing else.
|
|
227
227
|
|
|
228
228
|
### Control how many tools the client sees
|
|
229
229
|
|
|
@@ -292,7 +292,7 @@ const result = await handleToolCall(bridge, entries, toolCall, { headers: req.he
|
|
|
292
292
|
|
|
293
293
|
Because you pass `toolMode` explicitly, a single process can run one assistant with `attach_all` and another with `on_demand`. `handleToolCall` needs no mode at all — it dispatches on the tool name (a meta-tool name runs the discovery flow, anything else runs that entry directly), so your loop is written once and never changes when you switch modes.
|
|
294
294
|
|
|
295
|
-
Your `createMiddleware` chain runs on these tool calls just like it does on HTTP and MCP. Unlike MCP — where the server already holds the client's headers — an LLM loop is your own code, so you pass the request `headers` into `handleToolCall` to drive the chain. The middleware-derived context is merged on top of any `context` you also pass. If a needed header is missing, the matching middleware denies the call (the model receives a JSON `{
|
|
295
|
+
Your `createMiddleware` chain runs on these tool calls just like it does on HTTP and MCP. Unlike MCP — where the server already holds the client's headers — an LLM loop is your own code, so you pass the request `headers` into `handleToolCall` to drive the chain. The middleware-derived context is merged on top of any `context` you also pass. If a needed header is missing, the matching middleware denies the call (the model receives a JSON `{ error }`), exactly as it would over HTTP.
|
|
296
296
|
|
|
297
297
|
### `on_demand` — three tools, discovered as needed (default)
|
|
298
298
|
|
|
@@ -372,7 +372,7 @@ GraphQL is unmatched when clients need to shape their own queries across a compl
|
|
|
372
372
|
|
|
373
373
|
## Middleware when you need it
|
|
374
374
|
|
|
375
|
-
Pattern based middleware runs before handlers and can inject context. The **same chain runs on all three surfaces** — HTTP requests, MCP tool calls, and LLM tool calls — matched by entry name (e.g. `user.fetch` matches `user.*`). Over HTTP a middleware blocks by writing to `res`; on tool calls that same `res.status(code).send(msg)` is reported back to the model as JSON (`{
|
|
375
|
+
Pattern based middleware runs before handlers and can inject context. The **same chain runs on all three surfaces** — HTTP requests, MCP tool calls, and LLM tool calls — matched by entry name (e.g. `user.fetch` matches `user.*`). Over HTTP a middleware blocks by writing to `res`; on tool calls that same `res.status(code).send(msg)` is reported back to the model as JSON (`{ error }`) — the message reaches the model, the status code (irrelevant off HTTP) is dropped. On tool surfaces a middleware sees `req.headers` and `req.path` (the matched entry name) but **no `req.body`** and no real `res` for side effects (cookies, custom headers are no-ops), so write auth/context logic against `req.headers` / `req.path`:
|
|
376
376
|
|
|
377
377
|
```ts
|
|
378
378
|
import { createMiddleware } from 'typed-bridge'
|
package/dist/httpError.d.ts
DELETED
package/dist/httpError.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.httpError = exports.HttpError = void 0;
|
|
4
|
-
// Error carrying an explicit HTTP status code. When thrown from a bridge handler
|
|
5
|
-
// or middleware, createBridge responds with the given status instead of 500.
|
|
6
|
-
// Useful for auth/permission failures (401/403), not-found (404), etc.
|
|
7
|
-
class HttpError extends Error {
|
|
8
|
-
status;
|
|
9
|
-
constructor(status, message) {
|
|
10
|
-
super(message);
|
|
11
|
-
this.name = 'HttpError';
|
|
12
|
-
this.status = status;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
exports.HttpError = HttpError;
|
|
16
|
-
// Convenience constructor for `throw httpError(403, 'Denied')`.
|
|
17
|
-
const httpError = (status, message) => new HttpError(status, message);
|
|
18
|
-
exports.httpError = httpError;
|