@briefgate/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 +329 -0
- package/dist/client.d.ts +128 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +138 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +188 -0
- package/dist/index.js.map +1 -0
- package/dist/tools.d.ts +616 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +591 -0
- package/dist/tools.js.map +1 -0
- package/dist/webhook.d.ts +54 -0
- package/dist/webhook.d.ts.map +1 -0
- package/dist/webhook.js +94 -0
- package/dist/webhook.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export type BriefGateWebhookEventType = 'item.submitted' | 'intake.completed' | 'client.viewed' | 'chase.bounced' | 'intake.stalled';
|
|
2
|
+
interface BaseEvent {
|
|
3
|
+
event: BriefGateWebhookEventType;
|
|
4
|
+
intake_id: string;
|
|
5
|
+
account_id: string;
|
|
6
|
+
created_at: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ItemSubmittedEvent extends BaseEvent {
|
|
9
|
+
event: 'item.submitted';
|
|
10
|
+
item_key: string;
|
|
11
|
+
item_status: 'submitted';
|
|
12
|
+
}
|
|
13
|
+
export interface IntakeCompletedEvent extends BaseEvent {
|
|
14
|
+
event: 'intake.completed';
|
|
15
|
+
}
|
|
16
|
+
export interface ClientViewedEvent extends BaseEvent {
|
|
17
|
+
event: 'client.viewed';
|
|
18
|
+
client_email: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ChaseBounced extends BaseEvent {
|
|
21
|
+
event: 'chase.bounced';
|
|
22
|
+
channel: 'email' | 'sms';
|
|
23
|
+
reason?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface IntakeStalledEvent extends BaseEvent {
|
|
26
|
+
event: 'intake.stalled';
|
|
27
|
+
attempts: number;
|
|
28
|
+
}
|
|
29
|
+
export type BriefGateWebhookEvent = ItemSubmittedEvent | IntakeCompletedEvent | ClientViewedEvent | ChaseBounced | IntakeStalledEvent;
|
|
30
|
+
/**
|
|
31
|
+
* Verify a BriefGate webhook delivery.
|
|
32
|
+
*
|
|
33
|
+
* BriefGate signs each webhook body with HMAC-SHA256 over `${t}.${rawBody}`
|
|
34
|
+
* and sends the result in `X-BriefGate-Signature` as `t=<unix>,v1=<hex>`.
|
|
35
|
+
*
|
|
36
|
+
* This function performs a timing-safe comparison and rejects requests whose
|
|
37
|
+
* timestamp falls outside the tolerance window (replay protection).
|
|
38
|
+
*
|
|
39
|
+
* @param secret Webhook secret configured in the BriefGate dashboard.
|
|
40
|
+
* @param header Value of the `X-BriefGate-Signature` header.
|
|
41
|
+
* @param rawBody Raw request body string — do not JSON-parse before passing.
|
|
42
|
+
* @param opts Optional: toleranceSec (default 300).
|
|
43
|
+
* @returns true if the signature is valid and the timestamp is fresh.
|
|
44
|
+
*/
|
|
45
|
+
export declare function verifyWebhookSignature(secret: string, header: string, rawBody: string, opts?: {
|
|
46
|
+
toleranceSec?: number;
|
|
47
|
+
}): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Parse and type a webhook event from a raw body string.
|
|
50
|
+
* Throws if the body is not valid JSON or the event field is missing.
|
|
51
|
+
*/
|
|
52
|
+
export declare function parseWebhookEvent(rawBody: string): BriefGateWebhookEvent;
|
|
53
|
+
export {};
|
|
54
|
+
//# sourceMappingURL=webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../src/webhook.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,yBAAyB,GACjC,gBAAgB,GAChB,kBAAkB,GAClB,eAAe,GACf,eAAe,GACf,gBAAgB,CAAC;AAErB,UAAU,SAAS;IACjB,KAAK,EAAE,yBAAyB,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAmB,SAAQ,SAAS;IACnD,KAAK,EAAE,gBAAgB,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED,MAAM,WAAW,oBAAqB,SAAQ,SAAS;IACrD,KAAK,EAAE,kBAAkB,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAkB,SAAQ,SAAS;IAClD,KAAK,EAAE,eAAe,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,YAAa,SAAQ,SAAS;IAC7C,KAAK,EAAE,eAAe,CAAC;IACvB,OAAO,EAAE,OAAO,GAAG,KAAK,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAmB,SAAQ,SAAS;IACnD,KAAK,EAAE,gBAAgB,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,qBAAqB,GAC7B,kBAAkB,GAClB,oBAAoB,GACpB,iBAAiB,GACjB,YAAY,GACZ,kBAAkB,CAAC;AAIvB;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/B,OAAO,CA2BT;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,qBAAqB,CAmBxE"}
|
package/dist/webhook.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Webhook signature verification and event parsing.
|
|
2
|
+
//
|
|
3
|
+
// Export path: @briefgate/mcp/webhook
|
|
4
|
+
// This module is kept separate so consumers can import it without pulling in
|
|
5
|
+
// the full MCP server and its SDK dependencies.
|
|
6
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
7
|
+
// ─── Signature verification ───────────────────────────────────────────────────
|
|
8
|
+
/**
|
|
9
|
+
* Verify a BriefGate webhook delivery.
|
|
10
|
+
*
|
|
11
|
+
* BriefGate signs each webhook body with HMAC-SHA256 over `${t}.${rawBody}`
|
|
12
|
+
* and sends the result in `X-BriefGate-Signature` as `t=<unix>,v1=<hex>`.
|
|
13
|
+
*
|
|
14
|
+
* This function performs a timing-safe comparison and rejects requests whose
|
|
15
|
+
* timestamp falls outside the tolerance window (replay protection).
|
|
16
|
+
*
|
|
17
|
+
* @param secret Webhook secret configured in the BriefGate dashboard.
|
|
18
|
+
* @param header Value of the `X-BriefGate-Signature` header.
|
|
19
|
+
* @param rawBody Raw request body string — do not JSON-parse before passing.
|
|
20
|
+
* @param opts Optional: toleranceSec (default 300).
|
|
21
|
+
* @returns true if the signature is valid and the timestamp is fresh.
|
|
22
|
+
*/
|
|
23
|
+
export function verifyWebhookSignature(secret, header, rawBody, opts) {
|
|
24
|
+
const toleranceSec = opts?.toleranceSec ?? 300;
|
|
25
|
+
const parsed = parseSignatureHeader(header);
|
|
26
|
+
if (!parsed)
|
|
27
|
+
return false;
|
|
28
|
+
const { t, v1 } = parsed;
|
|
29
|
+
// Reject requests outside the replay-protection window.
|
|
30
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
31
|
+
if (Math.abs(nowSec - t) > toleranceSec)
|
|
32
|
+
return false;
|
|
33
|
+
// Recompute HMAC over the same payload the server signed.
|
|
34
|
+
const payload = `${t}.${rawBody}`;
|
|
35
|
+
const actualHex = createHmac('sha256', secret).update(payload).digest('hex');
|
|
36
|
+
// Timing-safe comparison prevents side-channel attacks that infer the
|
|
37
|
+
// correct signature from response time differences.
|
|
38
|
+
try {
|
|
39
|
+
const expected = Buffer.from(v1, 'hex');
|
|
40
|
+
const actual = Buffer.from(actualHex, 'hex');
|
|
41
|
+
// Lengths must match before timingSafeEqual; a length mismatch would
|
|
42
|
+
// itself leak information, so we compare lengths first then reject.
|
|
43
|
+
if (expected.length !== actual.length)
|
|
44
|
+
return false;
|
|
45
|
+
return timingSafeEqual(expected, actual);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Parse and type a webhook event from a raw body string.
|
|
53
|
+
* Throws if the body is not valid JSON or the event field is missing.
|
|
54
|
+
*/
|
|
55
|
+
export function parseWebhookEvent(rawBody) {
|
|
56
|
+
let parsed;
|
|
57
|
+
try {
|
|
58
|
+
parsed = JSON.parse(rawBody);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
throw new Error('Webhook body is not valid JSON.');
|
|
62
|
+
}
|
|
63
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
64
|
+
throw new Error('Webhook body must be a JSON object.');
|
|
65
|
+
}
|
|
66
|
+
const obj = parsed;
|
|
67
|
+
if (typeof obj['event'] !== 'string' || typeof obj['intake_id'] !== 'string') {
|
|
68
|
+
throw new Error('Webhook event is missing required fields: event, intake_id.');
|
|
69
|
+
}
|
|
70
|
+
return obj;
|
|
71
|
+
}
|
|
72
|
+
// ─── Internal helpers ─────────────────────────────────────────────────────────
|
|
73
|
+
function parseSignatureHeader(header) {
|
|
74
|
+
// Expected format: t=1234567890,v1=abcdef0123...
|
|
75
|
+
const parts = {};
|
|
76
|
+
for (const segment of header.split(',')) {
|
|
77
|
+
const eq = segment.indexOf('=');
|
|
78
|
+
if (eq === -1)
|
|
79
|
+
return null;
|
|
80
|
+
parts[segment.slice(0, eq).trim()] = segment.slice(eq + 1).trim();
|
|
81
|
+
}
|
|
82
|
+
const tStr = parts['t'];
|
|
83
|
+
const v1 = parts['v1'];
|
|
84
|
+
if (!tStr || !v1)
|
|
85
|
+
return null;
|
|
86
|
+
const t = parseInt(tStr, 10);
|
|
87
|
+
if (isNaN(t))
|
|
88
|
+
return null;
|
|
89
|
+
// Reject obviously wrong hex lengths (SHA-256 = 64 hex chars).
|
|
90
|
+
if (v1.length !== 64)
|
|
91
|
+
return null;
|
|
92
|
+
return { t, v1 };
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.js","sourceRoot":"","sources":["../src/webhook.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@briefgate/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"mcpName": "io.github.sekera-radim/briefgate",
|
|
5
|
+
"description": "BriefGate MCP server — your coding agent asks the client for logos, copy and credentials, and BriefGate chases them until they arrive.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"mcp",
|
|
8
|
+
"briefgate",
|
|
9
|
+
"client-intake",
|
|
10
|
+
"onboarding",
|
|
11
|
+
"ai-agent",
|
|
12
|
+
"claude-code",
|
|
13
|
+
"freelance"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"author": "Radim Sekera",
|
|
17
|
+
"homepage": "https://briefgate.dev",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://gitlab.com/sekera.radim/briefgate.git",
|
|
21
|
+
"directory": "mcp"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://gitlab.com/sekera.radim/briefgate/-/issues"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"type": "module",
|
|
30
|
+
"main": "./dist/index.js",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": "./dist/index.js",
|
|
33
|
+
"./webhook": "./dist/webhook.js"
|
|
34
|
+
},
|
|
35
|
+
"bin": {
|
|
36
|
+
"briefgate-mcp": "dist/index.js"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"README.md",
|
|
41
|
+
"LICENSE"
|
|
42
|
+
],
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsc -p tsconfig.build.json",
|
|
48
|
+
"dev": "tsx watch src/index.ts",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"lint": "eslint src test",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"check": "npm run typecheck && npm run lint && npm run test",
|
|
53
|
+
"prepublishOnly": "npm run build"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
57
|
+
"zod": "^3.25.76"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@eslint/js": "^9.39.0",
|
|
61
|
+
"@types/node": "^22.15.29",
|
|
62
|
+
"eslint": "^9.39.0",
|
|
63
|
+
"tsx": "^4.23.1",
|
|
64
|
+
"typescript": "^5.9.3",
|
|
65
|
+
"typescript-eslint": "^8.64.0",
|
|
66
|
+
"vitest": "^3.2.7"
|
|
67
|
+
}
|
|
68
|
+
}
|