@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
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The only code the library itself emits — surfaced in the redacted
|
|
3
|
+
* envelope when a thrown error has no mapping. Consumer codes are theirs.
|
|
4
|
+
*/
|
|
5
|
+
export const INTERNAL_ERROR = "INTERNAL_ERROR";
|
|
6
|
+
function logMappedError(mapped, meta) {
|
|
7
|
+
if (mapped.severity === "error") {
|
|
8
|
+
meta.logger.error({
|
|
9
|
+
cause: mapped.cause,
|
|
10
|
+
code: mapped.code,
|
|
11
|
+
tool: meta.tool,
|
|
12
|
+
sessionId: meta.sessionId,
|
|
13
|
+
}, "mcp tool failed");
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
meta.logger.info({
|
|
17
|
+
code: mapped.code,
|
|
18
|
+
tool: meta.tool,
|
|
19
|
+
sessionId: meta.sessionId,
|
|
20
|
+
message: mapped.message,
|
|
21
|
+
}, "mcp tool rejected");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function mappedEnvelope(mapped) {
|
|
25
|
+
return {
|
|
26
|
+
isError: true,
|
|
27
|
+
content: [{ type: "text", text: mapped.message }],
|
|
28
|
+
structuredContent: {
|
|
29
|
+
code: mapped.code,
|
|
30
|
+
message: mapped.message,
|
|
31
|
+
...(mapped.details ? { details: mapped.details } : {}),
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function unknownErrorEnvelope(err, meta) {
|
|
36
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
37
|
+
meta.logger.error({
|
|
38
|
+
err: err instanceof Error ? err : undefined,
|
|
39
|
+
tool: meta.tool,
|
|
40
|
+
sessionId: meta.sessionId,
|
|
41
|
+
}, "mcp tool unhandled error");
|
|
42
|
+
return {
|
|
43
|
+
isError: true,
|
|
44
|
+
content: [
|
|
45
|
+
{ type: "text", text: "An unexpected error occurred. Please try again." },
|
|
46
|
+
],
|
|
47
|
+
structuredContent: {
|
|
48
|
+
code: INTERNAL_ERROR,
|
|
49
|
+
message,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build the MCP `CallToolResult` envelope for a thrown error. Called
|
|
55
|
+
* automatically by `tool()` after the consumer's {@link McpErrorMapper}
|
|
56
|
+
* has had a chance to translate the throwable. Exported for consumers
|
|
57
|
+
* writing their own tool wrappers.
|
|
58
|
+
*
|
|
59
|
+
* Behaviour:
|
|
60
|
+
* - `mapped` non-null with `severity: "warn"` → logged at info; envelope
|
|
61
|
+
* carries the mapped `code`/`message`/`details`. The agent sees the
|
|
62
|
+
* message so it can react.
|
|
63
|
+
* - `mapped` non-null with `severity: "error"` → logged at error (with
|
|
64
|
+
* `cause`); envelope carries the mapped `code`/`message`/`details`.
|
|
65
|
+
* - `mapped` null → logged at error with the original throwable; envelope
|
|
66
|
+
* is a generic `INTERNAL_ERROR` with a redacted message. The original
|
|
67
|
+
* error message is **not** surfaced.
|
|
68
|
+
*
|
|
69
|
+
* Never throws (assumes `meta.logger` does not throw).
|
|
70
|
+
*/
|
|
71
|
+
export function toCallToolError(mapped, originalErr, meta) {
|
|
72
|
+
if (mapped) {
|
|
73
|
+
logMappedError(mapped, meta);
|
|
74
|
+
return mappedEnvelope(mapped);
|
|
75
|
+
}
|
|
76
|
+
return unknownErrorEnvelope(originalErr, meta);
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAE/C,SAAS,cAAc,CAAC,MAAoB,EAAE,IAAmB;IAC/D,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAChC,IAAI,CAAC,MAAM,CAAC,KAAK,CACf;YACE,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,EACD,iBAAiB,CAClB,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,MAAM,CAAC,IAAI,CACd;YACE,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,OAAO,EAAE,MAAM,CAAC,OAAO;SACxB,EACD,mBAAmB,CACpB,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,MAAoB;IAC1C,OAAO;QACL,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;QACjD,iBAAiB,EAAE;YACjB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACvD;KACF,CAAC;AACJ,CAAC;AAED,SAAS,oBAAoB,CAC3B,GAAY,EACZ,IAAmB;IAEnB,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjE,IAAI,CAAC,MAAM,CAAC,KAAK,CACf;QACE,GAAG,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS;QAC3C,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,SAAS,EAAE,IAAI,CAAC,SAAS;KAC1B,EACD,0BAA0B,CAC3B,CAAC;IACF,OAAO;QACL,OAAO,EAAE,IAAI;QACb,OAAO,EAAE;YACP,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iDAAiD,EAAE;SAC1E;QACD,iBAAiB,EAAE;YACjB,IAAI,EAAE,cAAc;YACpB,OAAO;SACR;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,eAAe,CAC7B,MAA2B,EAC3B,WAAoB,EACpB,IAAmB;IAEnB,IAAI,MAAM,EAAE,CAAC;QACX,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC7B,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,oBAAoB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;AACjD,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";
|
|
2
|
+
declare global {
|
|
3
|
+
namespace Express {
|
|
4
|
+
interface Request {
|
|
5
|
+
mcpSessionId?: string;
|
|
6
|
+
auth?: AuthInfo;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export { createMcpServer } from "./server/index.ts";
|
|
11
|
+
export type { CreateMcpServerOptions, CreateMcpServerResult, } from "./server/server.types.ts";
|
|
12
|
+
export { tool } from "./tool/index.ts";
|
|
13
|
+
export type { ToolSpec, ToolAnnotations, ToolContext, } from "./tool/tool.types.ts";
|
|
14
|
+
export type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
15
|
+
export { mcpAuth } from "./auth/index.ts";
|
|
16
|
+
export type { McpAuthOptions, AuthUser } from "./auth/auth.types.ts";
|
|
17
|
+
export { INTERNAL_ERROR, toCallToolError } from "./errors/index.ts";
|
|
18
|
+
export type { McpErrorMapper, McpToolError, McpToolErrorSeverity, ToolErrorMeta, } from "./errors/errors.types.ts";
|
|
19
|
+
export type { RequestStore } from "./als.ts";
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gDAAgD,CAAC;AAE/E,OAAO,CAAC,MAAM,CAAC;IAIb,UAAU,OAAO,CAAC;QAChB,UAAU,OAAO;YACf,YAAY,CAAC,EAAE,MAAM,CAAC;YACtB,IAAI,CAAC,EAAE,QAAQ,CAAC;SACjB;KACF;CACF;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,YAAY,EACV,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,YAAY,EACV,QAAQ,EACR,eAAe,EACf,WAAW,GACZ,MAAM,sBAAsB,CAAC;AAE9B,YAAY,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErE,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpE,YAAY,EACV,cAAc,EACd,YAAY,EACZ,oBAAoB,EACpB,aAAa,GACd,MAAM,0BAA0B,CAAC;AAElC,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAMpD,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AASvC,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAG1C,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CreateMcpServerOptions, CreateMcpServerResult } from "./server.types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Build an MCP server bound to the Streamable HTTP transport.
|
|
4
|
+
*
|
|
5
|
+
* Returns a `{ server, mount }` pair. Register tools against `server` using
|
|
6
|
+
* `tool()` at boot time, then call `mount(router)` to wire the JSON-RPC
|
|
7
|
+
* endpoint onto an Express router. Each incoming HTTP request runs through:
|
|
8
|
+
*
|
|
9
|
+
* `express.json()` → auth middleware → caller's `setupPostgan` → tool dispatch
|
|
10
|
+
*
|
|
11
|
+
* Tool handlers run inside an `AsyncLocalStorage` scope, so they pull
|
|
12
|
+
* `user` / `postgan` / `transaction` / `sessionId` / `logger` from
|
|
13
|
+
* `ToolContext` without Express objects leaking in.
|
|
14
|
+
*
|
|
15
|
+
* Synchronous and never throws.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createMcpServer(options: CreateMcpServerOptions): CreateMcpServerResult;
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,sBAAsB,EACtB,qBAAqB,EAKtB,MAAM,mBAAmB,CAAC;AA2G3B;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,sBAAsB,GAC9B,qBAAqB,CAuBvB"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import express, {} from "express";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4
|
+
import { mcpAuth } from "../auth/index.js";
|
|
5
|
+
import { requestStore } from "../als.js";
|
|
6
|
+
/**
|
|
7
|
+
* Build the recording shim handed to consumers as `server`.
|
|
8
|
+
*
|
|
9
|
+
* Why a shim and not a real `McpServer`: the SDK's
|
|
10
|
+
* `StreamableHTTPServerTransport` (stateless mode) is single-use — once it
|
|
11
|
+
* has handled one request it cannot be reused. The transport must be paired
|
|
12
|
+
* with an `McpServer`, so we need a fresh server per HTTP request. But
|
|
13
|
+
* consumers register tools once at boot, not per request — so registration
|
|
14
|
+
* is split in two:
|
|
15
|
+
*
|
|
16
|
+
* - boot: `tool(server, spec)` lands here and pushes onto `registrations`.
|
|
17
|
+
* - request: {@link handleMcpRequest} constructs a real `McpServer` and
|
|
18
|
+
* replays every recorded registration onto it before connecting
|
|
19
|
+
* the transport.
|
|
20
|
+
*
|
|
21
|
+
* The recorder is typed as `McpServer` so `tool(server, spec)` type-checks,
|
|
22
|
+
* but `registerTool` is the only method actually implemented. Nothing in
|
|
23
|
+
* the library calls anything else on it, and the consumer is documented to
|
|
24
|
+
* only pass it to `tool()` — calls to other methods are undefined behaviour.
|
|
25
|
+
*/
|
|
26
|
+
function createToolRecorder() {
|
|
27
|
+
const registrations = [];
|
|
28
|
+
const recorder = {
|
|
29
|
+
registerTool: (name, config, handler) => {
|
|
30
|
+
registrations.push({ name, config, handler });
|
|
31
|
+
// Real `registerTool` returns a registration handle for later
|
|
32
|
+
// update/remove. We don't expose that surface — empty object satisfies
|
|
33
|
+
// the return type and is never read.
|
|
34
|
+
return {};
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
return { server: recorder, registrations };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Per-request handler. Builds the ALS store from `req.user` / `req.postgan`
|
|
41
|
+
* (set upstream by the auth + setupPostgan middleware), spins up a fresh
|
|
42
|
+
* `McpServer` + transport pair, replays the recorded tool registrations,
|
|
43
|
+
* dispatches the JSON-RPC call inside `requestStore.run(...)`, and tears
|
|
44
|
+
* down server + transport in `finally`.
|
|
45
|
+
*/
|
|
46
|
+
function handleMcpRequest(deps) {
|
|
47
|
+
return async (req, res) => {
|
|
48
|
+
const { user, postgan } = req;
|
|
49
|
+
if (!user || !postgan) {
|
|
50
|
+
// Defensive: mcpAuth + setupPostgan should have populated both.
|
|
51
|
+
res.status(401).json({ error: "Unauthenticated" });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const sessionId = req.mcpSessionId;
|
|
55
|
+
const store = {
|
|
56
|
+
user,
|
|
57
|
+
postgan,
|
|
58
|
+
sessionId,
|
|
59
|
+
logger: deps.logger.child({
|
|
60
|
+
sessionId,
|
|
61
|
+
userId: user.id,
|
|
62
|
+
company: user.companySubdomainName,
|
|
63
|
+
}),
|
|
64
|
+
errorMapper: deps.errorMapper,
|
|
65
|
+
};
|
|
66
|
+
const requestServer = new McpServer({
|
|
67
|
+
name: deps.name,
|
|
68
|
+
version: deps.version,
|
|
69
|
+
});
|
|
70
|
+
for (const reg of deps.registrations) {
|
|
71
|
+
requestServer.registerTool(reg.name, reg.config, reg.handler);
|
|
72
|
+
}
|
|
73
|
+
const transport = new StreamableHTTPServerTransport({
|
|
74
|
+
sessionIdGenerator: undefined,
|
|
75
|
+
});
|
|
76
|
+
await requestServer.connect(transport);
|
|
77
|
+
await requestStore.run(store, async () => {
|
|
78
|
+
try {
|
|
79
|
+
await transport.handleRequest(req, res, req.body);
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
deps.logger.error({ err, sessionId }, "mcp: transport.handleRequest threw");
|
|
83
|
+
if (!res.headersSent) {
|
|
84
|
+
res.status(500).json({ error: "Internal MCP transport error" });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
await transport.close().catch((closeErr) => {
|
|
89
|
+
deps.logger.warn({ err: closeErr }, "mcp: transport close failed");
|
|
90
|
+
});
|
|
91
|
+
await requestServer.close().catch((closeErr) => {
|
|
92
|
+
deps.logger.warn({ err: closeErr }, "mcp: server close failed");
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Build an MCP server bound to the Streamable HTTP transport.
|
|
100
|
+
*
|
|
101
|
+
* Returns a `{ server, mount }` pair. Register tools against `server` using
|
|
102
|
+
* `tool()` at boot time, then call `mount(router)` to wire the JSON-RPC
|
|
103
|
+
* endpoint onto an Express router. Each incoming HTTP request runs through:
|
|
104
|
+
*
|
|
105
|
+
* `express.json()` → auth middleware → caller's `setupPostgan` → tool dispatch
|
|
106
|
+
*
|
|
107
|
+
* Tool handlers run inside an `AsyncLocalStorage` scope, so they pull
|
|
108
|
+
* `user` / `postgan` / `transaction` / `sessionId` / `logger` from
|
|
109
|
+
* `ToolContext` without Express objects leaking in.
|
|
110
|
+
*
|
|
111
|
+
* Synchronous and never throws.
|
|
112
|
+
*/
|
|
113
|
+
export function createMcpServer(options) {
|
|
114
|
+
const mcpLogger = options.logger.child({ component: "mcp" });
|
|
115
|
+
const { server, registrations } = createToolRecorder();
|
|
116
|
+
const mount = async (target) => {
|
|
117
|
+
target.use(express.json());
|
|
118
|
+
target.use(mcpAuth({ tokenToUser: options.tokenToUser, logger: mcpLogger }));
|
|
119
|
+
target.use(options.setupPostgan);
|
|
120
|
+
target.post("/", handleMcpRequest({
|
|
121
|
+
name: options.name,
|
|
122
|
+
version: options.version,
|
|
123
|
+
registrations,
|
|
124
|
+
logger: mcpLogger,
|
|
125
|
+
errorMapper: options.errorMapper,
|
|
126
|
+
}));
|
|
127
|
+
};
|
|
128
|
+
return { server, mount };
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Request, RequestHandler, Router } from "express";
|
|
2
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import type { Postgan } from "@ganintegrity/postgan";
|
|
4
|
+
import type { Logger } from "pino";
|
|
5
|
+
import type { AuthUser } from "../auth/auth.types.ts";
|
|
6
|
+
import type { McpErrorMapper } from "../errors/errors.types.ts";
|
|
7
|
+
/**
|
|
8
|
+
* Configuration for `createMcpServer()`.
|
|
9
|
+
*/
|
|
10
|
+
export interface CreateMcpServerOptions {
|
|
11
|
+
/** MCP server name advertised to clients during initialisation. */
|
|
12
|
+
name: string;
|
|
13
|
+
/** MCP server version advertised to clients during initialisation. */
|
|
14
|
+
version: string;
|
|
15
|
+
/**
|
|
16
|
+
* Service logger. The library calls `logger.child({ component: "mcp" })`
|
|
17
|
+
* internally and further childs with `sessionId` / `userId` / `company`
|
|
18
|
+
* per request and with `tool` / `sessionId` per tool call.
|
|
19
|
+
*/
|
|
20
|
+
logger: Logger;
|
|
21
|
+
/**
|
|
22
|
+
* Resolve a bearer token to a user. See `McpAuthOptions.tokenToUser`.
|
|
23
|
+
* Reject by throwing — the library responds `401`.
|
|
24
|
+
*/
|
|
25
|
+
tokenToUser: (token: string) => Promise<AuthUser>;
|
|
26
|
+
/**
|
|
27
|
+
* Express middleware that attaches a `Postgan` instance to `req.postgan`.
|
|
28
|
+
* Mounted automatically between the auth middleware and the JSON-RPC
|
|
29
|
+
* dispatcher; tools open per-call transactions against `ctx.postgan`.
|
|
30
|
+
*
|
|
31
|
+
* The library is neutral about how Postgan is constructed — supply your
|
|
32
|
+
* service's existing setup-postgan middleware.
|
|
33
|
+
*/
|
|
34
|
+
setupPostgan: RequestHandler;
|
|
35
|
+
/**
|
|
36
|
+
* Translate a thrown error into an MCP envelope. Return `null` to fall
|
|
37
|
+
* through to a redacted `INTERNAL_ERROR` envelope — appropriate for
|
|
38
|
+
* anything you don't want surfaced to the agent. Without a mapper every
|
|
39
|
+
* thrown error becomes `INTERNAL_ERROR`.
|
|
40
|
+
*/
|
|
41
|
+
errorMapper?: McpErrorMapper;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Return value of `createMcpServer()`.
|
|
45
|
+
*/
|
|
46
|
+
export interface CreateMcpServerResult {
|
|
47
|
+
/**
|
|
48
|
+
* Tool-registration target. Pass to `tool()` at boot, once per tool.
|
|
49
|
+
* Treat as opaque — `tool()` is the only supported way to use it.
|
|
50
|
+
*/
|
|
51
|
+
server: McpServer;
|
|
52
|
+
/**
|
|
53
|
+
* Attach the MCP route handlers (auth + setupPostgan + JSON-RPC dispatch)
|
|
54
|
+
* to the supplied Express router. Caller decides the mount path
|
|
55
|
+
* (`app.use("/mcp", router)`).
|
|
56
|
+
*
|
|
57
|
+
* Resolves once the route is attached. Never throws.
|
|
58
|
+
*/
|
|
59
|
+
mount: (target: Router) => Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
export type RegisterToolArgs = Parameters<McpServer["registerTool"]>;
|
|
62
|
+
export type ReqWithUserPostgan = Request & {
|
|
63
|
+
user?: AuthUser;
|
|
64
|
+
postgan?: Postgan;
|
|
65
|
+
};
|
|
66
|
+
export interface ToolRegistration {
|
|
67
|
+
name: RegisterToolArgs[0];
|
|
68
|
+
config: RegisterToolArgs[1];
|
|
69
|
+
handler: RegisterToolArgs[2];
|
|
70
|
+
}
|
|
71
|
+
export interface HandleRequestDeps {
|
|
72
|
+
name: string;
|
|
73
|
+
version: string;
|
|
74
|
+
registrations: readonly ToolRegistration[];
|
|
75
|
+
logger: Logger;
|
|
76
|
+
errorMapper?: McpErrorMapper;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=server.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.types.d.ts","sourceRoot":"","sources":["../../src/server/server.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAClD;;;;;;;OAOG;IACH,YAAY,EAAE,cAAc,CAAC;IAC7B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,cAAc,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,MAAM,EAAE,SAAS,CAAC;IAClB;;;;;;OAMG;IACH,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C;AAID,MAAM,MAAM,gBAAgB,GAAG,UAAU,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC,CAAC;AAErE,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG;IACzC,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAC1B,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAC5B,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,cAAc,CAAC;CAC9B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.types.js","sourceRoot":"","sources":["../../src/server/server.types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { z } from "zod";
|
|
3
|
+
import type { ToolSpec } from "./tool.types.ts";
|
|
4
|
+
/**
|
|
5
|
+
* Register a tool against the MCP server returned by `createMcpServer()`.
|
|
6
|
+
* The wrapper around your handler:
|
|
7
|
+
*
|
|
8
|
+
* 1. Builds a {@link ToolContext} from the active request scope.
|
|
9
|
+
* 2. Opens a fresh `Postgan.Transaction` before calling your handler.
|
|
10
|
+
* 3. On resolve, commits the transaction and returns a `CallToolResult`
|
|
11
|
+
* where the handler's return value is surfaced both as a JSON `text`
|
|
12
|
+
* content block (for non-structured-aware clients) and as
|
|
13
|
+
* `structuredContent` (for AI-SDK clients).
|
|
14
|
+
* 4. On throw, rolls back the transaction and routes the error through
|
|
15
|
+
* the consumer's `McpErrorMapper` (passed to `createMcpServer`) to
|
|
16
|
+
* build an MCP error envelope.
|
|
17
|
+
*
|
|
18
|
+
* Registration itself is synchronous and does not throw. The wrapped handler
|
|
19
|
+
* **may** throw at request time — but only if it is invoked outside an
|
|
20
|
+
* active request scope, which indicates a programming bug (the handler was
|
|
21
|
+
* dispatched without going through `mount()`'s ALS wrapper). Such throws
|
|
22
|
+
* propagate to the SDK transport and surface as an HTTP 500. Errors raised
|
|
23
|
+
* by your own handler logic are caught and never propagate.
|
|
24
|
+
*/
|
|
25
|
+
export declare function tool<Schema extends z.ZodObject, Output extends Record<string, unknown>>(server: McpServer, spec: ToolSpec<Schema, Output>): void;
|
|
26
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tool/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIzE,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAI7B,OAAO,KAAK,EAAe,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AA6B7D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,IAAI,CAClB,MAAM,SAAS,CAAC,CAAC,SAAS,EAC1B,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACtC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAiDzD"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { requestStore } from "../als.js";
|
|
2
|
+
import { toCallToolError } from "../errors/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Open a postgan transaction, run `body` against it, and finalise:
|
|
5
|
+
* commit on resolve, rollback on throw. A failing rollback is logged at
|
|
6
|
+
* error level but never re-thrown — the original error from `body` is what
|
|
7
|
+
* propagates.
|
|
8
|
+
*/
|
|
9
|
+
async function withTransaction(postgan, logger, body) {
|
|
10
|
+
const tx = new postgan.Transaction();
|
|
11
|
+
await tx.begin();
|
|
12
|
+
try {
|
|
13
|
+
const result = await body(tx);
|
|
14
|
+
await tx.commit();
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
if (tx.active) {
|
|
19
|
+
await tx.rollback().catch((rollbackErr) => {
|
|
20
|
+
logger.error({ err: rollbackErr }, "mcp: transaction rollback failed");
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
throw err;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Register a tool against the MCP server returned by `createMcpServer()`.
|
|
28
|
+
* The wrapper around your handler:
|
|
29
|
+
*
|
|
30
|
+
* 1. Builds a {@link ToolContext} from the active request scope.
|
|
31
|
+
* 2. Opens a fresh `Postgan.Transaction` before calling your handler.
|
|
32
|
+
* 3. On resolve, commits the transaction and returns a `CallToolResult`
|
|
33
|
+
* where the handler's return value is surfaced both as a JSON `text`
|
|
34
|
+
* content block (for non-structured-aware clients) and as
|
|
35
|
+
* `structuredContent` (for AI-SDK clients).
|
|
36
|
+
* 4. On throw, rolls back the transaction and routes the error through
|
|
37
|
+
* the consumer's `McpErrorMapper` (passed to `createMcpServer`) to
|
|
38
|
+
* build an MCP error envelope.
|
|
39
|
+
*
|
|
40
|
+
* Registration itself is synchronous and does not throw. The wrapped handler
|
|
41
|
+
* **may** throw at request time — but only if it is invoked outside an
|
|
42
|
+
* active request scope, which indicates a programming bug (the handler was
|
|
43
|
+
* dispatched without going through `mount()`'s ALS wrapper). Such throws
|
|
44
|
+
* propagate to the SDK transport and surface as an HTTP 500. Errors raised
|
|
45
|
+
* by your own handler logic are caught and never propagate.
|
|
46
|
+
*/
|
|
47
|
+
export function tool(server, spec) {
|
|
48
|
+
server.registerTool(spec.name, {
|
|
49
|
+
description: spec.description,
|
|
50
|
+
inputSchema: spec.inputSchema.shape,
|
|
51
|
+
...(spec.annotations ? { annotations: spec.annotations } : {}),
|
|
52
|
+
}, async (args, extra) => {
|
|
53
|
+
const store = requestStore.getStore();
|
|
54
|
+
if (!store) {
|
|
55
|
+
// Unreachable: createMcpServer.mount() always wraps tool dispatch in
|
|
56
|
+
// requestStore.run(). Throw to surface the bug rather than silently
|
|
57
|
+
// returning an error envelope without a logger.
|
|
58
|
+
throw new Error("mcp tool invoked outside request scope");
|
|
59
|
+
}
|
|
60
|
+
const sessionId = store.sessionId ?? extra?.sessionId;
|
|
61
|
+
const logger = store.logger.child({ tool: spec.name, sessionId });
|
|
62
|
+
try {
|
|
63
|
+
const result = await withTransaction(store.postgan, logger, (transaction) => {
|
|
64
|
+
const ctx = {
|
|
65
|
+
user: store.user,
|
|
66
|
+
postgan: store.postgan,
|
|
67
|
+
transaction,
|
|
68
|
+
sessionId,
|
|
69
|
+
logger,
|
|
70
|
+
};
|
|
71
|
+
return spec.handler(args, ctx);
|
|
72
|
+
});
|
|
73
|
+
return {
|
|
74
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
75
|
+
structuredContent: result,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
const mapped = store.errorMapper?.(err) ?? null;
|
|
80
|
+
return toCallToolError(mapped, err, {
|
|
81
|
+
tool: spec.name,
|
|
82
|
+
sessionId,
|
|
83
|
+
logger,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tool/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Postgan, PostganTransaction } from "@ganintegrity/postgan";
|
|
2
|
+
import type { Logger } from "pino";
|
|
3
|
+
import type { z } from "zod";
|
|
4
|
+
import type { AuthUser } from "../auth/auth.types.ts";
|
|
5
|
+
/**
|
|
6
|
+
* What every tool handler receives as its second argument.
|
|
7
|
+
*/
|
|
8
|
+
export interface ToolContext {
|
|
9
|
+
/** Authenticated user. Cast to your service's richer type for extra fields. */
|
|
10
|
+
user: AuthUser;
|
|
11
|
+
/** Per-request Postgan instance. */
|
|
12
|
+
postgan: Postgan;
|
|
13
|
+
/**
|
|
14
|
+
* Transaction opened immediately before the handler runs. Auto-committed
|
|
15
|
+
* if the handler resolves; rolled back if it throws. **Do not call
|
|
16
|
+
* `commit()` or `rollback()` yourself** — the library owns the lifecycle.
|
|
17
|
+
*/
|
|
18
|
+
transaction: PostganTransaction;
|
|
19
|
+
/** Forwarded `X-Session-Id` header value if present. Useful for log correlation. */
|
|
20
|
+
sessionId?: string;
|
|
21
|
+
/** Logger childed with this tool's name and the session id. */
|
|
22
|
+
logger: Logger;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* MCP-spec tool annotations surfaced to clients during `tools/list`. All
|
|
26
|
+
* fields are optional hints — clients use them to decide things like whether
|
|
27
|
+
* to auto-approve calls or warn users.
|
|
28
|
+
*
|
|
29
|
+
* Pick honestly: `readOnlyHint: true` on a tool that mutates is worse than
|
|
30
|
+
* leaving the hint unset.
|
|
31
|
+
*/
|
|
32
|
+
export interface ToolAnnotations {
|
|
33
|
+
/** Human-readable display title (defaults to `name` if omitted). */
|
|
34
|
+
title?: string;
|
|
35
|
+
/** True if the tool only reads — never writes or mutates state. */
|
|
36
|
+
readOnlyHint?: boolean;
|
|
37
|
+
/** True if the tool may delete or destroy data. */
|
|
38
|
+
destructiveHint?: boolean;
|
|
39
|
+
/** True if calling twice with the same args produces the same result. */
|
|
40
|
+
idempotentHint?: boolean;
|
|
41
|
+
/** True if the tool reaches out to external systems (network, third-party APIs). */
|
|
42
|
+
openWorldHint?: boolean;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Specification passed to `tool()`. Defines the tool's name, description,
|
|
46
|
+
* input schema, optional annotations, and the handler that executes it.
|
|
47
|
+
*
|
|
48
|
+
* @template Schema Zod object schema describing the tool's input.
|
|
49
|
+
* @template Output Plain object the handler returns. Surfaced as both a JSON
|
|
50
|
+
* `text` content block and as `structuredContent`.
|
|
51
|
+
*/
|
|
52
|
+
export interface ToolSpec<Schema extends z.ZodObject, Output> {
|
|
53
|
+
/** Stable identifier the agent uses to invoke the tool. */
|
|
54
|
+
name: string;
|
|
55
|
+
/**
|
|
56
|
+
* Description shown to the agent. Treat this as a prompt — be precise about
|
|
57
|
+
* when to call the tool, when not to, and how to interpret the output.
|
|
58
|
+
*/
|
|
59
|
+
description: string;
|
|
60
|
+
/** Zod object schema. The library forwards `.shape` to the SDK. */
|
|
61
|
+
inputSchema: Schema;
|
|
62
|
+
/** Optional MCP annotations; see {@link ToolAnnotations}. */
|
|
63
|
+
annotations?: ToolAnnotations;
|
|
64
|
+
/**
|
|
65
|
+
* The actual tool implementation. Receives the parsed input plus the
|
|
66
|
+
* per-call {@link ToolContext}. Throw to signal failure — the wrapper
|
|
67
|
+
* rolls back the transaction and routes the error through the consumer's
|
|
68
|
+
* `McpErrorMapper` to build an MCP error envelope.
|
|
69
|
+
*/
|
|
70
|
+
handler: (args: z.infer<Schema>, ctx: ToolContext) => Promise<Output>;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=tool.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool.types.d.ts","sourceRoot":"","sources":["../../src/tool/tool.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEtD;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,+EAA+E;IAC/E,IAAI,EAAE,QAAQ,CAAC;IACf,oCAAoC;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,WAAW,EAAE,kBAAkB,CAAC;IAChC,oFAAoF;IACpF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,mDAAmD;IACnD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,yEAAyE;IACzE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,oFAAoF;IACpF,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,QAAQ,CAAC,MAAM,SAAS,CAAC,CAAC,SAAS,EAAE,MAAM;IAC1D,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,WAAW,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B;;;;;OAKG;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACvE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool.types.js","sourceRoot":"","sources":["../../src/tool/tool.types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ganintegrity/mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Provides tooling to scaffold an MCP server",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"test:watch": "vitest",
|
|
22
|
+
"test:coverage": "vitest run --coverage",
|
|
23
|
+
"lint": "eslint 'src/**/*.ts'",
|
|
24
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.test.json",
|
|
25
|
+
"format": "pnpx prettier . -w",
|
|
26
|
+
"format:check": "pnpx prettier . -c",
|
|
27
|
+
"typecheck:watch": "tsc --noEmit --watch"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"mcp",
|
|
31
|
+
"ai"
|
|
32
|
+
],
|
|
33
|
+
"author": "GAN Integrity",
|
|
34
|
+
"license": "Proprietary",
|
|
35
|
+
"packageManager": "pnpm@10.33.0",
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@ganintegrity/postgan": "^28",
|
|
38
|
+
"@ganintegrity/postgres-migrations": "^16",
|
|
39
|
+
"express": "^5",
|
|
40
|
+
"pino": "^10",
|
|
41
|
+
"zod": "^4"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@modelcontextprotocol/sdk": "1.29.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@commitlint/cli": "^20.5.3",
|
|
48
|
+
"@commitlint/config-conventional": "^20.5.3",
|
|
49
|
+
"@eslint/js": "^10.0.1",
|
|
50
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
51
|
+
"@semantic-release/gitlab": "^13.3.2",
|
|
52
|
+
"@types/express": "^5.0.6",
|
|
53
|
+
"@types/node": "^25.6.0",
|
|
54
|
+
"@types/supertest": "^7.2.0",
|
|
55
|
+
"@vitest/coverage-v8": "^4.1.4",
|
|
56
|
+
"eslint": "^10.3.0",
|
|
57
|
+
"fallow": "^2.58.0",
|
|
58
|
+
"prettier": "^3.8.3",
|
|
59
|
+
"semantic-release": "^25.0.3",
|
|
60
|
+
"supertest": "^7.2.2",
|
|
61
|
+
"typescript": "^6.0.3",
|
|
62
|
+
"typescript-eslint": "^8.59.1",
|
|
63
|
+
"vitest": "^4.1.4"
|
|
64
|
+
}
|
|
65
|
+
}
|