@rubytech/create-realagent-code 0.1.125 → 0.1.127
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/dist/index.js +23 -1
- package/package.json +2 -2
- package/payload/platform/config/brand.json +20 -1
- package/payload/platform/lib/aeo-llms-txt-writer/dist/index.d.ts +33 -0
- package/payload/platform/lib/aeo-llms-txt-writer/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/aeo-llms-txt-writer/dist/index.js +119 -0
- package/payload/platform/lib/aeo-llms-txt-writer/dist/index.js.map +1 -0
- package/payload/platform/lib/aeo-llms-txt-writer/src/index.ts +172 -0
- package/payload/platform/lib/aeo-llms-txt-writer/tsconfig.json +8 -0
- package/payload/platform/lib/graph-style/dist/index.d.ts +78 -0
- package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/graph-style/dist/index.js +296 -0
- package/payload/platform/lib/graph-style/dist/index.js.map +1 -0
- package/payload/platform/lib/graph-style/src/__tests__/parity.test.ts +114 -0
- package/payload/platform/lib/graph-style/src/index.ts +307 -0
- package/payload/platform/lib/graph-style/tsconfig.json +9 -0
- package/payload/platform/lib/graph-style/vitest.config.ts +9 -0
- package/payload/platform/package-lock.json +318 -0
- package/payload/platform/package.json +3 -2
- package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
- package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/admin/PLUGIN.md +4 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js +73 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/tools/publish-site.d.ts +34 -0
- package/payload/platform/plugins/admin/mcp/dist/tools/publish-site.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/tools/publish-site.js +176 -0
- package/payload/platform/plugins/admin/mcp/dist/tools/publish-site.js.map +1 -0
- package/payload/platform/plugins/admin/skills/publish-site/SKILL.md +19 -57
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.d.ts +5 -38
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.d.ts.map +1 -1
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.js +7 -114
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.js.map +1 -1
- package/payload/platform/plugins/docs/references/visitor-graph.md +4 -3
- package/payload/platform/plugins/graph-viewer/.claude-plugin/plugin.json +8 -0
- package/payload/platform/plugins/graph-viewer/PLUGIN.md +56 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/index.d.ts +7 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/index.js +74 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.js +43 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.d.ts +28 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.d.ts.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.js +73 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.js.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.d.ts +40 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.d.ts.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.js +77 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.js.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.d.ts +45 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.d.ts.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js +170 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/package.json +25 -0
- package/payload/platform/plugins/graph-viewer/mcp/vitest.config.ts +8 -0
- package/payload/platform/plugins/graph-viewer/skills/render-graph/SKILL.md +35 -0
- package/payload/platform/scripts/check-plugin-tools-mcp-consistency.mjs +136 -0
- package/payload/platform/templates/specialists/agents/content-producer.md +3 -3
- package/payload/premium-plugins/real-agent/plugins/buyers/PLUGIN.md +6 -2
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/index.js +11 -5
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/index.js.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/lib/visitor-token.d.ts +9 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/lib/visitor-token.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/lib/visitor-token.js +66 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/lib/visitor-token.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/mint-visitor-token.d.ts +12 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/mint-visitor-token.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/mint-visitor-token.js +21 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/mint-visitor-token.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-backfill-from-logs.d.ts +0 -5
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-backfill-from-logs.d.ts.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-backfill-from-logs.js +6 -27
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-backfill-from-logs.js.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/index.ts +17 -5
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/lib/visitor-token.ts +79 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/tools/mint-visitor-token.ts +35 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/tools/visitor-backfill-from-logs.ts +6 -33
- package/payload/premium-plugins/real-agent/plugins/buyers/skills/property-recommender/SKILL.md +1 -10
- package/payload/premium-plugins/real-agent/plugins/leads/skills/lead-nurturing/SKILL.md +1 -1
- package/payload/premium-plugins/real-agent/plugins/leads/skills/morning-round/SKILL.md +1 -1
- package/payload/premium-plugins/real-agent/plugins/vendors/skills/vendor-updates/SKILL.md +1 -1
- package/payload/server/public/assets/{admin-D6IfAzYY.js → admin-FcRHAL-3.js} +7 -7
- package/payload/server/public/assets/{data-BGbQyufe.js → data-Ds37mflX.js} +1 -1
- package/payload/server/public/assets/{graph-D-1lRTeL.js → graph-CmWRhaiS.js} +1 -1
- package/payload/server/public/assets/graph-labels-Ch2r00Gt.js +1 -0
- package/payload/server/public/assets/page-BOtNny_4.js +51 -0
- package/payload/server/public/assets/page-BcHhJXUt.js +1 -0
- package/payload/server/public/consent.css +97 -0
- package/payload/server/public/consent.js +259 -0
- package/payload/server/public/data.html +3 -3
- package/payload/server/public/graph.html +3 -3
- package/payload/server/public/index.html +4 -4
- package/payload/server/public/privacy.html +68 -5
- package/payload/server/public/v.js +53 -0
- package/payload/server/server.js +465 -176
- package/payload/server/public/assets/graph-labels-BRXJHNYE.js +0 -1
- package/payload/server/public/assets/page-B4oirCvn.js +0 -1
- package/payload/server/public/assets/page-FmJ7PIYx.js +0 -51
|
@@ -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()
|
|
@@ -3,7 +3,7 @@ name: content-producer
|
|
|
3
3
|
description: "Visual production and static-site hosting. Reads from the populated graph to produce visual artifacts (image generation, PDF rendering, component delivery) and hosts already-prepared static sites by extracting attached archives via unzip-attachment then placing the tree under <accountDir>/sites/<slug>/ via publish-site. Delegate for: generating images, saving rendered pages as PDF, or any 'host this website' / 'publish this site' / 'put this online' intent carrying an HTML+assets archive. Not document ingestion: graph ingestion of any kind routes to specialists:database-operator. Static-site zips are extracted to disk for publication, never written to the graph."
|
|
4
4
|
summary: "Produces visual output from your graph: generates images, renders pages to PDF, and hosts static websites you upload as a zip."
|
|
5
5
|
model: claude-sonnet-4-6
|
|
6
|
-
tools: Bash, mcp__memory__memory-search, mcp__replicate__image-generate, mcp__plugin_playwright_playwright__browser_navigate, mcp__plugin_playwright_playwright__browser_snapshot, mcp__plugin_playwright_playwright__browser_take_screenshot, mcp__plugin_playwright_playwright__browser_pdf_save, mcp__admin__file-attach, mcp__admin__plugin-read, mcp__admin__public-hostname
|
|
6
|
+
tools: Bash, mcp__memory__memory-search, mcp__replicate__image-generate, mcp__plugin_playwright_playwright__browser_navigate, mcp__plugin_playwright_playwright__browser_snapshot, mcp__plugin_playwright_playwright__browser_take_screenshot, mcp__plugin_playwright_playwright__browser_pdf_save, mcp__admin__file-attach, mcp__admin__plugin-read, mcp__admin__public-hostname, mcp__admin__publish-site
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Content Producer
|
|
@@ -34,9 +34,9 @@ Generate the HTML, navigate the Playwright browser to it, and capture with `brow
|
|
|
34
34
|
|
|
35
35
|
## Hosting websites
|
|
36
36
|
|
|
37
|
-
When a brief carries a "host this website" / "publish this site" / "put this online" intent with a `.zip` of HTML and assets, run
|
|
37
|
+
When a brief carries a "host this website" / "publish this site" / "put this online" intent with a `.zip` of HTML and assets, run `skill-load skillName=unzip-attachment` to extract, then call `mcp__admin__publish-site` directly. The tool owns slug validation, the symlink scan, the `mv`, the refusal taxonomy, and the post-publish llms.txt refresh — load `skill-load skillName=publish-site` only if you need the intent-router context. Do not improvise a fallback server.
|
|
38
38
|
|
|
39
|
-
Confirm the slug with the operator before publishing. The slug is one or more `/`-separated segments under `<accountDir>/sites/`. Refusals are loud-fail; relay the
|
|
39
|
+
Confirm the slug with the operator before publishing. The slug is one or more `/`-separated segments under `<accountDir>/sites/`. Refusals are loud-fail; relay the tool's `Operator action:` line verbatim and stop. On success, take the `pathSlug` from the tool response, call `mcp__admin__public-hostname`, and surface `https://<hostname><pathSlug>` to the operator as one line.
|
|
40
40
|
|
|
41
41
|
## File delivery
|
|
42
42
|
|
|
@@ -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
|
|
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
|
|
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
|
-
//
|
|
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 (
|
|
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
|
|
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.
|
|
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":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -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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"}
|
package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/mint-visitor-token.d.ts
ADDED
|
@@ -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
|
package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/mint-visitor-token.d.ts.map
ADDED
|
@@ -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"}
|
package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/mint-visitor-token.js
ADDED
|
@@ -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
|
package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/mint-visitor-token.js.map
ADDED
|
@@ -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;
|
|
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"}
|
|
@@ -12,15 +12,15 @@
|
|
|
12
12
|
// -> MATCH/MERGE Session, CREATE Click {label:'card', listingSlug}
|
|
13
13
|
// MERGE Session-[:HAS_EVENT]->Click
|
|
14
14
|
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
15
|
+
// Recovery path: used when a live POST /v/event write was lost (process
|
|
16
|
+
// restart, dropped connection) or for ad-hoc forensics. The graph is
|
|
17
|
+
// the canonical surface — these log lines are the join key, not the
|
|
18
|
+
// aggregation path.
|
|
18
19
|
//
|
|
19
20
|
// Idempotency: re-running over the same log window does not duplicate
|
|
20
21
|
// Recommendation nodes (MERGE on the exact sessionKey + slug + ts tuple).
|
|
21
22
|
// Click nodes are immutable event records by design; if you re-run on a
|
|
22
|
-
// window already processed you get duplicates.
|
|
23
|
-
// cutover.
|
|
23
|
+
// window already processed you get duplicates.
|
|
24
24
|
import { readFileSync } from "node:fs";
|
|
25
25
|
import { getSession } from "../lib/neo4j.js";
|
|
26
26
|
const REC_RE = /\[property-recommended\]\s+sessionKey=(\S+)\s+listingSlug=(\S+)\s+ts=(\S+)/;
|
|
@@ -116,26 +116,7 @@ export async function visitorBackfillFromLogs(p) {
|
|
|
116
116
|
const row = result.records[0];
|
|
117
117
|
clickedWritten += Number(row?.get("written") ?? 0);
|
|
118
118
|
}
|
|
119
|
-
|
|
120
|
-
WHERE rec.recommendedAt >= datetime($since) AND rec.recommendedAt < datetime($until)
|
|
121
|
-
WITH count(DISTINCT rec) AS recCount
|
|
122
|
-
OPTIONAL MATCH (cl:Click {accountId: $accountId, label: 'card'})
|
|
123
|
-
WHERE cl.occurredAt >= datetime($since) AND cl.occurredAt < datetime($until)
|
|
124
|
-
AND cl.backfilled = true
|
|
125
|
-
RETURN recCount, count(DISTINCT cl) AS clickCount`, {
|
|
126
|
-
accountId: p.accountId,
|
|
127
|
-
since: windowStart ?? new Date(0).toISOString(),
|
|
128
|
-
until: windowEnd ? new Date(Date.parse(windowEnd) + 1).toISOString() : new Date().toISOString(),
|
|
129
|
-
});
|
|
130
|
-
const head = parityRes.records[0];
|
|
131
|
-
const recGraph = Number(head?.get("recCount") ?? 0);
|
|
132
|
-
const clickGraph = Number(head?.get("clickCount") ?? 0);
|
|
133
|
-
const parityDelta = {
|
|
134
|
-
recommended: recGraph - rec.length,
|
|
135
|
-
clicked: clickGraph - click.length,
|
|
136
|
-
};
|
|
137
|
-
const parityCheck = parityDelta.recommended === 0 && parityDelta.clicked === 0 ? "ok" : "mismatch";
|
|
138
|
-
console.error(`[visitor-backfill] done parityCheck=${parityCheck} recParsed=${rec.length} recWritten=${recommendedWritten} clickParsed=${click.length} clickWritten=${clickedWritten} unparseable=${unparseable}`);
|
|
119
|
+
console.error(`[visitor-backfill] done recParsed=${rec.length} recWritten=${recommendedWritten} clickParsed=${click.length} clickWritten=${clickedWritten} unparseable=${unparseable}`);
|
|
139
120
|
return {
|
|
140
121
|
windowStart,
|
|
141
122
|
windowEnd,
|
|
@@ -145,8 +126,6 @@ export async function visitorBackfillFromLogs(p) {
|
|
|
145
126
|
clickedParsed: click.length,
|
|
146
127
|
clickedWritten,
|
|
147
128
|
unparseable,
|
|
148
|
-
parityCheck,
|
|
149
|
-
parityDelta,
|
|
150
129
|
};
|
|
151
130
|
}
|
|
152
131
|
finally {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"visitor-backfill-from-logs.js","sourceRoot":"","sources":["../../src/tools/visitor-backfill-from-logs.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,EAAE;AACF,yEAAyE;AACzE,2DAA2D;AAC3D,EAAE;AACF,uEAAuE;AACvE,sCAAsC;AACtC,0EAA0E;AAC1E,0DAA0D;AAC1D,EAAE;AACF,sEAAsE;AACtE,uEAAuE;AACvE,2CAA2C;AAC3C,EAAE;AACF,
|
|
1
|
+
{"version":3,"file":"visitor-backfill-from-logs.js","sourceRoot":"","sources":["../../src/tools/visitor-backfill-from-logs.ts"],"names":[],"mappings":"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"}
|
|
@@ -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
|
-
//
|
|
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 (
|
|
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
|
|
10
|
+
// visitor-backfill-from-logs write (one-shot recovery importer)
|
|
11
|
+
// mint-visitor-token mint (Task 362 — sign &v=<token> URLs)
|
|
11
12
|
|
|
12
13
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
13
14
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -21,6 +22,7 @@ import { visitorRecommendationCtr } from "./tools/visitor-recommendation-ctr.js"
|
|
|
21
22
|
import { visitorSessionDetail } from "./tools/visitor-session-detail.js";
|
|
22
23
|
import { visitorEventIngest } from "./tools/visitor-event-ingest.js";
|
|
23
24
|
import { visitorBackfillFromLogs } from "./tools/visitor-backfill-from-logs.js";
|
|
25
|
+
import { mintVisitorTokenTool } from "./tools/mint-visitor-token.js";
|
|
24
26
|
|
|
25
27
|
const accountId = process.env.ACCOUNT_ID ?? "local";
|
|
26
28
|
|
|
@@ -119,7 +121,7 @@ server.tool(
|
|
|
119
121
|
|
|
120
122
|
server.tool(
|
|
121
123
|
"visitor-backfill-from-logs",
|
|
122
|
-
"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.
|
|
124
|
+
"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.",
|
|
123
125
|
{
|
|
124
126
|
logPath: z.string().min(1).describe("Absolute path to the platform log file"),
|
|
125
127
|
sinceIso: z.string().optional().describe("ISO timestamp lower bound (inclusive)"),
|
|
@@ -129,6 +131,16 @@ server.tool(
|
|
|
129
131
|
jsonHandler((p) => visitorBackfillFromLogs(p as unknown as Parameters<typeof visitorBackfillFromLogs>[0])),
|
|
130
132
|
);
|
|
131
133
|
|
|
134
|
+
server.tool(
|
|
135
|
+
"mint-visitor-token",
|
|
136
|
+
"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.",
|
|
137
|
+
{
|
|
138
|
+
personId: z.string().min(1).describe(":Person elementId to bind the token to"),
|
|
139
|
+
ttlDays: z.number().int().positive().max(365).optional().describe("Token lifetime in days (default 30, max 365)"),
|
|
140
|
+
},
|
|
141
|
+
jsonHandler((p) => mintVisitorTokenTool(p as unknown as Parameters<typeof mintVisitorTokenTool>[0])),
|
|
142
|
+
);
|
|
143
|
+
|
|
132
144
|
process.on("SIGINT", async () => {
|
|
133
145
|
console.error("[buyers] SIGINT — closing driver");
|
|
134
146
|
await closeDriver();
|