@ganintegrity/mcp 1.0.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/README.md +375 -0
- package/dist/als.d.ts +24 -0
- package/dist/als.d.ts.map +1 -0
- package/dist/als.js +3 -0
- package/dist/als.js.map +1 -0
- package/dist/auth/auth.types.d.ts +29 -0
- package/dist/auth/auth.types.d.ts.map +1 -0
- package/dist/auth/auth.types.js +2 -0
- package/dist/auth/auth.types.js.map +1 -0
- package/dist/auth/index.d.ts +29 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +74 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/errors/errors.types.d.ts +34 -0
- package/dist/errors/errors.types.d.ts.map +1 -0
- package/dist/errors/errors.types.js +2 -0
- package/dist/errors/errors.types.js.map +1 -0
- package/dist/errors/index.d.ts +27 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +78 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/server/index.d.ts +18 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +130 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/server.types.d.ts +78 -0
- package/dist/server/server.types.d.ts.map +1 -0
- package/dist/server/server.types.js +2 -0
- package/dist/server/server.types.js.map +1 -0
- package/dist/tool/index.d.ts +26 -0
- package/dist/tool/index.d.ts.map +1 -0
- package/dist/tool/index.js +88 -0
- package/dist/tool/index.js.map +1 -0
- package/dist/tool/tool.types.d.ts +72 -0
- package/dist/tool/tool.types.d.ts.map +1 -0
- package/dist/tool/tool.types.js +2 -0
- package/dist/tool/tool.types.js.map +1 -0
- package/package.json +65 -0
package/README.md
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
# @ganintegrity/mcp
|
|
2
|
+
|
|
3
|
+
A small library for mounting a [Model Context Protocol](https://modelcontextprotocol.io) server onto an Express app inside a Gan Integrity service. It owns the MCP plumbing — Streamable-HTTP transport wiring, per-request scoping, tool-result envelopes — and **inverts the dependencies** that vary per service: auth, logging, postgan construction, and the application's own error types are all injected by the caller.
|
|
4
|
+
|
|
5
|
+
If you've worked on a `src/mcp/scaffold/` folder before, this is the library extraction of that pattern.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Why this library exists
|
|
10
|
+
|
|
11
|
+
The MCP TypeScript SDK gives you a `McpServer` and a `StreamableHTTPServerTransport`, but several things sit awkwardly between the SDK and a real Express service:
|
|
12
|
+
|
|
13
|
+
1. **The transport is single-use.** `StreamableHTTPServerTransport` (in stateless mode) cannot be reused across requests. So you can't just instantiate one `McpServer` at boot and forward requests to it — you need a fresh server+transport per HTTP call, with the same tools registered on each one.
|
|
14
|
+
|
|
15
|
+
2. **Tool handlers need request-scoped state.** A tool needs the authenticated user, a database client (postgan), the request's session id, and a correlation logger — none of which the MCP SDK knows about. Threading these through every tool call is noisy; AsyncLocalStorage is the natural fit.
|
|
16
|
+
|
|
17
|
+
3. **Tool calls want database transactions.** Each tool should run inside its own transaction so that a failure rolls back automatically. This is boilerplate every consumer would write the same way.
|
|
18
|
+
|
|
19
|
+
4. **Errors need to become MCP envelopes.** When a tool throws, the agent on the other end should see a structured `CallToolResult` with `code`/`message`/`details`, not a stack trace. The library handles the envelope; your service supplies a small `errorMapper` callback that translates whatever it throws into a shape the library can emit.
|
|
20
|
+
|
|
21
|
+
This library packages (1)–(4) and asks the caller to plug in the parts that genuinely differ between services.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Concepts
|
|
26
|
+
|
|
27
|
+
A two-minute mental model before you read the API:
|
|
28
|
+
|
|
29
|
+
### The recording shim
|
|
30
|
+
|
|
31
|
+
`createMcpServer()` returns a `server` object that _looks_ like an `McpServer` but only implements `registerTool` — it records every registration in an in-memory list. On each HTTP request, `mount()` instantiates a brand-new `McpServer`, replays the recorded registrations onto it, hands it a fresh transport, and tears both down when the response finishes. This is how we satisfy the SDK's single-use transport contract without re-registering tools on every call site.
|
|
32
|
+
|
|
33
|
+
You register tools once at boot time. The library handles the per-request lifecycle.
|
|
34
|
+
|
|
35
|
+
### The ALS scope
|
|
36
|
+
|
|
37
|
+
Inside `mount()`, every tool dispatch runs inside an `AsyncLocalStorage.run(...)` scope holding a `RequestStore` (user, postgan, sessionId, logger). The `tool()` helper reads from this store to build a `ToolContext` for your handler. Your handler signature is `(args, ctx) => Promise<Output>` — no Express objects leak in, no DI container needed.
|
|
38
|
+
|
|
39
|
+
### Per-tool transactions
|
|
40
|
+
|
|
41
|
+
When a tool fires, the helper opens a `Postgan.Transaction`, runs your handler, and commits on success or rolls back on throw. Your handler just calls `ctx.transaction.query(...)` (or `ctx.postgan.something.do(...)`) and never has to think about commit/rollback.
|
|
42
|
+
|
|
43
|
+
### Error envelopes
|
|
44
|
+
|
|
45
|
+
When a handler throws, the library passes the throwable to your `errorMapper`. If the mapper returns an `McpToolError` shape, the library produces a `CallToolResult` with that `code`/`message`/`details`, logging at info (`severity: "warn"`) or error (`severity: "error"`). If the mapper returns `null` — or if you didn't supply one — the library emits a generic `INTERNAL_ERROR` envelope with a redacted message and logs the original throwable at error level.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pnpm add @ganintegrity/mcp
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Peer dependencies (must exist in your service):
|
|
56
|
+
|
|
57
|
+
| Peer | Range |
|
|
58
|
+
| ----------------------------------- | ----- |
|
|
59
|
+
| `@ganintegrity/postgan` | `^28` |
|
|
60
|
+
| `@ganintegrity/postgres-migrations` | `^16` |
|
|
61
|
+
| `express` | `^5` |
|
|
62
|
+
| `pino` | `^10` |
|
|
63
|
+
| `zod` | `^4` |
|
|
64
|
+
|
|
65
|
+
The library does not bundle any of these — your service is expected to already have them.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Quick start
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
// src/mcp/bootstrap.ts
|
|
73
|
+
import express from "express";
|
|
74
|
+
import { createMcpServer, tool, type McpErrorMapper } from "@ganintegrity/mcp";
|
|
75
|
+
import { z } from "zod";
|
|
76
|
+
|
|
77
|
+
import { logger } from "../logger.ts";
|
|
78
|
+
import { AppError } from "../errors.ts";
|
|
79
|
+
import { decipherToken } from "../middleware/verify.js";
|
|
80
|
+
import identity from "../middleware/identity/identitymodel.js";
|
|
81
|
+
import setupPostgan from "../middleware/setupPostgan.js";
|
|
82
|
+
|
|
83
|
+
const errorMapper: McpErrorMapper = (err) => {
|
|
84
|
+
if (err instanceof AppError) {
|
|
85
|
+
return {
|
|
86
|
+
code: err.code,
|
|
87
|
+
message: err.message,
|
|
88
|
+
severity: err.statusCode >= 500 ? "error" : "warn",
|
|
89
|
+
details: err.details,
|
|
90
|
+
cause: err.cause,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const { server, mount } = createMcpServer({
|
|
97
|
+
name: "tprm-mcp",
|
|
98
|
+
version: "1.0.0",
|
|
99
|
+
logger,
|
|
100
|
+
setupPostgan: setupPostgan(),
|
|
101
|
+
tokenToUser: async (token) => {
|
|
102
|
+
const decoded = await decipherToken(token);
|
|
103
|
+
return identity.construct({ ...decoded, id: decoded._id });
|
|
104
|
+
},
|
|
105
|
+
errorMapper,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Register tools at boot.
|
|
109
|
+
tool(server, {
|
|
110
|
+
name: "summarise_entity",
|
|
111
|
+
description: "Produce a short summary of an entity by uuid.",
|
|
112
|
+
inputSchema: z.object({ uuid: z.string().uuid() }),
|
|
113
|
+
handler: async ({ uuid }, ctx) => {
|
|
114
|
+
const entity = await ctx.postgan.entity.fetch({ uuid });
|
|
115
|
+
return { summary: entity.name, fetchedBy: ctx.user.id };
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Mount onto an Express router.
|
|
120
|
+
const router = express.Router();
|
|
121
|
+
await mount(router);
|
|
122
|
+
app.use("/mcp", router);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
That's it. The MCP server is reachable at `POST /mcp/`, will authenticate via `Authorization: Bearer <token>` (or `X-Access-Token`), and will execute tools inside a postgan transaction with full ALS context.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## API reference
|
|
130
|
+
|
|
131
|
+
### `createMcpServer(options)`
|
|
132
|
+
|
|
133
|
+
Builds an MCP server and returns a `{ server, mount }` pair.
|
|
134
|
+
|
|
135
|
+
**Options:**
|
|
136
|
+
|
|
137
|
+
| Field | Type | Description |
|
|
138
|
+
| -------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
139
|
+
| `name` | `string` | MCP server name advertised to clients. |
|
|
140
|
+
| `version` | `string` | MCP server version advertised to clients. |
|
|
141
|
+
| `logger` | `pino.Logger` | Service logger. The library calls `logger.child({ component: "mcp" })` internally. |
|
|
142
|
+
| `tokenToUser` | `(token: string) => Promise<AuthUser>` | Resolve a bearer token to a user. The library is neutral about how tokens are verified — plug in your service's existing JWT/cipher/identity pipeline. |
|
|
143
|
+
| `setupPostgan` | `RequestHandler` | Express middleware that attaches a `Postgan` instance to `req.postgan`. The library mounts this between auth and the JSON-RPC dispatcher. |
|
|
144
|
+
| `errorMapper?` | `McpErrorMapper` | Translate a thrown error into an MCP envelope. Return `null` for the redacted `INTERNAL_ERROR` fallback. Without a mapper, every thrown error becomes `INTERNAL_ERROR` with the message redacted. |
|
|
145
|
+
|
|
146
|
+
**Returns:**
|
|
147
|
+
|
|
148
|
+
| Field | Type | Description |
|
|
149
|
+
| -------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
150
|
+
| `server` | `McpServer` (recording shim) | Pass to `tool()` to register tools. Only `registerTool` is exposed; other `McpServer` methods are not implemented. |
|
|
151
|
+
| `mount` | `(target: Router) => Promise<void>` | Attaches the MCP route handlers to an Express router. Caller decides the mount path. |
|
|
152
|
+
|
|
153
|
+
### `tool(server, spec)`
|
|
154
|
+
|
|
155
|
+
Register a tool. The handler runs inside an ALS scope with a fresh postgan transaction.
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
tool(server, {
|
|
159
|
+
name: "tool_name",
|
|
160
|
+
description: "What this tool does",
|
|
161
|
+
inputSchema: z.object({
|
|
162
|
+
/* ... */
|
|
163
|
+
}),
|
|
164
|
+
annotations: { readOnlyHint: true }, // optional
|
|
165
|
+
handler: async (args, ctx) => {
|
|
166
|
+
// ctx.user, ctx.postgan, ctx.transaction, ctx.sessionId, ctx.logger
|
|
167
|
+
return {
|
|
168
|
+
/* plain object — surfaced as both text and structuredContent */
|
|
169
|
+
};
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**`ToolContext`** (the second handler argument):
|
|
175
|
+
|
|
176
|
+
| Field | Type |
|
|
177
|
+
| ------------- | ---------------------------------------------------------- |
|
|
178
|
+
| `user` | `AuthUser` (cast to your richer type as needed) |
|
|
179
|
+
| `postgan` | `Postgan` |
|
|
180
|
+
| `transaction` | `PostganTransaction` (auto-committed/rolled-back) |
|
|
181
|
+
| `sessionId` | `string \| undefined` |
|
|
182
|
+
| `logger` | `pino.Logger` (already child'd with tool name + sessionId) |
|
|
183
|
+
|
|
184
|
+
**Annotations** are MCP spec hints surfaced during `tools/list`:
|
|
185
|
+
|
|
186
|
+
| Field | Meaning |
|
|
187
|
+
| ----------------- | ------------------------------------ |
|
|
188
|
+
| `title` | Human-readable title |
|
|
189
|
+
| `readOnlyHint` | Tool only reads, never writes |
|
|
190
|
+
| `destructiveHint` | Tool may delete or destroy data |
|
|
191
|
+
| `idempotentHint` | Calling twice with same args is safe |
|
|
192
|
+
| `openWorldHint` | Tool reaches out to external systems |
|
|
193
|
+
|
|
194
|
+
### `mcpAuth(options)`
|
|
195
|
+
|
|
196
|
+
The auth middleware that `createMcpServer` mounts internally. Exported in case you need to compose it differently.
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
mcpAuth({
|
|
200
|
+
tokenToUser: async (token) => {
|
|
201
|
+
/* ... */
|
|
202
|
+
},
|
|
203
|
+
logger: pinoLogger,
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Sets `req.user`, `req.headers.company`, `req.auth` (SDK-shaped `AuthInfo`), and optionally `req.mcpSessionId` (from `X-Session-Id` header).
|
|
208
|
+
|
|
209
|
+
### Errors
|
|
210
|
+
|
|
211
|
+
The library deliberately does **not** define an error class. Your service throws whatever it already throws (an `AppError`, a `ZodError`, anything), and you supply an `McpErrorMapper` callback to translate those throwables into MCP envelopes at the tool boundary.
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
import {
|
|
215
|
+
INTERNAL_ERROR,
|
|
216
|
+
toCallToolError,
|
|
217
|
+
type McpErrorMapper,
|
|
218
|
+
type McpToolError,
|
|
219
|
+
} from "@ganintegrity/mcp";
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
- **`McpToolError`** — the shape an envelope is built from:
|
|
223
|
+
```ts
|
|
224
|
+
interface McpToolError {
|
|
225
|
+
code: string; // surfaced to the agent
|
|
226
|
+
message: string; // surfaced to the agent
|
|
227
|
+
severity: "warn" | "error"; // drives log level
|
|
228
|
+
details?: Record<string, unknown>; // surfaced to the agent
|
|
229
|
+
cause?: unknown; // logged at error level only, never surfaced
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
- **`McpErrorMapper`** — `(err: unknown) => McpToolError | null`. Return `null` to fall through to a redacted `INTERNAL_ERROR` envelope (the original message is **not** leaked to the agent).
|
|
233
|
+
- **`INTERNAL_ERROR`** — string constant, the only code the library itself emits.
|
|
234
|
+
- **`toCallToolError(mapped, originalErr, meta)`** — used internally by `tool()`. Exported in case you build your own tool wrappers.
|
|
235
|
+
|
|
236
|
+
The `tool()` helper invokes the mapper and `toCallToolError` automatically when a handler throws — you typically don't call them directly.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Integration notes
|
|
241
|
+
|
|
242
|
+
### Express type augmentation
|
|
243
|
+
|
|
244
|
+
The library augments `Express.Request` with two fields:
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
declare global {
|
|
248
|
+
namespace Express {
|
|
249
|
+
interface Request {
|
|
250
|
+
mcpSessionId?: string;
|
|
251
|
+
auth?: AuthInfo; // from @modelcontextprotocol/sdk
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
This ships in `dist/index.d.ts` — once you import anything from `@ganintegrity/mcp`, both fields are available on `Request` throughout your project. The library does **not** declare `req.user` or `req.postgan` — those remain your service's concern (typically in your existing `types/express.d.ts`).
|
|
258
|
+
|
|
259
|
+
### The `AuthUser` contract
|
|
260
|
+
|
|
261
|
+
`AuthUser` is the minimal shape the library reads:
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
interface AuthUser {
|
|
265
|
+
id: string;
|
|
266
|
+
companySubdomainName: string;
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Your `tokenToUser` returns an object that satisfies this — typically your service's richer user type (`RequestUser` etc.) which already has both fields. Inside tool handlers, `ctx.user` is typed as `AuthUser`; cast to your richer type when you need extra fields:
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
handler: async (args, ctx) => {
|
|
274
|
+
const user = ctx.user as Express.Request["user"];
|
|
275
|
+
return { firstName: user.firstName };
|
|
276
|
+
};
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Postgan middleware contract
|
|
280
|
+
|
|
281
|
+
`setupPostgan` is invoked after auth, so `req.user` is populated. Your middleware should read whatever it needs (typically `userToken`, `postgresId`, `postgresCompanyId`) off `req.user`, build a `Postgan`, and assign it to both `req.postgan` and `res.locals.postgan`. The library reads it back from `req.postgan`.
|
|
282
|
+
|
|
283
|
+
If `req.postgan` is missing when a request lands at `POST /`, the library responds with `401 Unauthenticated` — auth + setupPostgan are presumed to have run upstream.
|
|
284
|
+
|
|
285
|
+
### Bridging your error type
|
|
286
|
+
|
|
287
|
+
Your service keeps its own `AppError` (or whatever throwable it already uses). The library only sees the bridge you supply via `errorMapper`. Typical shape:
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
import type { McpErrorMapper } from "@ganintegrity/mcp";
|
|
291
|
+
import { AppError } from "../errors.ts";
|
|
292
|
+
|
|
293
|
+
export const errorMapper: McpErrorMapper = (err) => {
|
|
294
|
+
if (err instanceof AppError) {
|
|
295
|
+
return {
|
|
296
|
+
code: err.code,
|
|
297
|
+
message: err.message,
|
|
298
|
+
severity: err.statusCode >= 500 ? "error" : "warn",
|
|
299
|
+
details: err.details,
|
|
300
|
+
cause: err.cause,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
// Anything not recognised → null → redacted INTERNAL_ERROR envelope.
|
|
304
|
+
return null;
|
|
305
|
+
};
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Mapping rules to keep in mind:
|
|
309
|
+
|
|
310
|
+
- **`severity`** drives log level (`"warn"` → info log, `"error"` → error log) **and** is your call. Pick `"warn"` for failures the agent can react to (not found, validation, forbidden); `"error"` for anything you'd want a human to look at.
|
|
311
|
+
- **`message` and `details` are surfaced to the agent.** Don't put internals (DB error text, stack traces, internal IDs) here. Sanitise upstream if you need to.
|
|
312
|
+
- **`cause` is logged but never surfaced.** Use it to attach the original error for debugging without leaking it to the agent.
|
|
313
|
+
- **Returning `null`** triggers the redacted fallback: `INTERNAL_ERROR` code, generic "An unexpected error occurred." message, original error logged at error level. Use this for anything you don't want the agent to see verbatim.
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Architecture deep dive
|
|
318
|
+
|
|
319
|
+
### Why a recording shim?
|
|
320
|
+
|
|
321
|
+
The natural shape would be: `const server = new McpServer(...); server.registerTool(...); app.post("/mcp", (req, res) => transport.handleRequest(req, res, req.body))`. This breaks because `StreamableHTTPServerTransport` in stateless mode is single-use — once it has handled one request, it cannot be reused. The MCP SDK enforces this internally.
|
|
322
|
+
|
|
323
|
+
The fix is to instantiate a transport per request. But then the `McpServer` would also need to be per-request (the SDK couples the two via `connect()`), which means re-registering every tool on every request.
|
|
324
|
+
|
|
325
|
+
The recording shim solves this: tools are registered once into a list, and `mount()` replays the list onto a fresh `McpServer` per request. The replay is cheap — `registerTool` just stores function references, and the McpServer constructor is similarly lightweight. End-to-end latency is dominated by the actual tool work.
|
|
326
|
+
|
|
327
|
+
### Why AsyncLocalStorage instead of passing context?
|
|
328
|
+
|
|
329
|
+
Tools could receive the `Request`/`Response` directly, and pull `user`/`postgan`/etc. off them. But that:
|
|
330
|
+
|
|
331
|
+
1. Couples every tool to Express. If the MCP server ever needs to be invoked over a different transport (a worker, a CLI, a test), every tool breaks.
|
|
332
|
+
2. Forces every helper called by a tool to also receive the context, polluting helper signatures throughout the service.
|
|
333
|
+
|
|
334
|
+
ALS lets tool helpers reach context anywhere down the call stack without parameter threading, while keeping the tool-handler signature `(args, ctx)` clean and transport-agnostic. The `requestStore.run(...)` boundary in `mount()` is the only place ALS knows about Express.
|
|
335
|
+
|
|
336
|
+
### Why per-tool transactions?
|
|
337
|
+
|
|
338
|
+
A single MCP request can dispatch multiple tool calls. Wrapping the entire request in one transaction means a late-call failure rolls back work the agent already saw as successful. Wrapping each tool call in its own transaction gives the agent committed, observable side effects after each call — matching the "tool" mental model.
|
|
339
|
+
|
|
340
|
+
The cost is more transaction begin/commits, which on a healthy postgres is negligible.
|
|
341
|
+
|
|
342
|
+
### Why is `tool()` separate from the `McpServer` returned by `createMcpServer`?
|
|
343
|
+
|
|
344
|
+
`tool()` is a thin wrapper that:
|
|
345
|
+
|
|
346
|
+
1. Reads the ALS store to build a `ToolContext`.
|
|
347
|
+
2. Opens a transaction, runs your handler, commits or rolls back.
|
|
348
|
+
3. Maps thrown errors to `CallToolResult` envelopes.
|
|
349
|
+
|
|
350
|
+
You could call `server.registerTool(...)` directly and skip all of this — useful if you want raw access to the SDK's request/response shape. But you'd lose ALS, transactions, and the error mapping. `tool()` is what makes the rest of the library useful; `server.registerTool` is the escape hatch.
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## Development
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
pnpm install # install deps
|
|
358
|
+
pnpm build # tsc → dist/
|
|
359
|
+
pnpm typecheck # tsc --noEmit
|
|
360
|
+
pnpm test # vitest run
|
|
361
|
+
pnpm lint # eslint src/**/*.ts
|
|
362
|
+
pnpm format # prettier --write
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
The repo uses:
|
|
366
|
+
|
|
367
|
+
- **TypeScript 6** with NodeNext module resolution and `rewriteRelativeImportExtensions` (so source can import `./foo.ts` and emit `./foo.js`).
|
|
368
|
+
- **ESLint 10 flat config** + `typescript-eslint`.
|
|
369
|
+
- **Vitest 4** for tests.
|
|
370
|
+
- **Semantic-release** for publishing.
|
|
371
|
+
- **Commitlint** with conventional-commits.
|
|
372
|
+
|
|
373
|
+
### A note on `pnpm.peerDependencyRules`
|
|
374
|
+
|
|
375
|
+
`package.json` sets `pnpm.peerDependencyRules.allowedVersions` for several `@ganintegrity/*` packages and an `ignoreMissing` list for optional peers (`pg-native`, `tedious`, etc.). These suppress warnings caused by transitive packages declaring older peer ranges than the resolved versions. The suppression is intentional — the version skew is known and tolerated within the gan ecosystem. If a real peer mismatch appears in a future install, it'll stand out instead of being lost in noise.
|
package/dist/als.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import type { Postgan } from "@ganintegrity/postgan";
|
|
3
|
+
import type { Logger } from "pino";
|
|
4
|
+
import type { AuthUser } from "./auth/auth.types.ts";
|
|
5
|
+
import type { McpErrorMapper } from "./errors/errors.types.ts";
|
|
6
|
+
/**
|
|
7
|
+
* Internal per-request bundle. Exported only for advanced consumers who
|
|
8
|
+
* write their own tool wrappers and need to read the active scope directly;
|
|
9
|
+
* regular tool handlers receive `ToolContext` instead.
|
|
10
|
+
*/
|
|
11
|
+
export interface RequestStore {
|
|
12
|
+
user: AuthUser;
|
|
13
|
+
postgan: Postgan;
|
|
14
|
+
sessionId?: string;
|
|
15
|
+
logger: Logger;
|
|
16
|
+
/**
|
|
17
|
+
* Translate thrown errors into MCP envelopes. Set by `createMcpServer`
|
|
18
|
+
* from its `errorMapper` option; tool wrappers use it before falling
|
|
19
|
+
* through to the redacted `INTERNAL_ERROR` envelope.
|
|
20
|
+
*/
|
|
21
|
+
errorMapper?: McpErrorMapper;
|
|
22
|
+
}
|
|
23
|
+
export declare const requestStore: AsyncLocalStorage<RequestStore>;
|
|
24
|
+
//# sourceMappingURL=als.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"als.d.ts","sourceRoot":"","sources":["../src/als.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE/D;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,WAAW,CAAC,EAAE,cAAc,CAAC;CAC9B;AAED,eAAO,MAAM,YAAY,iCAAwC,CAAC"}
|
package/dist/als.js
ADDED
package/dist/als.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"als.js","sourceRoot":"","sources":["../src/als.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAyBrD,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,iBAAiB,EAAgB,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Logger } from "pino";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal user shape the library reads off authenticated requests. The
|
|
4
|
+
* caller's `tokenToUser` returns a value that satisfies this — typically a
|
|
5
|
+
* richer service-specific user type. Inside tool handlers, `ctx.user` is
|
|
6
|
+
* typed as `AuthUser`; cast to your service's richer type when you need
|
|
7
|
+
* extra fields.
|
|
8
|
+
*/
|
|
9
|
+
export interface AuthUser {
|
|
10
|
+
id: string;
|
|
11
|
+
companySubdomainName: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Configuration for `mcpAuth`.
|
|
15
|
+
*/
|
|
16
|
+
export interface McpAuthOptions {
|
|
17
|
+
/**
|
|
18
|
+
* Resolve a bearer / X-Access-Token to a user. The library is intentionally
|
|
19
|
+
* neutral about how tokens are verified — plug in whatever cipher / JWT /
|
|
20
|
+
* identity pipeline your service already uses.
|
|
21
|
+
*
|
|
22
|
+
* Reject by throwing; the middleware catches the throw, logs it at `warn`,
|
|
23
|
+
* and responds `401`.
|
|
24
|
+
*/
|
|
25
|
+
tokenToUser: (token: string) => Promise<AuthUser>;
|
|
26
|
+
/** Logger used for the "token rejected" warning on auth failure. */
|
|
27
|
+
logger: Logger;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=auth.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.types.d.ts","sourceRoot":"","sources":["../../src/auth/auth.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;;OAOG;IACH,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAClD,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;CAChB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.types.js","sourceRoot":"","sources":["../../src/auth/auth.types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { RequestHandler } from "express";
|
|
2
|
+
import type { McpAuthOptions } from "./auth.types.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Bearer-token auth middleware for the MCP HTTP endpoint.
|
|
5
|
+
*
|
|
6
|
+
* Token sources, in order: `Authorization: Bearer <token>`, then
|
|
7
|
+
* `X-Access-Token`. If neither is present the middleware responds `401` and
|
|
8
|
+
* does not call `next`.
|
|
9
|
+
*
|
|
10
|
+
* On success it sets:
|
|
11
|
+
* - `req.user` — the resolved {@link AuthUser}
|
|
12
|
+
* - `req.headers.company` — `user.companySubdomainName` (for downstream
|
|
13
|
+
* middleware that keys off the company header)
|
|
14
|
+
* - `req.auth` — SDK-shaped `AuthInfo`, surfaced to MCP tool handlers as
|
|
15
|
+
* `RequestHandlerExtra.authInfo`
|
|
16
|
+
* - `req.mcpSessionId` — value of the `X-Session-Id` header if present, used
|
|
17
|
+
* for log correlation
|
|
18
|
+
*
|
|
19
|
+
* Then calls `next()`.
|
|
20
|
+
*
|
|
21
|
+
* **Never throws.** All failures (missing token, `tokenToUser` rejection)
|
|
22
|
+
* are turned into `401` responses; the middleware does not call `next(err)`.
|
|
23
|
+
*
|
|
24
|
+
* Mounted automatically by `createMcpServer`'s `mount()`. Exported in case
|
|
25
|
+
* you want to compose it differently (e.g. mount under a different router,
|
|
26
|
+
* or stack additional middleware).
|
|
27
|
+
*/
|
|
28
|
+
export declare function mcpAuth(options: McpAuthOptions): RequestHandler;
|
|
29
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmC,cAAc,EAAE,MAAM,SAAS,CAAC;AAC/E,OAAO,KAAK,EAAY,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAmChE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,CAwB/D"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const BEARER_PREFIX = /^Bearer\s+/i;
|
|
2
|
+
function extractBearer(req) {
|
|
3
|
+
const auth = req.headers["authorization"];
|
|
4
|
+
if (typeof auth === "string" && BEARER_PREFIX.test(auth)) {
|
|
5
|
+
return auth.replace(BEARER_PREFIX, "").trim();
|
|
6
|
+
}
|
|
7
|
+
// Parity fallback: REST accepts X-Access-Token. Same middleware shape.
|
|
8
|
+
const xat = req.headers["x-access-token"];
|
|
9
|
+
if (typeof xat === "string" && xat && xat !== "null" && xat !== "undefined") {
|
|
10
|
+
return xat;
|
|
11
|
+
}
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
function applyAuthToRequest(req, user, token) {
|
|
15
|
+
req.user = user;
|
|
16
|
+
req.headers.company = user.companySubdomainName;
|
|
17
|
+
req.auth = {
|
|
18
|
+
token,
|
|
19
|
+
clientId: user.id,
|
|
20
|
+
scopes: [],
|
|
21
|
+
extra: { user },
|
|
22
|
+
};
|
|
23
|
+
const sessionId = req.headers["x-session-id"];
|
|
24
|
+
if (typeof sessionId === "string" && sessionId.length > 0) {
|
|
25
|
+
req.mcpSessionId = sessionId;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Bearer-token auth middleware for the MCP HTTP endpoint.
|
|
30
|
+
*
|
|
31
|
+
* Token sources, in order: `Authorization: Bearer <token>`, then
|
|
32
|
+
* `X-Access-Token`. If neither is present the middleware responds `401` and
|
|
33
|
+
* does not call `next`.
|
|
34
|
+
*
|
|
35
|
+
* On success it sets:
|
|
36
|
+
* - `req.user` — the resolved {@link AuthUser}
|
|
37
|
+
* - `req.headers.company` — `user.companySubdomainName` (for downstream
|
|
38
|
+
* middleware that keys off the company header)
|
|
39
|
+
* - `req.auth` — SDK-shaped `AuthInfo`, surfaced to MCP tool handlers as
|
|
40
|
+
* `RequestHandlerExtra.authInfo`
|
|
41
|
+
* - `req.mcpSessionId` — value of the `X-Session-Id` header if present, used
|
|
42
|
+
* for log correlation
|
|
43
|
+
*
|
|
44
|
+
* Then calls `next()`.
|
|
45
|
+
*
|
|
46
|
+
* **Never throws.** All failures (missing token, `tokenToUser` rejection)
|
|
47
|
+
* are turned into `401` responses; the middleware does not call `next(err)`.
|
|
48
|
+
*
|
|
49
|
+
* Mounted automatically by `createMcpServer`'s `mount()`. Exported in case
|
|
50
|
+
* you want to compose it differently (e.g. mount under a different router,
|
|
51
|
+
* or stack additional middleware).
|
|
52
|
+
*/
|
|
53
|
+
export function mcpAuth(options) {
|
|
54
|
+
return async (req, res, next) => {
|
|
55
|
+
const token = extractBearer(req);
|
|
56
|
+
if (!token) {
|
|
57
|
+
res
|
|
58
|
+
.status(401)
|
|
59
|
+
.json({ error: "Missing or invalid Authorization header" });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const user = await options.tokenToUser(token);
|
|
64
|
+
applyAuthToRequest(req, user, token);
|
|
65
|
+
next();
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
const reason = err instanceof Error ? err.message : "invalid token";
|
|
69
|
+
options.logger.warn({ err }, "mcp auth: token rejected");
|
|
70
|
+
res.status(401).json({ error: `Authentication failed: ${reason}` });
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAGA,MAAM,aAAa,GAAG,aAAa,CAAC;AAEpC,SAAS,aAAa,CAAC,GAAY;IACjC,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAC1C,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACzD,OAAO,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAChD,CAAC;IACD,uEAAuE;IACvE,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC1C,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,WAAW,EAAE,CAAC;QAC5E,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAID,SAAS,kBAAkB,CAAC,GAAY,EAAE,IAAc,EAAE,KAAa;IACpE,GAAmB,CAAC,IAAI,GAAG,IAAI,CAAC;IACjC,GAAG,CAAC,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,oBAAoB,CAAC;IAChD,GAAG,CAAC,IAAI,GAAG;QACT,KAAK;QACL,QAAQ,EAAE,IAAI,CAAC,EAAE;QACjB,MAAM,EAAE,EAAE;QACV,KAAK,EAAE,EAAE,IAAI,EAAE;KAChB,CAAC;IAEF,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IAC9C,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1D,GAAG,CAAC,YAAY,GAAG,SAAS,CAAC;IAC/B,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,OAAO,CAAC,OAAuB;IAC7C,OAAO,KAAK,EACV,GAAY,EACZ,GAAa,EACb,IAAkB,EACH,EAAE;QACjB,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,GAAG;iBACA,MAAM,CAAC,GAAG,CAAC;iBACX,IAAI,CAAC,EAAE,KAAK,EAAE,yCAAyC,EAAE,CAAC,CAAC;YAC9D,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YAC9C,kBAAkB,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YACrC,IAAI,EAAE,CAAC;QACT,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;YACpE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,EAAE,0BAA0B,CAAC,CAAC;YACzD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,0BAA0B,MAAM,EAAE,EAAE,CAAC,CAAC;QACtE,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Logger } from "pino";
|
|
2
|
+
export type McpToolErrorSeverity = "warn" | "error";
|
|
3
|
+
/**
|
|
4
|
+
* Shape returned by an {@link McpErrorMapper} to describe how a thrown error
|
|
5
|
+
* should be surfaced as an MCP `CallToolResult` envelope.
|
|
6
|
+
*
|
|
7
|
+
* The library does not define an error *class* — consumers throw whatever
|
|
8
|
+
* type their service already uses, and the mapper translates it into this
|
|
9
|
+
* shape at the tool boundary.
|
|
10
|
+
*/
|
|
11
|
+
export interface McpToolError {
|
|
12
|
+
/** Stable machine-readable code surfaced to the agent. */
|
|
13
|
+
code: string;
|
|
14
|
+
/** Human-readable message surfaced to the agent. */
|
|
15
|
+
message: string;
|
|
16
|
+
/** Drives log level: `"warn"` → info log; `"error"` → error log. */
|
|
17
|
+
severity: McpToolErrorSeverity;
|
|
18
|
+
/** Optional structured details surfaced to the agent. */
|
|
19
|
+
details?: Record<string, unknown>;
|
|
20
|
+
/** Optional cause — logged at error level only, never surfaced to the agent. */
|
|
21
|
+
cause?: unknown;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Translate a thrown error into an {@link McpToolError} envelope, or
|
|
25
|
+
* return `null` to fall through to the generic `INTERNAL_ERROR` envelope
|
|
26
|
+
* with a redacted message.
|
|
27
|
+
*/
|
|
28
|
+
export type McpErrorMapper = (err: unknown) => McpToolError | null;
|
|
29
|
+
export interface ToolErrorMeta {
|
|
30
|
+
tool: string;
|
|
31
|
+
sessionId?: string;
|
|
32
|
+
logger: Logger;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=errors.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.types.d.ts","sourceRoot":"","sources":["../../src/errors/errors.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,OAAO,CAAC;AAEpD;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,gFAAgF;IAChF,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,YAAY,GAAG,IAAI,CAAC;AAEnE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.types.js","sourceRoot":"","sources":["../../src/errors/errors.types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import type { McpToolError, ToolErrorMeta } from "./errors.types.ts";
|
|
3
|
+
/**
|
|
4
|
+
* The only code the library itself emits — surfaced in the redacted
|
|
5
|
+
* envelope when a thrown error has no mapping. Consumer codes are theirs.
|
|
6
|
+
*/
|
|
7
|
+
export declare const INTERNAL_ERROR = "INTERNAL_ERROR";
|
|
8
|
+
/**
|
|
9
|
+
* Build the MCP `CallToolResult` envelope for a thrown error. Called
|
|
10
|
+
* automatically by `tool()` after the consumer's {@link McpErrorMapper}
|
|
11
|
+
* has had a chance to translate the throwable. Exported for consumers
|
|
12
|
+
* writing their own tool wrappers.
|
|
13
|
+
*
|
|
14
|
+
* Behaviour:
|
|
15
|
+
* - `mapped` non-null with `severity: "warn"` → logged at info; envelope
|
|
16
|
+
* carries the mapped `code`/`message`/`details`. The agent sees the
|
|
17
|
+
* message so it can react.
|
|
18
|
+
* - `mapped` non-null with `severity: "error"` → logged at error (with
|
|
19
|
+
* `cause`); envelope carries the mapped `code`/`message`/`details`.
|
|
20
|
+
* - `mapped` null → logged at error with the original throwable; envelope
|
|
21
|
+
* is a generic `INTERNAL_ERROR` with a redacted message. The original
|
|
22
|
+
* error message is **not** surfaced.
|
|
23
|
+
*
|
|
24
|
+
* Never throws (assumes `meta.logger` does not throw).
|
|
25
|
+
*/
|
|
26
|
+
export declare function toCallToolError(mapped: McpToolError | null, originalErr: unknown, meta: ToolErrorMeta): CallToolResult;
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAErE;;;GAGG;AACH,eAAO,MAAM,cAAc,mBAAmB,CAAC;AA+D/C;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,YAAY,GAAG,IAAI,EAC3B,WAAW,EAAE,OAAO,EACpB,IAAI,EAAE,aAAa,GAClB,cAAc,CAMhB"}
|