@rubytech/create-realagent-code 0.1.125 → 0.1.126

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.
Files changed (76) hide show
  1. package/dist/index.js +11 -1
  2. package/package.json +2 -2
  3. package/payload/platform/lib/graph-style/dist/index.d.ts +78 -0
  4. package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -0
  5. package/payload/platform/lib/graph-style/dist/index.js +296 -0
  6. package/payload/platform/lib/graph-style/dist/index.js.map +1 -0
  7. package/payload/platform/lib/graph-style/src/__tests__/parity.test.ts +114 -0
  8. package/payload/platform/lib/graph-style/src/index.ts +307 -0
  9. package/payload/platform/lib/graph-style/tsconfig.json +9 -0
  10. package/payload/platform/lib/graph-style/vitest.config.ts +9 -0
  11. package/payload/platform/package-lock.json +318 -0
  12. package/payload/platform/package.json +3 -2
  13. package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
  14. package/payload/platform/plugins/docs/references/visitor-graph.md +4 -3
  15. package/payload/platform/plugins/graph-viewer/.claude-plugin/plugin.json +8 -0
  16. package/payload/platform/plugins/graph-viewer/PLUGIN.md +56 -0
  17. package/payload/platform/plugins/graph-viewer/mcp/dist/index.d.ts +7 -0
  18. package/payload/platform/plugins/graph-viewer/mcp/dist/index.d.ts.map +1 -0
  19. package/payload/platform/plugins/graph-viewer/mcp/dist/index.js +74 -0
  20. package/payload/platform/plugins/graph-viewer/mcp/dist/index.js.map +1 -0
  21. package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.d.ts +5 -0
  22. package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.d.ts.map +1 -0
  23. package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.js +43 -0
  24. package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.js.map +1 -0
  25. package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.d.ts +28 -0
  26. package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.d.ts.map +1 -0
  27. package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.js +73 -0
  28. package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.js.map +1 -0
  29. package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.d.ts +40 -0
  30. package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.d.ts.map +1 -0
  31. package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.js +77 -0
  32. package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.js.map +1 -0
  33. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.d.ts +45 -0
  34. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.d.ts.map +1 -0
  35. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js +170 -0
  36. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js.map +1 -0
  37. package/payload/platform/plugins/graph-viewer/mcp/package.json +25 -0
  38. package/payload/platform/plugins/graph-viewer/mcp/vitest.config.ts +8 -0
  39. package/payload/platform/plugins/graph-viewer/skills/render-graph/SKILL.md +35 -0
  40. package/payload/platform/scripts/check-plugin-tools-mcp-consistency.mjs +136 -0
  41. package/payload/premium-plugins/real-agent/plugins/buyers/PLUGIN.md +6 -2
  42. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/index.js +11 -5
  43. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/index.js.map +1 -1
  44. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/lib/visitor-token.d.ts +9 -0
  45. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/lib/visitor-token.d.ts.map +1 -0
  46. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/lib/visitor-token.js +66 -0
  47. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/lib/visitor-token.js.map +1 -0
  48. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/mint-visitor-token.d.ts +12 -0
  49. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/mint-visitor-token.d.ts.map +1 -0
  50. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/mint-visitor-token.js +21 -0
  51. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/mint-visitor-token.js.map +1 -0
  52. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-backfill-from-logs.d.ts +0 -5
  53. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-backfill-from-logs.d.ts.map +1 -1
  54. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-backfill-from-logs.js +6 -27
  55. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-backfill-from-logs.js.map +1 -1
  56. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/index.ts +17 -5
  57. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/lib/visitor-token.ts +79 -0
  58. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/tools/mint-visitor-token.ts +35 -0
  59. package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/tools/visitor-backfill-from-logs.ts +6 -33
  60. package/payload/premium-plugins/real-agent/plugins/buyers/skills/property-recommender/SKILL.md +1 -10
  61. package/payload/premium-plugins/real-agent/plugins/leads/skills/lead-nurturing/SKILL.md +1 -1
  62. package/payload/premium-plugins/real-agent/plugins/leads/skills/morning-round/SKILL.md +1 -1
  63. package/payload/premium-plugins/real-agent/plugins/vendors/skills/vendor-updates/SKILL.md +1 -1
  64. package/payload/server/public/assets/{admin-D6IfAzYY.js → admin-FcRHAL-3.js} +7 -7
  65. package/payload/server/public/assets/{data-BGbQyufe.js → data-Ds37mflX.js} +1 -1
  66. package/payload/server/public/assets/{graph-D-1lRTeL.js → graph-CmWRhaiS.js} +1 -1
  67. package/payload/server/public/assets/graph-labels-Ch2r00Gt.js +1 -0
  68. package/payload/server/public/assets/page-BOtNny_4.js +51 -0
  69. package/payload/server/public/assets/page-BcHhJXUt.js +1 -0
  70. package/payload/server/public/data.html +3 -3
  71. package/payload/server/public/graph.html +3 -3
  72. package/payload/server/public/index.html +4 -4
  73. package/payload/server/server.js +236 -92
  74. package/payload/server/public/assets/graph-labels-BRXJHNYE.js +0 -1
  75. package/payload/server/public/assets/page-B4oirCvn.js +0 -1
  76. package/payload/server/public/assets/page-FmJ7PIYx.js +0 -51
@@ -0,0 +1,170 @@
1
+ /**
2
+ * graph-render — produce an inline PNG of an account-scoped Neo4j
3
+ * subgraph rooted at a given nodeId, walking up to `hopDepth` hops.
4
+ *
5
+ * Surface contract:
6
+ * - The root node must exist and belong to the caller's account.
7
+ * A missing or foreign-account root returns a structured refusal,
8
+ * never throws.
9
+ * - The expansion is bounded by both `hopDepth` (max 3 to keep the
10
+ * image legible) and a hard 200-node cap. Subgraphs that exceed
11
+ * the cap render the first 200 nodes and carry a `truncated`
12
+ * envelope so the agent can tell the operator.
13
+ * - `:Trashed` nodes are excluded everywhere — matches memory-search.
14
+ * - One observability line per invocation, prefixed `[graph-render]`.
15
+ *
16
+ * The Cypher hopDepth is INLINED into the query string after a strict
17
+ * validate (`Number.isInteger && 0 <= n <= 3`) because Neo4j does not
18
+ * parameterise variable-length path bounds. Anything outside that range
19
+ * fails fast at the param-validation layer, well before string assembly.
20
+ */
21
+ import { getSession } from "../lib/neo4j.js";
22
+ import { layoutGraph } from "../render/layout.js";
23
+ import { drawGraph } from "../render/draw.js";
24
+ const NODE_CAP = 200;
25
+ const MAX_HOP_DEPTH = 3;
26
+ const IMAGE_WIDTH = 1200;
27
+ const IMAGE_HEIGHT = 900;
28
+ function logLine(line) {
29
+ process.stderr.write(`${line}\n`);
30
+ }
31
+ export async function graphRender(params) {
32
+ const { accountId, nodeId } = params;
33
+ const hopDepth = params.hopDepth;
34
+ if (!Number.isInteger(hopDepth) || hopDepth < 0 || hopDepth > MAX_HOP_DEPTH) {
35
+ return {
36
+ ok: false,
37
+ reason: "invalid-input",
38
+ message: `hopDepth must be an integer between 0 and ${MAX_HOP_DEPTH} (got ${String(hopDepth)})`,
39
+ };
40
+ }
41
+ const t0 = Date.now();
42
+ const session = getSession();
43
+ try {
44
+ // Step 1 — confirm root exists and belongs to this account. The
45
+ // explicit two-step check (rather than relying on the expand to
46
+ // return zero rows) is what lets us return reason="scope" vs
47
+ // reason="empty" with a meaningful message.
48
+ const rootCheck = await session.run(`MATCH (root)
49
+ WHERE elementId(root) = $nodeId
50
+ RETURN root.accountId AS rootAccountId, labels(root) AS rootLabels`, { nodeId });
51
+ if (rootCheck.records.length === 0) {
52
+ logLine(`[graph-render] reject account=${accountId} reason=scope nodeId=${nodeId} cause=not-found`);
53
+ return {
54
+ ok: false,
55
+ reason: "scope",
56
+ message: `No node with id ${nodeId} is visible to this account.`,
57
+ };
58
+ }
59
+ const rootAccountId = rootCheck.records[0].get("rootAccountId");
60
+ if (rootAccountId !== accountId) {
61
+ logLine(`[graph-render] reject account=${accountId} reason=scope nodeId=${nodeId} cause=foreign-account`);
62
+ return {
63
+ ok: false,
64
+ reason: "scope",
65
+ message: `Node ${nodeId} is not visible to this account.`,
66
+ };
67
+ }
68
+ logLine(`[graph-render] start account=${accountId} nodeId=${nodeId} hopDepth=${hopDepth}`);
69
+ // Step 2 — expand. Variable-length path bound is inlined after the
70
+ // hopDepth validation above. Account filter + trash filter applied to
71
+ // every node on every path. RELATIONSHIPS only between matched nodes.
72
+ const expandQuery = `
73
+ MATCH (root) WHERE elementId(root) = $nodeId
74
+ MATCH path = (root)-[*0..${hopDepth}]-(n)
75
+ WHERE n.accountId = $accountId AND NOT n:Trashed
76
+ WITH collect(DISTINCT n) AS nodes
77
+ WITH nodes[0..$cap] AS capped, size(nodes) AS totalCount
78
+ UNWIND capped AS node
79
+ WITH collect({ id: elementId(node), labels: labels(node), properties: properties(node) }) AS nodeRows,
80
+ [n IN capped | elementId(n)] AS capIds, totalCount
81
+ OPTIONAL MATCH (a)-[r]->(b)
82
+ WHERE elementId(a) IN capIds AND elementId(b) IN capIds
83
+ RETURN nodeRows,
84
+ collect(DISTINCT { from: elementId(startNode(r)), to: elementId(endNode(r)), type: type(r) }) AS edgeRows,
85
+ totalCount
86
+ `;
87
+ const result = await session.run(expandQuery, {
88
+ nodeId,
89
+ accountId,
90
+ cap: NODE_CAP,
91
+ });
92
+ if (result.records.length === 0) {
93
+ logLine(`[graph-render] reject account=${accountId} reason=empty nodeId=${nodeId}`);
94
+ return {
95
+ ok: false,
96
+ reason: "empty",
97
+ message: "Root node found but its neighbourhood is empty after trash filter.",
98
+ };
99
+ }
100
+ const record = result.records[0];
101
+ const nodeRows = record.get("nodeRows");
102
+ const edgeRowsRaw = record.get("edgeRows");
103
+ const totalCountRaw = record.get("totalCount");
104
+ const totalCount = typeof totalCountRaw === "number"
105
+ ? totalCountRaw
106
+ : (totalCountRaw?.toNumber?.() ?? nodeRows.length);
107
+ // OPTIONAL MATCH on no rels produces one {from:null,to:null,type:null}
108
+ // row from collect; strip those.
109
+ const edges = edgeRowsRaw
110
+ .filter((e) => e.from && e.to && e.type)
111
+ .map((e) => ({ from: e.from, to: e.to, type: e.type }));
112
+ if (nodeRows.length === 0) {
113
+ logLine(`[graph-render] reject account=${accountId} reason=empty nodeId=${nodeId}`);
114
+ return {
115
+ ok: false,
116
+ reason: "empty",
117
+ message: "Subgraph contained no nodes after trash filter.",
118
+ };
119
+ }
120
+ const layoutNodes = nodeRows.map((n) => ({
121
+ id: n.id,
122
+ labels: n.labels,
123
+ }));
124
+ const nodeProperties = {};
125
+ for (const n of nodeRows)
126
+ nodeProperties[n.id] = n.properties;
127
+ const layout = layoutGraph(layoutNodes, edges, {
128
+ width: IMAGE_WIDTH,
129
+ height: IMAGE_HEIGHT,
130
+ });
131
+ let pngBuffer;
132
+ try {
133
+ pngBuffer = drawGraph({
134
+ layout,
135
+ nodeProperties,
136
+ width: IMAGE_WIDTH,
137
+ height: IMAGE_HEIGHT,
138
+ });
139
+ }
140
+ catch (err) {
141
+ const message = err instanceof Error ? err.message : String(err);
142
+ logLine(`[graph-render] reject account=${accountId} reason=draw-error nodeId=${nodeId} cause=${message}`);
143
+ return {
144
+ ok: false,
145
+ reason: "draw-error",
146
+ message: `Canvas draw failed: ${message}`,
147
+ };
148
+ }
149
+ const truncated = totalCount > nodeRows.length
150
+ ? { rendered: nodeRows.length, total: totalCount }
151
+ : undefined;
152
+ const ms = Date.now() - t0;
153
+ logLine(`[graph-render] ok account=${accountId} ms=${ms} bytes=${pngBuffer.length} ` +
154
+ `nodeCount=${nodeRows.length} edgeCount=${edges.length} ` +
155
+ `truncated=${truncated ? "true" : "false"} iterations=${layout.iterations}`);
156
+ return {
157
+ ok: true,
158
+ pngBase64: pngBuffer.toString("base64"),
159
+ width: IMAGE_WIDTH,
160
+ height: IMAGE_HEIGHT,
161
+ nodeCount: nodeRows.length,
162
+ edgeCount: edges.length,
163
+ ...(truncated ? { truncated } : {}),
164
+ };
165
+ }
166
+ finally {
167
+ await session.close();
168
+ }
169
+ }
170
+ //# sourceMappingURL=graph-render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-render.js","sourceRoot":"","sources":["../../src/tools/graph-render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@maxy/graph-viewer",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "start": "node dist/index.js",
10
+ "test": "vitest run"
11
+ },
12
+ "dependencies": {
13
+ "@modelcontextprotocol/sdk": "^1.12.1",
14
+ "@napi-rs/canvas": "^0.1.56",
15
+ "d3-force": "^3.0.0",
16
+ "neo4j-driver": "^5.28.1",
17
+ "zod": "^3.23.0"
18
+ },
19
+ "devDependencies": {
20
+ "@types/d3-force": "^3.0.10",
21
+ "@types/node": "^22.0.0",
22
+ "typescript": "^5.7.0",
23
+ "vitest": "^4.1.2"
24
+ }
25
+ }
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ["src/**/*.test.ts"],
6
+ environment: "node",
7
+ },
8
+ });
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: render-graph
3
+ description: "Render an inline PNG of a Neo4j subgraph for the operator using the graph-render MCP tool. Use when the operator asks to 'see the graph', 'show me the subgraph around X', 'render the owner subgraph', or any request whose outcome is a picture of nodes-and-edges they can look at in chat. Never substitute Mermaid, ASCII, or any other graph stand-in: graph-render is the only correct surface, and if it refuses (foreign account, missing node, empty neighbourhood, draw error) surface the structured error to the operator and stop. The image uses the same palette and short-label strings as the admin /graph page; node positions will differ because the renderer is d3-force not vis-network."
4
+ ---
5
+
6
+ # render-graph
7
+
8
+ When the operator asks for a picture of a subgraph:
9
+
10
+ 1. Resolve the root node's `elementId` first — usually via `memory-search`
11
+ or another graph tool the operator already pointed you at. Never pass a
12
+ property like `name` as `nodeId`.
13
+ 2. Pick a `hopDepth` between 0 and 3. Default to 1 unless the operator
14
+ asks for the wider neighbourhood. Depth 3 on a busy node will hit the
15
+ 200-node cap and return a `truncated` envelope.
16
+ 3. Call `graph-render` with `{ accountId, nodeId, hopDepth }`.
17
+ 4. If the result is `ok: true`, the image attaches itself to your reply.
18
+ Tell the operator how many nodes are in view and, if `truncated` is
19
+ present, that there were more nodes than the cap.
20
+ 5. If the result is `ok: false`, surface the structured `reason` and
21
+ `message` to the operator verbatim. Do not retry with a different
22
+ nodeId without their input. Do not fall back to Mermaid, ASCII, or any
23
+ other graph substitute.
24
+
25
+ ## Reasons graph-render can refuse
26
+
27
+ - `scope` — the nodeId belongs to a different account, or does not exist.
28
+ - `empty` — the root exists but its neighbourhood is empty after the
29
+ trash filter.
30
+ - `cap-exceeded` — reserved for future use; today subgraphs above 200
31
+ nodes return `ok: true` with a `truncated` envelope.
32
+ - `draw-error` — the canvas renderer failed. The message contains the
33
+ underlying error; if it mentions a missing font, the system font fallback
34
+ is unavailable on this device.
35
+ - `invalid-input` — `hopDepth` was outside the 0-3 range.
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env node
2
+ // Publish-time gate: every PLUGIN.md with a non-empty `tools:` frontmatter
3
+ // block must also declare an `mcp:` block. The `tools:` block populates the
4
+ // `--allowed-tools mcp__<plugin>__*` spawn argv; without a registered MCP
5
+ // server the agent boots with permissions for tools that have no backend.
6
+ //
7
+ // The session manager throws the same defect at boot
8
+ // (tool-surface.ts 'mcp-block-missing'), but boot is on the operator's
9
+ // device — by then the bad payload has already been published and installed.
10
+ // This gate fires before `npm publish`, in the prepublishOnly chain.
11
+ //
12
+ // Live repro: Task 357 added visitor-graph tools to buyers/PLUGIN.md without
13
+ // the matching mcp: block; the slip-through is what motivates Task 368.
14
+
15
+ import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs'
16
+ import { dirname, join, resolve } from 'node:path'
17
+ import { fileURLToPath } from 'node:url'
18
+
19
+ const __dirname = dirname(fileURLToPath(import.meta.url))
20
+ const MAXY_CODE_ROOT = resolve(__dirname, '..', '..')
21
+
22
+ const PLUGIN_ROOTS = [
23
+ // platform/plugins/<plugin>/PLUGIN.md
24
+ { root: join(MAXY_CODE_ROOT, 'platform', 'plugins'), depth: 1 },
25
+ // premium-plugins/<bundle>/PLUGIN.md (shape b)
26
+ { root: join(MAXY_CODE_ROOT, 'premium-plugins'), depth: 1 },
27
+ // premium-plugins/<bundle>/plugins/<sub>/PLUGIN.md (shape a)
28
+ { root: join(MAXY_CODE_ROOT, 'premium-plugins'), depth: 2, mid: 'plugins' },
29
+ ]
30
+
31
+ function collectPluginDirs() {
32
+ const dirs = []
33
+ for (const spec of PLUGIN_ROOTS) {
34
+ if (!existsSync(spec.root)) continue
35
+ for (const entry of readdirSync(spec.root)) {
36
+ if (entry.startsWith('.')) continue
37
+ const lvl1 = join(spec.root, entry)
38
+ if (!statSync(lvl1).isDirectory()) continue
39
+ if (spec.depth === 1) {
40
+ dirs.push(lvl1)
41
+ continue
42
+ }
43
+ const midDir = join(lvl1, spec.mid)
44
+ if (!existsSync(midDir)) continue
45
+ if (!statSync(midDir).isDirectory()) continue
46
+ for (const sub of readdirSync(midDir)) {
47
+ if (sub.startsWith('.')) continue
48
+ const lvl2 = join(midDir, sub)
49
+ if (!statSync(lvl2).isDirectory()) continue
50
+ dirs.push(lvl2)
51
+ }
52
+ }
53
+ }
54
+ return dirs
55
+ }
56
+
57
+ function extractFrontmatter(md) {
58
+ if (!md.startsWith('---')) return null
59
+ const end = md.indexOf('\n---', 3)
60
+ if (end < 0) return null
61
+ return md.slice(3, end)
62
+ }
63
+
64
+ // Detect a top-level key in YAML frontmatter (key at column 0, followed by `:`).
65
+ // The key may be bare (`tools:`) — empty value means "block follows on
66
+ // subsequent indented lines" OR "empty list".
67
+ function findTopLevelKey(frontmatter, key) {
68
+ const re = new RegExp(`^${key}\\s*:`, 'm')
69
+ return re.test(frontmatter)
70
+ }
71
+
72
+ // Returns true when the `tools:` block has at least one list entry. A bare
73
+ // `tools:` with no children, or `tools: []`, is treated as empty (no surface).
74
+ function hasNonEmptyToolsBlock(frontmatter) {
75
+ const lines = frontmatter.split('\n')
76
+ let inTools = false
77
+ for (let i = 0; i < lines.length; i++) {
78
+ const line = lines[i]
79
+ if (/^tools\s*:/.test(line)) {
80
+ const after = line.replace(/^tools\s*:/, '').trim()
81
+ // Inline form: `tools: []` or `tools: [a, b]`
82
+ if (after.startsWith('[')) {
83
+ return /\[\s*[^\]\s]/.test(after)
84
+ }
85
+ if (after) {
86
+ // Single-line scalar (unusual for tools, but treat as non-empty).
87
+ return true
88
+ }
89
+ inTools = true
90
+ continue
91
+ }
92
+ if (inTools) {
93
+ if (/^\S/.test(line) && line.trim() !== '') {
94
+ // Top-level key after tools: — block ended.
95
+ return false
96
+ }
97
+ if (/^\s*-\s+/.test(line)) return true
98
+ }
99
+ }
100
+ return false
101
+ }
102
+
103
+ function checkPlugin(pluginDir) {
104
+ const manifestPath = join(pluginDir, 'PLUGIN.md')
105
+ if (!existsSync(manifestPath)) return null
106
+ const raw = readFileSync(manifestPath, 'utf-8')
107
+ const fm = extractFrontmatter(raw)
108
+ if (fm === null) return null
109
+ if (!hasNonEmptyToolsBlock(fm)) return null
110
+ if (findTopLevelKey(fm, 'mcp')) return null
111
+ return manifestPath
112
+ }
113
+
114
+ function main() {
115
+ const offenders = []
116
+ for (const dir of collectPluginDirs()) {
117
+ const offender = checkPlugin(dir)
118
+ if (offender) offenders.push({ dir, manifestPath: offender })
119
+ }
120
+ if (offenders.length === 0) {
121
+ console.log('[gate] check-plugin-tools-mcp-consistency: PASS')
122
+ return
123
+ }
124
+ for (const { dir, manifestPath } of offenders) {
125
+ const plugin = dir.split('/').pop()
126
+ console.error(
127
+ `[gate] FATAL: plugin=${plugin} has tools: block but no mcp: block.\n` +
128
+ ` manifest: ${manifestPath}\n` +
129
+ ` Spawns would pass --allowed-tools mcp__${plugin}__* with no registered server.\n` +
130
+ ` Add mcp: block or remove tools: entries.`,
131
+ )
132
+ }
133
+ process.exit(1)
134
+ }
135
+
136
+ main()
@@ -35,6 +35,9 @@ tools:
35
35
  - name: visitor-backfill-from-logs
36
36
  publicAllowlist: false
37
37
  adminAllowlist: true
38
+ - name: mint-visitor-token
39
+ publicAllowlist: false
40
+ adminAllowlist: true
38
41
  always: false
39
42
  metadata: {"platform":{"optional":true,"embed":["public","admin"]}}
40
43
  ---
@@ -66,10 +69,11 @@ Skills operate via existing platform tools plus the visitor-graph MCP added in T
66
69
  - `visitor-recent-by-person` — recent :Session activity for a :Person (morning round, 1:1 prep)
67
70
  - `visitor-recent-by-page` — who has viewed a listing recently (operator side-bar)
68
71
  - `visitor-engagement-score` — engagement-ranked :Person list (nurture queue)
69
- - `visitor-recommendation-ctr` — recommendation CTR over a window (replaces Task 336 log-scan after 14-day parity)
72
+ - `visitor-recommendation-ctr` — recommendation CTR over a window, joined from `:Recommendation` and `:Click` nodes
70
73
  - `visitor-session-detail` — full event timeline for one :Session
71
74
  - `visitor-event-ingest` — admin-side companion to `POST /v/event` for test harness work
72
- - `visitor-backfill-from-logs` — one-shot importer for Task 336/294 logs with parity check
75
+ - `visitor-backfill-from-logs` — one-shot importer for `[property-recommended]` and `[property-card-click]` log lines, used to recover late-arriving sessions
76
+ - `mint-visitor-token` — sign a `&v=<token>` value bound to a `:Person` so outbound URLs in morning-round / lead-nurturing / vendor-updates attribute clicks back to the recipient (Task 362)
73
77
 
74
78
  ## References
75
79
 
@@ -1,13 +1,14 @@
1
- // buyers MCP server — visitor-graph tools (Task 357).
1
+ // buyers MCP server — visitor-graph tools (Task 357 + 362).
2
2
  //
3
- // Seven tools, all admin-side (none exposed on the public agent):
3
+ // Eight tools, all admin-side (none exposed on the public agent):
4
4
  // visitor-recent-by-person query
5
5
  // visitor-recent-by-page query
6
6
  // visitor-engagement-score query
7
- // visitor-recommendation-ctr query (replaces 336 log-scan after parity)
7
+ // visitor-recommendation-ctr query (graph-backed CTR)
8
8
  // visitor-session-detail query
9
9
  // visitor-event-ingest write (test harness / manual replay)
10
- // visitor-backfill-from-logs write (one-shot importer + parity check)
10
+ // visitor-backfill-from-logs write (one-shot recovery importer)
11
+ // mint-visitor-token mint (Task 362 — sign &v=<token> URLs)
11
12
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
13
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
14
  import { z } from "zod";
@@ -19,6 +20,7 @@ import { visitorRecommendationCtr } from "./tools/visitor-recommendation-ctr.js"
19
20
  import { visitorSessionDetail } from "./tools/visitor-session-detail.js";
20
21
  import { visitorEventIngest } from "./tools/visitor-event-ingest.js";
21
22
  import { visitorBackfillFromLogs } from "./tools/visitor-backfill-from-logs.js";
23
+ import { mintVisitorTokenTool } from "./tools/mint-visitor-token.js";
22
24
  const accountId = process.env.ACCOUNT_ID ?? "local";
23
25
  const server = new McpServer({ name: "maxy-buyers", version: "0.1.0" });
24
26
  console.error(`[buyers] server started, account=${accountId}`);
@@ -75,12 +77,16 @@ server.tool("visitor-event-ingest", "Admin-side companion to POST /v/event for t
75
77
  dwellMs: z.number().int().min(0).optional(),
76
78
  occurredAt: z.string().optional().describe("ISO timestamp override"),
77
79
  }, jsonHandler((p) => visitorEventIngest(p)));
78
- server.tool("visitor-backfill-from-logs", "One-shot importer: parse [property-recommended] and [property-card-click] log lines and write into the visitor graph. Idempotent for Recommendations; immutable for Click events. Reports a parityCheck comparing graph counts to log counts.", {
80
+ server.tool("visitor-backfill-from-logs", "One-shot importer: parse [property-recommended] and [property-card-click] log lines and write into the visitor graph. Idempotent for Recommendations; immutable for Click events. Use to recover late-arriving sessions or for ad-hoc forensics; the graph is the canonical aggregation surface.", {
79
81
  logPath: z.string().min(1).describe("Absolute path to the platform log file"),
80
82
  sinceIso: z.string().optional().describe("ISO timestamp lower bound (inclusive)"),
81
83
  untilIso: z.string().optional().describe("ISO timestamp upper bound (exclusive)"),
82
84
  batchSize: z.number().int().positive().max(2000).optional().describe("Lines per Cypher batch (default 200)"),
83
85
  }, jsonHandler((p) => visitorBackfillFromLogs(p)));
86
+ server.tool("mint-visitor-token", "Mint a signed visitor token bound to a known :Person elementId. Append the returned token to outbound listing URLs as `&v=<token>` (e.g. /listings/<slug>/click?session=<sk>&v=<token>). When the recipient clicks, the UI server verifies the HMAC and writes the same value into the `mxy_v` cookie so subsequent /v/event posts attribute to that :Person. TTL defaults to 30 days. Use from morning-round, lead-nurturing, and vendor-updates whenever the outbound URL goes to a known :Person.", {
87
+ personId: z.string().min(1).describe(":Person elementId to bind the token to"),
88
+ ttlDays: z.number().int().positive().max(365).optional().describe("Token lifetime in days (default 30, max 365)"),
89
+ }, jsonHandler((p) => mintVisitorTokenTool(p)));
84
90
  process.on("SIGINT", async () => {
85
91
  console.error("[buyers] SIGINT — closing driver");
86
92
  await closeDriver();
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export interface MintOpts {
2
+ personId: string;
3
+ expiryMs?: number;
4
+ }
5
+ export declare function mintVisitorToken(opts: MintOpts): {
6
+ token: string;
7
+ expiryMs: number;
8
+ };
9
+ //# sourceMappingURL=visitor-token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visitor-token.d.ts","sourceRoot":"","sources":["../../src/lib/visitor-token.ts"],"names":[],"mappings":"AAkEA,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAOpF"}
@@ -0,0 +1,66 @@
1
+ // Visitor-token minting — admin-side surface (Task 362).
2
+ //
3
+ // Mirrors the mint half of platform/ui/app/lib/visitor-token.ts. Verification
4
+ // still lives in the UI server (the only verifier; mxy_v cookie + /v/event +
5
+ // /listings/:slug/click consume tokens). Both processes share one secret file
6
+ // under <MAXY_DIR>/credentials/visitor-token-secret via the wx-create pattern,
7
+ // so whichever process reads first mints; the other reads the same bytes.
8
+ //
9
+ // MAXY_DIR is resolved from process.env.LOG_DIR's parent — the buyers MCP is
10
+ // spawned with LOG_DIR=<MAXY_DIR>/logs (see buyers/PLUGIN.md env block), so
11
+ // dirname(LOG_DIR) is the brand-scoped config dir whether the brand is maxy,
12
+ // realagent, or any future brand.
13
+ import { createHmac, randomBytes } from "node:crypto";
14
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
15
+ import { dirname, resolve } from "node:path";
16
+ const TOKEN_PREFIX = "v1.";
17
+ const SECRET_BYTES = 32;
18
+ const DEFAULT_TTL_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
19
+ let cachedSecret = null;
20
+ function secretFilePath() {
21
+ const logDir = process.env.LOG_DIR;
22
+ if (!logDir) {
23
+ throw new Error("[buyers] LOG_DIR unset — cannot resolve visitor-token secret path");
24
+ }
25
+ return resolve(dirname(logDir), "credentials", "visitor-token-secret");
26
+ }
27
+ function getSecret() {
28
+ if (cachedSecret)
29
+ return cachedSecret;
30
+ const path = secretFilePath();
31
+ try {
32
+ const hex = readFileSync(path, "utf-8").trim();
33
+ if (hex.length === SECRET_BYTES * 2) {
34
+ cachedSecret = Buffer.from(hex, "hex");
35
+ return cachedSecret;
36
+ }
37
+ }
38
+ catch {
39
+ /* fall through to mint */
40
+ }
41
+ const fresh = randomBytes(SECRET_BYTES).toString("hex");
42
+ try {
43
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
44
+ writeFileSync(path, fresh, { mode: 0o600, flag: "wx" });
45
+ console.error(`[buyers] visitor-token secret minted path=${path}`);
46
+ }
47
+ catch {
48
+ // Race with the UI server (or a peer reader) between read and write;
49
+ // the second read below picks up their value.
50
+ }
51
+ const hex = readFileSync(path, "utf-8").trim();
52
+ cachedSecret = Buffer.from(hex, "hex");
53
+ return cachedSecret;
54
+ }
55
+ function base64urlEncode(buf) {
56
+ return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
57
+ }
58
+ export function mintVisitorToken(opts) {
59
+ const expiryMs = opts.expiryMs ?? Date.now() + DEFAULT_TTL_MS;
60
+ const payload = { p: opts.personId, e: expiryMs };
61
+ const payloadJson = JSON.stringify(payload);
62
+ const payloadB64 = base64urlEncode(Buffer.from(payloadJson, "utf-8"));
63
+ const sig = base64urlEncode(createHmac("sha256", getSecret()).update(payloadJson).digest());
64
+ return { token: `${TOKEN_PREFIX}${payloadB64}.${sig}`, expiryMs };
65
+ }
66
+ //# sourceMappingURL=visitor-token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visitor-token.js","sourceRoot":"","sources":["../../src/lib/visitor-token.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export interface MintParams {
2
+ accountId: string;
3
+ personId: string;
4
+ ttlDays?: number;
5
+ }
6
+ export interface MintResult {
7
+ token: string;
8
+ expiryMs: number;
9
+ expiresAt: string;
10
+ }
11
+ export declare function mintVisitorTokenTool(p: MintParams): Promise<MintResult>;
12
+ //# sourceMappingURL=mint-visitor-token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mint-visitor-token.d.ts","sourceRoot":"","sources":["../../src/tools/mint-visitor-token.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAKD,wBAAsB,oBAAoB,CAAC,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAY7E"}
@@ -0,0 +1,21 @@
1
+ // mint-visitor-token — admin-side mint of a signed visitor token bound to
2
+ // a known :Person elementId. The agent appends `&v=<token>` to listing-click
3
+ // URLs in outbound marketing surfaces (morning-round, lead-nurturing,
4
+ // vendor-updates). Verification stays in the UI server; this tool only mints.
5
+ import { mintVisitorToken } from "../lib/visitor-token.js";
6
+ const DEFAULT_TTL_DAYS = 30;
7
+ const MAX_TTL_DAYS = 365;
8
+ export async function mintVisitorTokenTool(p) {
9
+ if (!p.personId || typeof p.personId !== "string") {
10
+ throw new Error("personId required");
11
+ }
12
+ const ttlDays = Math.max(1, Math.min(p.ttlDays ?? DEFAULT_TTL_DAYS, MAX_TTL_DAYS));
13
+ const expiryMs = Date.now() + ttlDays * 24 * 60 * 60 * 1000;
14
+ const minted = mintVisitorToken({ personId: p.personId, expiryMs });
15
+ return {
16
+ token: minted.token,
17
+ expiryMs: minted.expiryMs,
18
+ expiresAt: new Date(minted.expiryMs).toISOString(),
19
+ };
20
+ }
21
+ //# sourceMappingURL=mint-visitor-token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mint-visitor-token.js","sourceRoot":"","sources":["../../src/tools/mint-visitor-token.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,6EAA6E;AAC7E,sEAAsE;AACtE,8EAA8E;AAE9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAc3D,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAC5B,MAAM,YAAY,GAAG,GAAG,CAAC;AAEzB,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,CAAa;IACtD,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;IACvC,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,IAAI,gBAAgB,EAAE,YAAY,CAAC,CAAC,CAAC;IACnF,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC5D,MAAM,MAAM,GAAG,gBAAgB,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;IACpE,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,SAAS,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE;KACnD,CAAC;AACJ,CAAC"}
@@ -14,11 +14,6 @@ export interface BackfillResult {
14
14
  clickedParsed: number;
15
15
  clickedWritten: number;
16
16
  unparseable: number;
17
- parityCheck: "ok" | "mismatch";
18
- parityDelta: {
19
- recommended: number;
20
- clicked: number;
21
- };
22
17
  }
23
18
  export declare function visitorBackfillFromLogs(p: BackfillParams): Promise<BackfillResult>;
24
19
  //# sourceMappingURL=visitor-backfill-from-logs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"visitor-backfill-from-logs.d.ts","sourceRoot":"","sources":["../../src/tools/visitor-backfill-from-logs.ts"],"names":[],"mappings":"AA2BA,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,2BAA2B,EAAE,MAAM,CAAC;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,IAAI,GAAG,UAAU,CAAC;IAC/B,WAAW,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACvD;AA2BD,wBAAsB,uBAAuB,CAAC,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAmHxF"}
1
+ {"version":3,"file":"visitor-backfill-from-logs.d.ts","sourceRoot":"","sources":["../../src/tools/visitor-backfill-from-logs.ts"],"names":[],"mappings":"AA2BA,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,2BAA2B,EAAE,MAAM,CAAC;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;CACrB;AA2BD,wBAAsB,uBAAuB,CAAC,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CA0FxF"}