@tailrace/mcp 0.1.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 +21 -0
- package/README.md +37 -0
- package/dist/index.cjs +217 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +52 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +189 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tailrace
|
|
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,37 @@
|
|
|
1
|
+
# @tailrace/mcp
|
|
2
|
+
|
|
3
|
+
Policy enforcement for [MCP](https://modelcontextprotocol.io) client transports
|
|
4
|
+
(`@modelcontextprotocol/sdk` `>=1`). Wrap a transport so outbound `tools/call` arguments and
|
|
5
|
+
inbound tool / `resources/read` results pass through Tailrace before they leave the process.
|
|
6
|
+
|
|
7
|
+
A blocked call becomes a JSON-RPC error (`code: -32001`) instead of tearing down the transport.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @tailrace/core @tailrace/mcp @modelcontextprotocol/sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quickstart
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { createTailrace } from "@tailrace/core";
|
|
19
|
+
import { withMcp } from "@tailrace/mcp";
|
|
20
|
+
|
|
21
|
+
const tailrace = withMcp(createTailrace());
|
|
22
|
+
const transport = tailrace.transport(sseTransport, { server: "salesforce" });
|
|
23
|
+
// Pass `transport` to the MCP Client - secrets never reach the server.
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Standalone form: `wrapTransport(tailrace, transport, { server: "salesforce" })`.
|
|
27
|
+
|
|
28
|
+
## What gets scanned
|
|
29
|
+
|
|
30
|
+
| Direction | Method | Boundary |
|
|
31
|
+
| --------- | ----------------------- | -------------------------------------------------------- |
|
|
32
|
+
| Out | `tools/call` arguments | `{ kind: "mcp", server, tool: name, direction: "out" }` |
|
|
33
|
+
| In | `tools/call` result | same with `direction: "in"` |
|
|
34
|
+
| In | `resources/read` result | `{ kind: "mcp", server, tool: "read", direction: "in" }` |
|
|
35
|
+
|
|
36
|
+
All other JSON-RPC messages pass through. Spec: [`docs/integrations.md`](../../docs/integrations.md) §2.
|
|
37
|
+
Guide: [`docs/guides/mcp-integration.md`](../../docs/guides/mcp-integration.md).
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
withMcp: () => withMcp,
|
|
24
|
+
wrapTransport: () => wrapTransport
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/wrap-transport.ts
|
|
29
|
+
var import_core = require("@tailrace/core");
|
|
30
|
+
|
|
31
|
+
// src/internal/context.ts
|
|
32
|
+
function resolveWorkflowId(opts) {
|
|
33
|
+
return opts.workflowId ?? "default";
|
|
34
|
+
}
|
|
35
|
+
function buildCheckContext(boundary, opts) {
|
|
36
|
+
return {
|
|
37
|
+
boundary,
|
|
38
|
+
identity: { agent: opts.agent ?? "default" },
|
|
39
|
+
workflowId: resolveWorkflowId(opts)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
async function checkWithOpts(tailrace, input, boundary, opts, checkOptions) {
|
|
43
|
+
const ctx = buildCheckContext(boundary, opts);
|
|
44
|
+
const result = await tailrace.check(input, ctx, checkOptions);
|
|
45
|
+
if (opts.onDecision !== void 0 && result.decisions.length > 0) {
|
|
46
|
+
opts.onDecision(result.decisions);
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/internal/jsonrpc.ts
|
|
52
|
+
var POLICY_VIOLATION_RPC_CODE = -32001;
|
|
53
|
+
function isJsonRpcRequest(message) {
|
|
54
|
+
return typeof message === "object" && message !== null && "method" in message && "id" in message && !("result" in message) && !("error" in message);
|
|
55
|
+
}
|
|
56
|
+
function isJsonRpcResponse(message) {
|
|
57
|
+
return typeof message === "object" && message !== null && "id" in message && ("result" in message || "error" in message) && !("method" in message);
|
|
58
|
+
}
|
|
59
|
+
function policyViolationRpcError(id, err) {
|
|
60
|
+
const first = err.decisions[0];
|
|
61
|
+
const entity = first?.entity ?? "unknown";
|
|
62
|
+
const rule = first?.rule ?? "unknown";
|
|
63
|
+
return {
|
|
64
|
+
jsonrpc: "2.0",
|
|
65
|
+
id,
|
|
66
|
+
error: {
|
|
67
|
+
code: POLICY_VIOLATION_RPC_CODE,
|
|
68
|
+
message: `Blocked by data policy: ${entity} may not be sent to mcp (rule: ${rule})`,
|
|
69
|
+
data: {
|
|
70
|
+
type: "policy_violation",
|
|
71
|
+
entity,
|
|
72
|
+
rule
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/internal/messages.ts
|
|
79
|
+
function asCheckable(value) {
|
|
80
|
+
if (typeof value === "string") return value;
|
|
81
|
+
if (value === null || value === void 0) return {};
|
|
82
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
return { value };
|
|
86
|
+
}
|
|
87
|
+
function unwrapCheckable(original, checked) {
|
|
88
|
+
if (typeof original === "string") return checked;
|
|
89
|
+
if (original === null || original === void 0) return original;
|
|
90
|
+
if (typeof original === "object" && !Array.isArray(original)) return checked;
|
|
91
|
+
if (typeof checked === "object" && checked !== null && "value" in checked && (Array.isArray(original) || typeof original === "number" || typeof original === "boolean")) {
|
|
92
|
+
return checked["value"];
|
|
93
|
+
}
|
|
94
|
+
return checked;
|
|
95
|
+
}
|
|
96
|
+
function getToolsCallName(params) {
|
|
97
|
+
if (typeof params !== "object" || params === null) return void 0;
|
|
98
|
+
const name = params.name;
|
|
99
|
+
return typeof name === "string" ? name : void 0;
|
|
100
|
+
}
|
|
101
|
+
function getToolsCallArguments(params) {
|
|
102
|
+
if (typeof params !== "object" || params === null) return {};
|
|
103
|
+
const args = params.arguments;
|
|
104
|
+
return args === void 0 ? {} : args;
|
|
105
|
+
}
|
|
106
|
+
function withRewrittenArguments(params, checkedArgs) {
|
|
107
|
+
if (typeof params !== "object" || params === null) {
|
|
108
|
+
return { arguments: checkedArgs };
|
|
109
|
+
}
|
|
110
|
+
return { ...params, arguments: checkedArgs };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/wrap-transport.ts
|
|
114
|
+
function wrapTransport(tailrace, transport, opts) {
|
|
115
|
+
const pending = /* @__PURE__ */ new Map();
|
|
116
|
+
const originalSend = transport.send.bind(transport);
|
|
117
|
+
let userOnMessage = transport.onmessage;
|
|
118
|
+
const deliver = (message, extra) => {
|
|
119
|
+
userOnMessage?.(message, extra);
|
|
120
|
+
};
|
|
121
|
+
const pendingKey = (id) => String(id);
|
|
122
|
+
const wrappedOnMessage = (message, extra) => {
|
|
123
|
+
void processInbound(message, extra);
|
|
124
|
+
};
|
|
125
|
+
async function processInbound(message, extra) {
|
|
126
|
+
if (!isJsonRpcResponse(message) || !("result" in message)) {
|
|
127
|
+
deliver(message, extra);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const key = pendingKey(message.id);
|
|
131
|
+
const info = pending.get(key);
|
|
132
|
+
if (info === void 0) {
|
|
133
|
+
deliver(message, extra);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
pending.delete(key);
|
|
137
|
+
const boundary = {
|
|
138
|
+
kind: "mcp",
|
|
139
|
+
server: opts.server,
|
|
140
|
+
tool: info.tool,
|
|
141
|
+
direction: "in"
|
|
142
|
+
};
|
|
143
|
+
try {
|
|
144
|
+
const { output } = await checkWithOpts(tailrace, asCheckable(message.result), boundary, opts);
|
|
145
|
+
const rewritten = {
|
|
146
|
+
...message,
|
|
147
|
+
result: unwrapCheckable(message.result, output)
|
|
148
|
+
};
|
|
149
|
+
deliver(rewritten, extra);
|
|
150
|
+
} catch (err) {
|
|
151
|
+
if (err instanceof import_core.PolicyViolationError) {
|
|
152
|
+
deliver(policyViolationRpcError(message.id, err), extra);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
throw err;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
transport.send = async (message, options) => {
|
|
159
|
+
if (!isJsonRpcRequest(message)) {
|
|
160
|
+
return originalSend(message, options);
|
|
161
|
+
}
|
|
162
|
+
if (message.method === "tools/call") {
|
|
163
|
+
const tool = getToolsCallName(message.params) ?? "unknown";
|
|
164
|
+
const args = getToolsCallArguments(message.params);
|
|
165
|
+
const boundary = {
|
|
166
|
+
kind: "mcp",
|
|
167
|
+
server: opts.server,
|
|
168
|
+
tool,
|
|
169
|
+
direction: "out"
|
|
170
|
+
};
|
|
171
|
+
try {
|
|
172
|
+
const { output } = await checkWithOpts(tailrace, asCheckable(args), boundary, opts);
|
|
173
|
+
const checkedArgs = unwrapCheckable(args, output);
|
|
174
|
+
const nextParams = withRewrittenArguments(message.params, checkedArgs);
|
|
175
|
+
const nextMessage = {
|
|
176
|
+
...message,
|
|
177
|
+
params: nextParams
|
|
178
|
+
};
|
|
179
|
+
pending.set(pendingKey(message.id), { method: "tools/call", tool });
|
|
180
|
+
return originalSend(nextMessage, options);
|
|
181
|
+
} catch (err) {
|
|
182
|
+
if (err instanceof import_core.PolicyViolationError) {
|
|
183
|
+
deliver(policyViolationRpcError(message.id, err));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
throw err;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (message.method === "resources/read") {
|
|
190
|
+
pending.set(pendingKey(message.id), { method: "resources/read", tool: "read" });
|
|
191
|
+
return originalSend(message, options);
|
|
192
|
+
}
|
|
193
|
+
return originalSend(message, options);
|
|
194
|
+
};
|
|
195
|
+
Object.defineProperty(transport, "onmessage", {
|
|
196
|
+
configurable: true,
|
|
197
|
+
enumerable: true,
|
|
198
|
+
get: () => wrappedOnMessage,
|
|
199
|
+
set: (cb) => {
|
|
200
|
+
userOnMessage = cb;
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
return transport;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// src/fluent.ts
|
|
207
|
+
function withMcp(tailrace) {
|
|
208
|
+
return Object.assign(tailrace, {
|
|
209
|
+
transport: (transport, opts) => wrapTransport(tailrace, transport, opts)
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
213
|
+
0 && (module.exports = {
|
|
214
|
+
withMcp,
|
|
215
|
+
wrapTransport
|
|
216
|
+
});
|
|
217
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/wrap-transport.ts","../src/internal/context.ts","../src/internal/jsonrpc.ts","../src/internal/messages.ts","../src/fluent.ts"],"sourcesContent":["/**\n * @tailrace/mcp - MCP client transport wrapper.\n *\n * Intercepts outbound `tools/call` arguments and inbound results (and `resources/read`\n * results), applying policy at the `mcp` boundary. A blocked outbound call is turned into\n * a JSON-RPC error result rather than tearing down the transport (docs/integrations.md §2).\n */\n\nexport type { McpWrapOptions, TailraceWithMcp } from \"./types\";\nexport { wrapTransport } from \"./wrap-transport\";\nexport { withMcp } from \"./fluent\";\n","/**\n * wrapTransport — MCP client transport proxy (docs/integrations.md §2).\n */\n\nimport type { Tailrace } from \"@tailrace/core\";\nimport { PolicyViolationError } from \"@tailrace/core\";\nimport type {\n Transport,\n TransportSendOptions,\n} from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport type {\n JSONRPCMessage,\n MessageExtraInfo,\n RequestId,\n} from \"@modelcontextprotocol/sdk/types.js\";\n\nimport { checkWithOpts } from \"./internal/context\";\nimport { isJsonRpcRequest, isJsonRpcResponse, policyViolationRpcError } from \"./internal/jsonrpc\";\nimport {\n asCheckable,\n getToolsCallArguments,\n getToolsCallName,\n unwrapCheckable,\n withRewrittenArguments,\n} from \"./internal/messages\";\nimport type { McpWrapOptions } from \"./types\";\n\ninterface PendingCall {\n method: \"tools/call\" | \"resources/read\";\n tool: string;\n}\n\n/**\n * Wrap an MCP client transport so its calls pass through policy. Returns the same transport type.\n *\n * @example\n * ```ts\n * const transport = wrapTransport(tailrace, sseTransport, { server: \"salesforce\" });\n * ```\n */\nexport function wrapTransport<T extends Transport>(\n tailrace: Tailrace,\n transport: T,\n opts: McpWrapOptions,\n): T {\n const pending = new Map<string, PendingCall>();\n const originalSend = transport.send.bind(transport);\n\n let userOnMessage: Transport[\"onmessage\"] = transport.onmessage;\n\n const deliver = (message: JSONRPCMessage, extra?: MessageExtraInfo): void => {\n userOnMessage?.(message, extra);\n };\n\n const pendingKey = (id: RequestId): string => String(id);\n\n const wrappedOnMessage = (message: JSONRPCMessage, extra?: MessageExtraInfo): void => {\n void processInbound(message, extra);\n };\n\n async function processInbound(message: JSONRPCMessage, extra?: MessageExtraInfo): Promise<void> {\n if (!isJsonRpcResponse(message) || !(\"result\" in message)) {\n deliver(message, extra);\n return;\n }\n\n const key = pendingKey(message.id);\n const info = pending.get(key);\n if (info === undefined) {\n deliver(message, extra);\n return;\n }\n pending.delete(key);\n\n const boundary = {\n kind: \"mcp\" as const,\n server: opts.server,\n tool: info.tool,\n direction: \"in\" as const,\n };\n\n try {\n const { output } = await checkWithOpts(tailrace, asCheckable(message.result), boundary, opts);\n const rewritten = {\n ...message,\n result: unwrapCheckable(message.result, output),\n } as JSONRPCMessage;\n deliver(rewritten, extra);\n } catch (err) {\n if (err instanceof PolicyViolationError) {\n deliver(policyViolationRpcError(message.id, err), extra);\n return;\n }\n throw err;\n }\n }\n\n transport.send = async (\n message: JSONRPCMessage,\n options?: TransportSendOptions,\n ): Promise<void> => {\n if (!isJsonRpcRequest(message)) {\n return originalSend(message, options);\n }\n\n if (message.method === \"tools/call\") {\n const tool = getToolsCallName(message.params) ?? \"unknown\";\n const args = getToolsCallArguments(message.params);\n const boundary = {\n kind: \"mcp\" as const,\n server: opts.server,\n tool,\n direction: \"out\" as const,\n };\n\n try {\n const { output } = await checkWithOpts(tailrace, asCheckable(args), boundary, opts);\n const checkedArgs = unwrapCheckable(args, output);\n const nextParams = withRewrittenArguments(message.params, checkedArgs);\n const nextMessage = {\n ...message,\n params: nextParams,\n } as JSONRPCMessage;\n pending.set(pendingKey(message.id), { method: \"tools/call\", tool });\n return originalSend(nextMessage, options);\n } catch (err) {\n if (err instanceof PolicyViolationError) {\n deliver(policyViolationRpcError(message.id, err));\n return;\n }\n throw err;\n }\n }\n\n if (message.method === \"resources/read\") {\n // Outbound resources/read params are not scanned in v0.1; track for inbound result scan.\n pending.set(pendingKey(message.id), { method: \"resources/read\", tool: \"read\" });\n return originalSend(message, options);\n }\n\n return originalSend(message, options);\n };\n\n Object.defineProperty(transport, \"onmessage\", {\n configurable: true,\n enumerable: true,\n get: () => wrappedOnMessage,\n set: (cb: Transport[\"onmessage\"]) => {\n userOnMessage = cb;\n },\n });\n\n return transport;\n}\n","/**\n * Build CheckContext from wrap options + mcp boundary.\n */\n\nimport type {\n Boundary,\n CheckContext,\n CheckOptions,\n CheckResult,\n JsonObject,\n Tailrace,\n} from \"@tailrace/core\";\n\nimport type { McpWrapOptions } from \"../types\";\n\nexport function resolveWorkflowId(opts: McpWrapOptions): string {\n return opts.workflowId ?? \"default\";\n}\n\nexport function buildCheckContext(boundary: Boundary, opts: McpWrapOptions): CheckContext {\n return {\n boundary,\n identity: { agent: opts.agent ?? \"default\" },\n workflowId: resolveWorkflowId(opts),\n };\n}\n\n/**\n * Run check and forward decisions to the optional wrap-level onDecision callback.\n */\nexport async function checkWithOpts<T extends string | JsonObject>(\n tailrace: Tailrace,\n input: T,\n boundary: Boundary,\n opts: McpWrapOptions,\n checkOptions?: CheckOptions,\n): Promise<CheckResult<T>> {\n const ctx = buildCheckContext(boundary, opts);\n const result = await tailrace.check(input, ctx, checkOptions);\n if (opts.onDecision !== undefined && result.decisions.length > 0) {\n opts.onDecision(result.decisions);\n }\n return result;\n}\n","/**\n * JSON-RPC helpers for MCP policy error synthesis (docs/integrations.md §2).\n */\n\nimport type { PolicyViolationError } from \"@tailrace/core\";\nimport type { JSONRPCError, JSONRPCMessage, RequestId } from \"@modelcontextprotocol/sdk/types.js\";\n\n/** JSON-RPC application error for Tailrace policy blocks. */\nexport const POLICY_VIOLATION_RPC_CODE = -32001;\n\nexport function isJsonRpcRequest(\n message: JSONRPCMessage,\n): message is JSONRPCMessage & { method: string; id: RequestId; params?: unknown } {\n return (\n typeof message === \"object\" &&\n message !== null &&\n \"method\" in message &&\n \"id\" in message &&\n !(\"result\" in message) &&\n !(\"error\" in message)\n );\n}\n\nexport function isJsonRpcResponse(\n message: JSONRPCMessage,\n): message is JSONRPCMessage & { id: RequestId } & ({ result: unknown } | { error: unknown }) {\n return (\n typeof message === \"object\" &&\n message !== null &&\n \"id\" in message &&\n (\"result\" in message || \"error\" in message) &&\n !(\"method\" in message)\n );\n}\n\n/**\n * Build a JSON-RPC error response from a PolicyViolationError.\n * Never includes the raw detected value.\n */\nexport function policyViolationRpcError(id: RequestId, err: PolicyViolationError): JSONRPCError {\n const first = err.decisions[0];\n const entity = first?.entity ?? \"unknown\";\n const rule = first?.rule ?? \"unknown\";\n return {\n jsonrpc: \"2.0\",\n id,\n error: {\n code: POLICY_VIOLATION_RPC_CODE,\n message: `Blocked by data policy: ${entity} may not be sent to mcp (rule: ${rule})`,\n data: {\n type: \"policy_violation\",\n entity,\n rule,\n },\n },\n };\n}\n","/**\n * Extract / rewrite tools/call arguments and MCP result payloads for check().\n */\n\nimport type { JsonObject, JsonValue } from \"@tailrace/core\";\n\n/** Wrap opaque values so object-scan can walk string leaves. */\nexport function asCheckable(value: unknown): string | JsonObject {\n if (typeof value === \"string\") return value;\n if (value === null || value === undefined) return {};\n if (typeof value === \"object\" && !Array.isArray(value)) {\n return value as JsonObject;\n }\n return { value: value as JsonValue };\n}\n\nexport function unwrapCheckable(original: unknown, checked: string | JsonObject): unknown {\n if (typeof original === \"string\") return checked;\n if (original === null || original === undefined) return original;\n if (typeof original === \"object\" && !Array.isArray(original)) return checked;\n if (\n typeof checked === \"object\" &&\n checked !== null &&\n \"value\" in checked &&\n (Array.isArray(original) || typeof original === \"number\" || typeof original === \"boolean\")\n ) {\n return (checked as JsonObject)[\"value\"];\n }\n return checked;\n}\n\nexport function getToolsCallName(params: unknown): string | undefined {\n if (typeof params !== \"object\" || params === null) return undefined;\n const name = (params as { name?: unknown }).name;\n return typeof name === \"string\" ? name : undefined;\n}\n\nexport function getToolsCallArguments(params: unknown): unknown {\n if (typeof params !== \"object\" || params === null) return {};\n const args = (params as { arguments?: unknown }).arguments;\n return args === undefined ? {} : args;\n}\n\n/**\n * Rewrite `params.arguments` after a successful check. Returns a shallow-cloned params object.\n */\nexport function withRewrittenArguments(params: unknown, checkedArgs: unknown): unknown {\n if (typeof params !== \"object\" || params === null) {\n return { arguments: checkedArgs };\n }\n return { ...(params as Record<string, unknown>), arguments: checkedArgs };\n}\n","/**\n * Fluent MCP helpers without core importing the MCP SDK (docs/integrations.md §2).\n */\n\nimport type { Tailrace } from \"@tailrace/core\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\n\nimport type { McpWrapOptions, TailraceWithMcp } from \"./types\";\nimport { wrapTransport } from \"./wrap-transport\";\n\n/**\n * Attach a `transport` helper to an existing Tailrace instance.\n *\n * @example\n * ```ts\n * const tailrace = withMcp(createTailrace());\n * const transport = tailrace.transport(sseTransport, { server: \"salesforce\" });\n * ```\n */\nexport function withMcp(tailrace: Tailrace): TailraceWithMcp {\n return Object.assign(tailrace, {\n transport: <T extends Transport>(transport: T, opts: McpWrapOptions) =>\n wrapTransport(tailrace, transport, opts),\n });\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Decision, Tailrace } from '@tailrace/core';
|
|
2
|
+
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Public types for @tailrace/mcp.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
interface McpWrapOptions {
|
|
9
|
+
/** MCP server identity used in policy boundary keys (`mcp:{server}/{tool}`). */
|
|
10
|
+
server: string;
|
|
11
|
+
agent?: string;
|
|
12
|
+
workflowId?: string;
|
|
13
|
+
onDecision?: (decisions: Decision[]) => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Tailrace instance with fluent MCP helpers. Produced by {@link withMcp};
|
|
17
|
+
* core stays framework-free.
|
|
18
|
+
*/
|
|
19
|
+
interface TailraceWithMcp extends Tailrace {
|
|
20
|
+
transport<T extends Transport>(transport: T, opts: McpWrapOptions): T;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* wrapTransport — MCP client transport proxy (docs/integrations.md §2).
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Wrap an MCP client transport so its calls pass through policy. Returns the same transport type.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const transport = wrapTransport(tailrace, sseTransport, { server: "salesforce" });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
declare function wrapTransport<T extends Transport>(tailrace: Tailrace, transport: T, opts: McpWrapOptions): T;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Fluent MCP helpers without core importing the MCP SDK (docs/integrations.md §2).
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Attach a `transport` helper to an existing Tailrace instance.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const tailrace = withMcp(createTailrace());
|
|
47
|
+
* const transport = tailrace.transport(sseTransport, { server: "salesforce" });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
declare function withMcp(tailrace: Tailrace): TailraceWithMcp;
|
|
51
|
+
|
|
52
|
+
export { type McpWrapOptions, type TailraceWithMcp, withMcp, wrapTransport };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Decision, Tailrace } from '@tailrace/core';
|
|
2
|
+
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Public types for @tailrace/mcp.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
interface McpWrapOptions {
|
|
9
|
+
/** MCP server identity used in policy boundary keys (`mcp:{server}/{tool}`). */
|
|
10
|
+
server: string;
|
|
11
|
+
agent?: string;
|
|
12
|
+
workflowId?: string;
|
|
13
|
+
onDecision?: (decisions: Decision[]) => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Tailrace instance with fluent MCP helpers. Produced by {@link withMcp};
|
|
17
|
+
* core stays framework-free.
|
|
18
|
+
*/
|
|
19
|
+
interface TailraceWithMcp extends Tailrace {
|
|
20
|
+
transport<T extends Transport>(transport: T, opts: McpWrapOptions): T;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* wrapTransport — MCP client transport proxy (docs/integrations.md §2).
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Wrap an MCP client transport so its calls pass through policy. Returns the same transport type.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const transport = wrapTransport(tailrace, sseTransport, { server: "salesforce" });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
declare function wrapTransport<T extends Transport>(tailrace: Tailrace, transport: T, opts: McpWrapOptions): T;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Fluent MCP helpers without core importing the MCP SDK (docs/integrations.md §2).
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Attach a `transport` helper to an existing Tailrace instance.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const tailrace = withMcp(createTailrace());
|
|
47
|
+
* const transport = tailrace.transport(sseTransport, { server: "salesforce" });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
declare function withMcp(tailrace: Tailrace): TailraceWithMcp;
|
|
51
|
+
|
|
52
|
+
export { type McpWrapOptions, type TailraceWithMcp, withMcp, wrapTransport };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// src/wrap-transport.ts
|
|
2
|
+
import { PolicyViolationError } from "@tailrace/core";
|
|
3
|
+
|
|
4
|
+
// src/internal/context.ts
|
|
5
|
+
function resolveWorkflowId(opts) {
|
|
6
|
+
return opts.workflowId ?? "default";
|
|
7
|
+
}
|
|
8
|
+
function buildCheckContext(boundary, opts) {
|
|
9
|
+
return {
|
|
10
|
+
boundary,
|
|
11
|
+
identity: { agent: opts.agent ?? "default" },
|
|
12
|
+
workflowId: resolveWorkflowId(opts)
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
async function checkWithOpts(tailrace, input, boundary, opts, checkOptions) {
|
|
16
|
+
const ctx = buildCheckContext(boundary, opts);
|
|
17
|
+
const result = await tailrace.check(input, ctx, checkOptions);
|
|
18
|
+
if (opts.onDecision !== void 0 && result.decisions.length > 0) {
|
|
19
|
+
opts.onDecision(result.decisions);
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/internal/jsonrpc.ts
|
|
25
|
+
var POLICY_VIOLATION_RPC_CODE = -32001;
|
|
26
|
+
function isJsonRpcRequest(message) {
|
|
27
|
+
return typeof message === "object" && message !== null && "method" in message && "id" in message && !("result" in message) && !("error" in message);
|
|
28
|
+
}
|
|
29
|
+
function isJsonRpcResponse(message) {
|
|
30
|
+
return typeof message === "object" && message !== null && "id" in message && ("result" in message || "error" in message) && !("method" in message);
|
|
31
|
+
}
|
|
32
|
+
function policyViolationRpcError(id, err) {
|
|
33
|
+
const first = err.decisions[0];
|
|
34
|
+
const entity = first?.entity ?? "unknown";
|
|
35
|
+
const rule = first?.rule ?? "unknown";
|
|
36
|
+
return {
|
|
37
|
+
jsonrpc: "2.0",
|
|
38
|
+
id,
|
|
39
|
+
error: {
|
|
40
|
+
code: POLICY_VIOLATION_RPC_CODE,
|
|
41
|
+
message: `Blocked by data policy: ${entity} may not be sent to mcp (rule: ${rule})`,
|
|
42
|
+
data: {
|
|
43
|
+
type: "policy_violation",
|
|
44
|
+
entity,
|
|
45
|
+
rule
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/internal/messages.ts
|
|
52
|
+
function asCheckable(value) {
|
|
53
|
+
if (typeof value === "string") return value;
|
|
54
|
+
if (value === null || value === void 0) return {};
|
|
55
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
return { value };
|
|
59
|
+
}
|
|
60
|
+
function unwrapCheckable(original, checked) {
|
|
61
|
+
if (typeof original === "string") return checked;
|
|
62
|
+
if (original === null || original === void 0) return original;
|
|
63
|
+
if (typeof original === "object" && !Array.isArray(original)) return checked;
|
|
64
|
+
if (typeof checked === "object" && checked !== null && "value" in checked && (Array.isArray(original) || typeof original === "number" || typeof original === "boolean")) {
|
|
65
|
+
return checked["value"];
|
|
66
|
+
}
|
|
67
|
+
return checked;
|
|
68
|
+
}
|
|
69
|
+
function getToolsCallName(params) {
|
|
70
|
+
if (typeof params !== "object" || params === null) return void 0;
|
|
71
|
+
const name = params.name;
|
|
72
|
+
return typeof name === "string" ? name : void 0;
|
|
73
|
+
}
|
|
74
|
+
function getToolsCallArguments(params) {
|
|
75
|
+
if (typeof params !== "object" || params === null) return {};
|
|
76
|
+
const args = params.arguments;
|
|
77
|
+
return args === void 0 ? {} : args;
|
|
78
|
+
}
|
|
79
|
+
function withRewrittenArguments(params, checkedArgs) {
|
|
80
|
+
if (typeof params !== "object" || params === null) {
|
|
81
|
+
return { arguments: checkedArgs };
|
|
82
|
+
}
|
|
83
|
+
return { ...params, arguments: checkedArgs };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/wrap-transport.ts
|
|
87
|
+
function wrapTransport(tailrace, transport, opts) {
|
|
88
|
+
const pending = /* @__PURE__ */ new Map();
|
|
89
|
+
const originalSend = transport.send.bind(transport);
|
|
90
|
+
let userOnMessage = transport.onmessage;
|
|
91
|
+
const deliver = (message, extra) => {
|
|
92
|
+
userOnMessage?.(message, extra);
|
|
93
|
+
};
|
|
94
|
+
const pendingKey = (id) => String(id);
|
|
95
|
+
const wrappedOnMessage = (message, extra) => {
|
|
96
|
+
void processInbound(message, extra);
|
|
97
|
+
};
|
|
98
|
+
async function processInbound(message, extra) {
|
|
99
|
+
if (!isJsonRpcResponse(message) || !("result" in message)) {
|
|
100
|
+
deliver(message, extra);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const key = pendingKey(message.id);
|
|
104
|
+
const info = pending.get(key);
|
|
105
|
+
if (info === void 0) {
|
|
106
|
+
deliver(message, extra);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
pending.delete(key);
|
|
110
|
+
const boundary = {
|
|
111
|
+
kind: "mcp",
|
|
112
|
+
server: opts.server,
|
|
113
|
+
tool: info.tool,
|
|
114
|
+
direction: "in"
|
|
115
|
+
};
|
|
116
|
+
try {
|
|
117
|
+
const { output } = await checkWithOpts(tailrace, asCheckable(message.result), boundary, opts);
|
|
118
|
+
const rewritten = {
|
|
119
|
+
...message,
|
|
120
|
+
result: unwrapCheckable(message.result, output)
|
|
121
|
+
};
|
|
122
|
+
deliver(rewritten, extra);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
if (err instanceof PolicyViolationError) {
|
|
125
|
+
deliver(policyViolationRpcError(message.id, err), extra);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
throw err;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
transport.send = async (message, options) => {
|
|
132
|
+
if (!isJsonRpcRequest(message)) {
|
|
133
|
+
return originalSend(message, options);
|
|
134
|
+
}
|
|
135
|
+
if (message.method === "tools/call") {
|
|
136
|
+
const tool = getToolsCallName(message.params) ?? "unknown";
|
|
137
|
+
const args = getToolsCallArguments(message.params);
|
|
138
|
+
const boundary = {
|
|
139
|
+
kind: "mcp",
|
|
140
|
+
server: opts.server,
|
|
141
|
+
tool,
|
|
142
|
+
direction: "out"
|
|
143
|
+
};
|
|
144
|
+
try {
|
|
145
|
+
const { output } = await checkWithOpts(tailrace, asCheckable(args), boundary, opts);
|
|
146
|
+
const checkedArgs = unwrapCheckable(args, output);
|
|
147
|
+
const nextParams = withRewrittenArguments(message.params, checkedArgs);
|
|
148
|
+
const nextMessage = {
|
|
149
|
+
...message,
|
|
150
|
+
params: nextParams
|
|
151
|
+
};
|
|
152
|
+
pending.set(pendingKey(message.id), { method: "tools/call", tool });
|
|
153
|
+
return originalSend(nextMessage, options);
|
|
154
|
+
} catch (err) {
|
|
155
|
+
if (err instanceof PolicyViolationError) {
|
|
156
|
+
deliver(policyViolationRpcError(message.id, err));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
throw err;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (message.method === "resources/read") {
|
|
163
|
+
pending.set(pendingKey(message.id), { method: "resources/read", tool: "read" });
|
|
164
|
+
return originalSend(message, options);
|
|
165
|
+
}
|
|
166
|
+
return originalSend(message, options);
|
|
167
|
+
};
|
|
168
|
+
Object.defineProperty(transport, "onmessage", {
|
|
169
|
+
configurable: true,
|
|
170
|
+
enumerable: true,
|
|
171
|
+
get: () => wrappedOnMessage,
|
|
172
|
+
set: (cb) => {
|
|
173
|
+
userOnMessage = cb;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
return transport;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/fluent.ts
|
|
180
|
+
function withMcp(tailrace) {
|
|
181
|
+
return Object.assign(tailrace, {
|
|
182
|
+
transport: (transport, opts) => wrapTransport(tailrace, transport, opts)
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
export {
|
|
186
|
+
withMcp,
|
|
187
|
+
wrapTransport
|
|
188
|
+
};
|
|
189
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/wrap-transport.ts","../src/internal/context.ts","../src/internal/jsonrpc.ts","../src/internal/messages.ts","../src/fluent.ts"],"sourcesContent":["/**\n * wrapTransport — MCP client transport proxy (docs/integrations.md §2).\n */\n\nimport type { Tailrace } from \"@tailrace/core\";\nimport { PolicyViolationError } from \"@tailrace/core\";\nimport type {\n Transport,\n TransportSendOptions,\n} from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport type {\n JSONRPCMessage,\n MessageExtraInfo,\n RequestId,\n} from \"@modelcontextprotocol/sdk/types.js\";\n\nimport { checkWithOpts } from \"./internal/context\";\nimport { isJsonRpcRequest, isJsonRpcResponse, policyViolationRpcError } from \"./internal/jsonrpc\";\nimport {\n asCheckable,\n getToolsCallArguments,\n getToolsCallName,\n unwrapCheckable,\n withRewrittenArguments,\n} from \"./internal/messages\";\nimport type { McpWrapOptions } from \"./types\";\n\ninterface PendingCall {\n method: \"tools/call\" | \"resources/read\";\n tool: string;\n}\n\n/**\n * Wrap an MCP client transport so its calls pass through policy. Returns the same transport type.\n *\n * @example\n * ```ts\n * const transport = wrapTransport(tailrace, sseTransport, { server: \"salesforce\" });\n * ```\n */\nexport function wrapTransport<T extends Transport>(\n tailrace: Tailrace,\n transport: T,\n opts: McpWrapOptions,\n): T {\n const pending = new Map<string, PendingCall>();\n const originalSend = transport.send.bind(transport);\n\n let userOnMessage: Transport[\"onmessage\"] = transport.onmessage;\n\n const deliver = (message: JSONRPCMessage, extra?: MessageExtraInfo): void => {\n userOnMessage?.(message, extra);\n };\n\n const pendingKey = (id: RequestId): string => String(id);\n\n const wrappedOnMessage = (message: JSONRPCMessage, extra?: MessageExtraInfo): void => {\n void processInbound(message, extra);\n };\n\n async function processInbound(message: JSONRPCMessage, extra?: MessageExtraInfo): Promise<void> {\n if (!isJsonRpcResponse(message) || !(\"result\" in message)) {\n deliver(message, extra);\n return;\n }\n\n const key = pendingKey(message.id);\n const info = pending.get(key);\n if (info === undefined) {\n deliver(message, extra);\n return;\n }\n pending.delete(key);\n\n const boundary = {\n kind: \"mcp\" as const,\n server: opts.server,\n tool: info.tool,\n direction: \"in\" as const,\n };\n\n try {\n const { output } = await checkWithOpts(tailrace, asCheckable(message.result), boundary, opts);\n const rewritten = {\n ...message,\n result: unwrapCheckable(message.result, output),\n } as JSONRPCMessage;\n deliver(rewritten, extra);\n } catch (err) {\n if (err instanceof PolicyViolationError) {\n deliver(policyViolationRpcError(message.id, err), extra);\n return;\n }\n throw err;\n }\n }\n\n transport.send = async (\n message: JSONRPCMessage,\n options?: TransportSendOptions,\n ): Promise<void> => {\n if (!isJsonRpcRequest(message)) {\n return originalSend(message, options);\n }\n\n if (message.method === \"tools/call\") {\n const tool = getToolsCallName(message.params) ?? \"unknown\";\n const args = getToolsCallArguments(message.params);\n const boundary = {\n kind: \"mcp\" as const,\n server: opts.server,\n tool,\n direction: \"out\" as const,\n };\n\n try {\n const { output } = await checkWithOpts(tailrace, asCheckable(args), boundary, opts);\n const checkedArgs = unwrapCheckable(args, output);\n const nextParams = withRewrittenArguments(message.params, checkedArgs);\n const nextMessage = {\n ...message,\n params: nextParams,\n } as JSONRPCMessage;\n pending.set(pendingKey(message.id), { method: \"tools/call\", tool });\n return originalSend(nextMessage, options);\n } catch (err) {\n if (err instanceof PolicyViolationError) {\n deliver(policyViolationRpcError(message.id, err));\n return;\n }\n throw err;\n }\n }\n\n if (message.method === \"resources/read\") {\n // Outbound resources/read params are not scanned in v0.1; track for inbound result scan.\n pending.set(pendingKey(message.id), { method: \"resources/read\", tool: \"read\" });\n return originalSend(message, options);\n }\n\n return originalSend(message, options);\n };\n\n Object.defineProperty(transport, \"onmessage\", {\n configurable: true,\n enumerable: true,\n get: () => wrappedOnMessage,\n set: (cb: Transport[\"onmessage\"]) => {\n userOnMessage = cb;\n },\n });\n\n return transport;\n}\n","/**\n * Build CheckContext from wrap options + mcp boundary.\n */\n\nimport type {\n Boundary,\n CheckContext,\n CheckOptions,\n CheckResult,\n JsonObject,\n Tailrace,\n} from \"@tailrace/core\";\n\nimport type { McpWrapOptions } from \"../types\";\n\nexport function resolveWorkflowId(opts: McpWrapOptions): string {\n return opts.workflowId ?? \"default\";\n}\n\nexport function buildCheckContext(boundary: Boundary, opts: McpWrapOptions): CheckContext {\n return {\n boundary,\n identity: { agent: opts.agent ?? \"default\" },\n workflowId: resolveWorkflowId(opts),\n };\n}\n\n/**\n * Run check and forward decisions to the optional wrap-level onDecision callback.\n */\nexport async function checkWithOpts<T extends string | JsonObject>(\n tailrace: Tailrace,\n input: T,\n boundary: Boundary,\n opts: McpWrapOptions,\n checkOptions?: CheckOptions,\n): Promise<CheckResult<T>> {\n const ctx = buildCheckContext(boundary, opts);\n const result = await tailrace.check(input, ctx, checkOptions);\n if (opts.onDecision !== undefined && result.decisions.length > 0) {\n opts.onDecision(result.decisions);\n }\n return result;\n}\n","/**\n * JSON-RPC helpers for MCP policy error synthesis (docs/integrations.md §2).\n */\n\nimport type { PolicyViolationError } from \"@tailrace/core\";\nimport type { JSONRPCError, JSONRPCMessage, RequestId } from \"@modelcontextprotocol/sdk/types.js\";\n\n/** JSON-RPC application error for Tailrace policy blocks. */\nexport const POLICY_VIOLATION_RPC_CODE = -32001;\n\nexport function isJsonRpcRequest(\n message: JSONRPCMessage,\n): message is JSONRPCMessage & { method: string; id: RequestId; params?: unknown } {\n return (\n typeof message === \"object\" &&\n message !== null &&\n \"method\" in message &&\n \"id\" in message &&\n !(\"result\" in message) &&\n !(\"error\" in message)\n );\n}\n\nexport function isJsonRpcResponse(\n message: JSONRPCMessage,\n): message is JSONRPCMessage & { id: RequestId } & ({ result: unknown } | { error: unknown }) {\n return (\n typeof message === \"object\" &&\n message !== null &&\n \"id\" in message &&\n (\"result\" in message || \"error\" in message) &&\n !(\"method\" in message)\n );\n}\n\n/**\n * Build a JSON-RPC error response from a PolicyViolationError.\n * Never includes the raw detected value.\n */\nexport function policyViolationRpcError(id: RequestId, err: PolicyViolationError): JSONRPCError {\n const first = err.decisions[0];\n const entity = first?.entity ?? \"unknown\";\n const rule = first?.rule ?? \"unknown\";\n return {\n jsonrpc: \"2.0\",\n id,\n error: {\n code: POLICY_VIOLATION_RPC_CODE,\n message: `Blocked by data policy: ${entity} may not be sent to mcp (rule: ${rule})`,\n data: {\n type: \"policy_violation\",\n entity,\n rule,\n },\n },\n };\n}\n","/**\n * Extract / rewrite tools/call arguments and MCP result payloads for check().\n */\n\nimport type { JsonObject, JsonValue } from \"@tailrace/core\";\n\n/** Wrap opaque values so object-scan can walk string leaves. */\nexport function asCheckable(value: unknown): string | JsonObject {\n if (typeof value === \"string\") return value;\n if (value === null || value === undefined) return {};\n if (typeof value === \"object\" && !Array.isArray(value)) {\n return value as JsonObject;\n }\n return { value: value as JsonValue };\n}\n\nexport function unwrapCheckable(original: unknown, checked: string | JsonObject): unknown {\n if (typeof original === \"string\") return checked;\n if (original === null || original === undefined) return original;\n if (typeof original === \"object\" && !Array.isArray(original)) return checked;\n if (\n typeof checked === \"object\" &&\n checked !== null &&\n \"value\" in checked &&\n (Array.isArray(original) || typeof original === \"number\" || typeof original === \"boolean\")\n ) {\n return (checked as JsonObject)[\"value\"];\n }\n return checked;\n}\n\nexport function getToolsCallName(params: unknown): string | undefined {\n if (typeof params !== \"object\" || params === null) return undefined;\n const name = (params as { name?: unknown }).name;\n return typeof name === \"string\" ? name : undefined;\n}\n\nexport function getToolsCallArguments(params: unknown): unknown {\n if (typeof params !== \"object\" || params === null) return {};\n const args = (params as { arguments?: unknown }).arguments;\n return args === undefined ? {} : args;\n}\n\n/**\n * Rewrite `params.arguments` after a successful check. Returns a shallow-cloned params object.\n */\nexport function withRewrittenArguments(params: unknown, checkedArgs: unknown): unknown {\n if (typeof params !== \"object\" || params === null) {\n return { arguments: checkedArgs };\n }\n return { ...(params as Record<string, unknown>), arguments: checkedArgs };\n}\n","/**\n * Fluent MCP helpers without core importing the MCP SDK (docs/integrations.md §2).\n */\n\nimport type { Tailrace } from \"@tailrace/core\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\n\nimport type { McpWrapOptions, TailraceWithMcp } from \"./types\";\nimport { wrapTransport } from \"./wrap-transport\";\n\n/**\n * Attach a `transport` helper to an existing Tailrace instance.\n *\n * @example\n * ```ts\n * const tailrace = withMcp(createTailrace());\n * const transport = tailrace.transport(sseTransport, { server: \"salesforce\" });\n * ```\n */\nexport function withMcp(tailrace: Tailrace): TailraceWithMcp {\n return Object.assign(tailrace, {\n transport: <T extends Transport>(transport: T, opts: McpWrapOptions) =>\n wrapTransport(tailrace, transport, opts),\n });\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tailrace/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Tailrace wrapper for MCP client transports: policy at the tools/call and resources/read boundary.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"main": "./dist/index.cjs",
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"require": {
|
|
18
|
+
"types": "./dist/index.d.cts",
|
|
19
|
+
"default": "./dist/index.cjs"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@tailrace/core": "0.1.0"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"@modelcontextprotocol/sdk": ">=1"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
38
|
+
"expect-type": "^1.4.0",
|
|
39
|
+
"vitest": "^2.1.9"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsup",
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.testd.json"
|
|
45
|
+
}
|
|
46
|
+
}
|