frisk-mcp 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Frisk contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # frisk-mcp
2
+
3
+ An [MCP](https://modelcontextprotocol.io) server for [Frisk](https://github.com/Jiangw2718i/frisk):
4
+ screen the counterparty of an x402 payment before an agent pays it.
5
+
6
+ It exposes one tool, `screen_payment`, which runs Frisk's deterministic checks —
7
+ address sanity, dynamic-`payTo` comparison, transport safety, and your own
8
+ spending policy — and returns `allow` / `review` / `block` with reasons.
9
+
10
+ ## Where this belongs, and where it does not
11
+
12
+ An MCP tool runs when a model decides to call it. A payment check that the model
13
+ can skip is a weaker guarantee than the same check on the code path that signs
14
+ the payment.
15
+
16
+ So:
17
+
18
+ - **Use the library** — [`frisk-screen`](https://www.npmjs.com/package/frisk-screen) —
19
+ where the money actually moves. It is MIT, dependency-free, and runs locally.
20
+ - **Use this server** for inspection ("is this address safe to pay?"), and for
21
+ agents that cannot import a library.
22
+
23
+ Frisk is advisory in both cases: it returns a verdict and your code decides. It
24
+ never holds funds and cannot stop a payment by itself.
25
+
26
+ ## Install
27
+
28
+ ```bash
29
+ npx frisk-mcp
30
+ ```
31
+
32
+ In any MCP client's server configuration:
33
+
34
+ ```json
35
+ {
36
+ "mcpServers": {
37
+ "frisk": {
38
+ "command": "npx",
39
+ "args": ["-y", "frisk-mcp"]
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ No API key, no account, no network call: with no configuration the server
46
+ screens entirely on your machine.
47
+
48
+ ## Hosted mode (optional)
49
+
50
+ Set `FRISK_API_KEY` to have the same tool answered by the hosted service, which
51
+ adds reputation-graph signals and returns higher-confidence verdicts:
52
+
53
+ ```json
54
+ {
55
+ "mcpServers": {
56
+ "frisk": {
57
+ "command": "npx",
58
+ "args": ["-y", "frisk-mcp"],
59
+ "env": { "FRISK_API_KEY": "..." }
60
+ }
61
+ }
62
+ }
63
+ ```
64
+
65
+ The hosted tier is in early access — email support@tryfrisk.dev. Everything
66
+ above works without it.
67
+
68
+ | variable | default | meaning |
69
+ | --- | --- | --- |
70
+ | `FRISK_API_KEY` | unset | Enables hosted screening. Unset means fully offline. |
71
+ | `FRISK_BASE_URL` | `https://api.tryfrisk.dev` | Override the hosted endpoint. |
72
+
73
+ ## The tool
74
+
75
+ `screen_payment`
76
+
77
+ | argument | type | notes |
78
+ | --- | --- | --- |
79
+ | `counterparty` | string, required | The address the agent intends to pay. |
80
+ | `endpoint` | string | URL that quoted the payment; checked for plaintext transport. |
81
+ | `amount` | number | Amount about to be paid. |
82
+ | `asset` | string | Asset symbol, e.g. `USDC`. |
83
+ | `observedPayTo` | string | The `payTo` the endpoint actually returned. If it differs from `counterparty`, that is the dynamic-`payTo` swap. |
84
+ | `strictness` | number 0–1 | 0 permissive, 1 paranoid. Default 0.3. |
85
+ | `maxPerCall` | number | Spending ceiling for one call. |
86
+ | `allowedAssets` | string[] | Assets the agent may pay in. |
87
+
88
+ Returns `verdict`, `trustScore`, `confidence`, `reasons`, `policyHits`,
89
+ `source` (`lite` or `hosted`), and `allowed`.
90
+
91
+ ## What it does not do
92
+
93
+ It does not detect Sybil clusters, and offline it has no view of an address's
94
+ history — lite screening always reports `"low"` confidence for that reason. It
95
+ also cannot repair the protocol underneath a payment: replay, settlement races,
96
+ and facilitator trust live in x402 itself, not in the request being signed.
97
+ Those limits are discussed in
98
+ [Five ways an x402 payment can go wrong](https://tryfrisk.dev/blog/screening-x402-payments).
99
+
100
+ ## License
101
+
102
+ MIT
package/dist/index.js ADDED
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
5
+
6
+ // src/server.ts
7
+ import { McpServer } from "@modelcontextprotocol/server";
8
+ import { Client } from "frisk-screen";
9
+ import { z } from "zod";
10
+ var inputSchema = z.object({
11
+ counterparty: z.string().describe("The address the agent intends to pay, e.g. an 0x EVM address."),
12
+ endpoint: z.string().optional().describe("URL that quoted the payment. Used to check transport safety."),
13
+ amount: z.number().optional().describe("Amount the agent is about to pay."),
14
+ asset: z.string().optional().describe("Asset symbol, e.g. USDC."),
15
+ observedPayTo: z.string().optional().describe(
16
+ "The payTo address the endpoint actually returned for this request. If it differs from the counterparty, that is the dynamic-payTo swap."
17
+ ),
18
+ strictness: z.number().min(0).max(1).optional().describe("0 is permissive, 1 is paranoid. Defaults to 0.3."),
19
+ maxPerCall: z.number().optional().describe("Spending ceiling for a single call."),
20
+ allowedAssets: z.array(z.string()).optional().describe("Assets the agent is permitted to pay in.")
21
+ });
22
+ function describeResult(result) {
23
+ const lines = [
24
+ `verdict: ${result.verdict}`,
25
+ `trust score: ${result.trustScore}`,
26
+ `confidence: ${result.confidence}`,
27
+ `source: ${result.source}`
28
+ ];
29
+ if (result.reasons.length > 0) {
30
+ lines.push(`reasons: ${result.reasons.join("; ")}`);
31
+ }
32
+ if (result.policyHits.length > 0) {
33
+ lines.push(`policy hits: ${result.policyHits.join("; ")}`);
34
+ }
35
+ return lines.join("\n");
36
+ }
37
+ function createServer(options = {}) {
38
+ const client = new Client({
39
+ apiKey: options.apiKey,
40
+ baseUrl: options.baseUrl
41
+ });
42
+ const server = new McpServer({
43
+ name: "frisk",
44
+ version: "0.0.1"
45
+ });
46
+ server.registerTool(
47
+ "screen_payment",
48
+ {
49
+ title: "Screen a payment before paying",
50
+ description: client.mode === "hosted" ? "Screen a counterparty an agent is about to pay. Runs the local deterministic checks and the hosted reputation graph, and returns allow / review / block with reasons. Advisory: the caller decides." : "Screen a counterparty an agent is about to pay. Runs entirely offline: address sanity, dynamic-payTo comparison, transport check, and your spending policy. Returns allow / review / block with reasons. Advisory: the caller decides. Set FRISK_API_KEY to add hosted reputation signals.",
51
+ inputSchema,
52
+ annotations: {
53
+ readOnlyHint: true,
54
+ openWorldHint: client.mode === "hosted"
55
+ }
56
+ },
57
+ async ({ counterparty, maxPerCall, allowedAssets, ...rest }) => {
58
+ const policy = maxPerCall === void 0 && allowedAssets === void 0 ? void 0 : { maxPerCall, allowedAssets };
59
+ try {
60
+ const result = await client.screen(counterparty, { ...rest, policy });
61
+ return {
62
+ content: [{ type: "text", text: describeResult(result) }],
63
+ structuredContent: { ...result }
64
+ };
65
+ } catch (error) {
66
+ const message = error instanceof Error ? error.message : String(error);
67
+ return {
68
+ isError: true,
69
+ content: [
70
+ {
71
+ type: "text",
72
+ text: `screening failed: ${message}`
73
+ }
74
+ ]
75
+ };
76
+ }
77
+ }
78
+ );
79
+ return server;
80
+ }
81
+
82
+ // src/index.ts
83
+ async function main() {
84
+ const server = createServer({
85
+ apiKey: process.env.FRISK_API_KEY,
86
+ baseUrl: process.env.FRISK_BASE_URL
87
+ });
88
+ await server.connect(new StdioServerTransport());
89
+ }
90
+ main().catch((error) => {
91
+ console.error(error);
92
+ process.exit(1);
93
+ });
94
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/server.ts"],"sourcesContent":["import { StdioServerTransport } from \"@modelcontextprotocol/server/stdio\";\nimport { createServer } from \"./server.js\";\n\nasync function main(): Promise<void> {\n const server = createServer({\n apiKey: process.env.FRISK_API_KEY,\n baseUrl: process.env.FRISK_BASE_URL,\n });\n await server.connect(new StdioServerTransport());\n}\n\nmain().catch((error) => {\n // stdout carries the protocol, so diagnostics go to stderr.\n console.error(error);\n process.exit(1);\n});\n","import { McpServer } from \"@modelcontextprotocol/server\";\nimport { Client, type ScreenResult } from \"frisk-screen\";\nimport { z } from \"zod\";\n\nexport interface ServerOptions {\n /** Hosted API key. Without one the server screens entirely offline. */\n apiKey?: string;\n /** Override the hosted base URL. Only used when an API key is supplied. */\n baseUrl?: string;\n}\n\nconst inputSchema = z.object({\n counterparty: z\n .string()\n .describe(\"The address the agent intends to pay, e.g. an 0x EVM address.\"),\n endpoint: z\n .string()\n .optional()\n .describe(\"URL that quoted the payment. Used to check transport safety.\"),\n amount: z.number().optional().describe(\"Amount the agent is about to pay.\"),\n asset: z.string().optional().describe(\"Asset symbol, e.g. USDC.\"),\n observedPayTo: z\n .string()\n .optional()\n .describe(\n \"The payTo address the endpoint actually returned for this request. If it differs from the counterparty, that is the dynamic-payTo swap.\",\n ),\n strictness: z\n .number()\n .min(0)\n .max(1)\n .optional()\n .describe(\"0 is permissive, 1 is paranoid. Defaults to 0.3.\"),\n maxPerCall: z\n .number()\n .optional()\n .describe(\"Spending ceiling for a single call.\"),\n allowedAssets: z\n .array(z.string())\n .optional()\n .describe(\"Assets the agent is permitted to pay in.\"),\n});\n\n/**\n * The verdict is advisory. Frisk returns a recommendation and the caller\n * decides; it never holds funds and cannot stop a payment by itself.\n */\nfunction describeResult(result: ScreenResult): string {\n const lines = [\n `verdict: ${result.verdict}`,\n `trust score: ${result.trustScore}`,\n `confidence: ${result.confidence}`,\n `source: ${result.source}`,\n ];\n if (result.reasons.length > 0) {\n lines.push(`reasons: ${result.reasons.join(\"; \")}`);\n }\n if (result.policyHits.length > 0) {\n lines.push(`policy hits: ${result.policyHits.join(\"; \")}`);\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * Build the Frisk MCP server.\n *\n * One tool, deliberately. `screen_payment` is the whole deterministic floor:\n * counterparty sanity, dynamic-payTo comparison, transport check, and the\n * caller's own spending policy. With an API key the same call is answered by\n * the hosted service, which adds reputation signals.\n *\n * Note on where this belongs: an MCP tool runs when a model decides to call it,\n * so it is a weaker guarantee than calling `screen()` on the code path that\n * actually signs the payment. Use this for inspection and for agents that\n * cannot import the library; use the library where the money moves.\n */\nexport function createServer(options: ServerOptions = {}): McpServer {\n const client = new Client({\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n });\n\n const server = new McpServer({\n name: \"frisk\",\n version: \"0.0.1\",\n });\n\n server.registerTool(\n \"screen_payment\",\n {\n title: \"Screen a payment before paying\",\n description:\n client.mode === \"hosted\"\n ? \"Screen a counterparty an agent is about to pay. Runs the local deterministic checks and the hosted reputation graph, and returns allow / review / block with reasons. Advisory: the caller decides.\"\n : \"Screen a counterparty an agent is about to pay. Runs entirely offline: address sanity, dynamic-payTo comparison, transport check, and your spending policy. Returns allow / review / block with reasons. Advisory: the caller decides. Set FRISK_API_KEY to add hosted reputation signals.\",\n inputSchema,\n annotations: {\n readOnlyHint: true,\n openWorldHint: client.mode === \"hosted\",\n },\n },\n async ({ counterparty, maxPerCall, allowedAssets, ...rest }) => {\n const policy =\n maxPerCall === undefined && allowedAssets === undefined\n ? undefined\n : { maxPerCall, allowedAssets };\n try {\n const result = await client.screen(counterparty, { ...rest, policy });\n return {\n content: [{ type: \"text\" as const, text: describeResult(result) }],\n structuredContent: { ...result },\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n isError: true,\n content: [\n {\n type: \"text\" as const,\n text: `screening failed: ${message}`,\n },\n ],\n };\n }\n },\n );\n\n return server;\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "frisk-mcp",
3
+ "version": "0.0.1",
4
+ "description": "MCP server for Frisk: screen an x402 payment counterparty before an agent pays.",
5
+ "mcpName": "dev.tryfrisk/frisk",
6
+ "type": "module",
7
+ "bin": {
8
+ "frisk-mcp": "./dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsup",
15
+ "test": "vitest run",
16
+ "typecheck": "tsc --noEmit",
17
+ "lint": "biome check .",
18
+ "format": "biome check --write .",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "keywords": [
22
+ "mcp",
23
+ "model-context-protocol",
24
+ "ai-agents",
25
+ "x402",
26
+ "agent-payments",
27
+ "risk"
28
+ ],
29
+ "license": "MIT",
30
+ "author": "Frisk contributors",
31
+ "homepage": "https://github.com/Jiangw2718i/frisk#readme",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/Jiangw2718i/frisk.git",
35
+ "directory": "mcp"
36
+ },
37
+ "bugs": {
38
+ "url": "https://github.com/Jiangw2718i/frisk/issues"
39
+ },
40
+ "engines": {
41
+ "node": ">=18"
42
+ },
43
+ "dependencies": {
44
+ "@modelcontextprotocol/server": "^2.0.0",
45
+ "frisk-screen": "^0.0.2",
46
+ "zod": "^4.2.0"
47
+ },
48
+ "devDependencies": {
49
+ "@biomejs/biome": "^2.4.16",
50
+ "@modelcontextprotocol/client": "^2.0.0",
51
+ "@types/node": "^25.9.3",
52
+ "tsup": "^8.5.1",
53
+ "typescript": "^6.0.3",
54
+ "vitest": "^4.1.8"
55
+ }
56
+ }