neuron-inspector 0.2.1 → 0.3.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/dist/server.js CHANGED
@@ -1,9 +1,51 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
  import { WebSocketServer, WebSocket } from "ws";
4
+ import { z } from "zod";
4
5
  import { PendingCalls } from "./correlation.js";
5
6
  import { TOOLS, toolToPrimitive } from "./tools.js";
6
7
  import { RECIPE_TOOLS, handleRecipeTool } from "./recipe-tools.js";
8
+ import { COMPOUND_TOOLS, handleCompoundTool } from "./compound-tools.js";
9
+ function propToZod(p) {
10
+ let zt;
11
+ if (Array.isArray(p?.enum) && p.enum.length > 0) {
12
+ zt = z.enum(p.enum);
13
+ }
14
+ else {
15
+ switch (p?.type) {
16
+ case "number":
17
+ case "integer":
18
+ zt = z.number();
19
+ break;
20
+ case "boolean":
21
+ zt = z.boolean();
22
+ break;
23
+ case "array":
24
+ zt = z.array(p.items ? propToZod(p.items) : z.any());
25
+ break;
26
+ case "object":
27
+ zt = p.properties ? z.object(shapeFromSchema(p)) : z.record(z.string(), z.any());
28
+ break;
29
+ case "string":
30
+ zt = z.string();
31
+ break;
32
+ default: zt = z.any();
33
+ }
34
+ }
35
+ if (p?.description)
36
+ zt = zt.describe(p.description);
37
+ return zt;
38
+ }
39
+ function shapeFromSchema(schema) {
40
+ const props = schema?.properties ?? {};
41
+ const required = Array.isArray(schema?.required) ? schema.required : [];
42
+ const shape = {};
43
+ for (const [k, v] of Object.entries(props)) {
44
+ const zt = propToZod(v);
45
+ shape[k] = required.includes(k) ? zt : zt.optional();
46
+ }
47
+ return shape;
48
+ }
7
49
  const PORT = parseInt(process.env.NEURON_BRIDGE_PORT ?? "7377", 10);
8
50
  // ── State ────────────────────────────────────────────────────
9
51
  let extensionWs = null;
@@ -43,7 +85,7 @@ const mcp = new McpServer({
43
85
  });
44
86
  // Browser tools (forwarded to extension via WebSocket)
45
87
  for (const tool of TOOLS) {
46
- mcp.tool(tool.name, tool.description, tool.inputSchema, async (args) => {
88
+ mcp.tool(tool.name, tool.description, shapeFromSchema(tool.inputSchema), async (args) => {
47
89
  if (!extensionWs || extensionWs.readyState !== WebSocket.OPEN) {
48
90
  return {
49
91
  content: [{ type: "text", text: "Extension not connected. Open Chrome with the Neuron extension and enable developer mode." }],
@@ -67,7 +109,7 @@ for (const tool of TOOLS) {
67
109
  }
68
110
  // Recipe tools (local, no extension needed)
69
111
  for (const tool of RECIPE_TOOLS) {
70
- mcp.tool(tool.name, tool.description, tool.inputSchema, async (args) => {
112
+ mcp.tool(tool.name, tool.description, shapeFromSchema(tool.inputSchema), async (args) => {
71
113
  try {
72
114
  const result = await handleRecipeTool(tool.name, args);
73
115
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -80,11 +122,33 @@ for (const tool of RECIPE_TOOLS) {
80
122
  }
81
123
  });
82
124
  }
125
+ // Compound tools (bridge-side orchestration, forward to extension)
126
+ for (const tool of COMPOUND_TOOLS) {
127
+ mcp.tool(tool.name, tool.description, shapeFromSchema(tool.inputSchema), async (args) => {
128
+ if (!extensionWs || extensionWs.readyState !== WebSocket.OPEN) {
129
+ return {
130
+ content: [{ type: "text", text: "Extension not connected. Open Chrome with the Neuron extension and enable developer mode." }],
131
+ isError: true,
132
+ };
133
+ }
134
+ try {
135
+ const result = await handleCompoundTool(tool.name, args, { ws: extensionWs, pending });
136
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
137
+ }
138
+ catch (err) {
139
+ return {
140
+ content: [{ type: "text", text: `Error: ${err.message}` }],
141
+ isError: true,
142
+ };
143
+ }
144
+ });
145
+ }
83
146
  // ── Start ────────────────────────────────────────────────────
84
147
  async function main() {
85
148
  const transport = new StdioServerTransport();
86
149
  await mcp.connect(transport);
87
- console.error(`[bridge] MCP server ready on stdio (${TOOLS.length} browser + ${RECIPE_TOOLS.length} recipe tools)`);
150
+ const total = TOOLS.length + COMPOUND_TOOLS.length + RECIPE_TOOLS.length;
151
+ console.error(`[bridge] MCP server ready on stdio (${total} tools: ${TOOLS.length} browser + ${COMPOUND_TOOLS.length} compound + ${RECIPE_TOOLS.length} recipe)`);
88
152
  }
89
153
  main().catch((err) => {
90
154
  console.error("[bridge] Fatal:", err);
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neuron-inspector",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "64 tools for AI agents. Turn Chrome into an MCP server — inspect DOM, automate clicks, audit security, mock APIs, extract data, record demos, self-improving recipes.",
5
5
  "type": "module",
6
6
  "main": "dist/server.js",
@@ -49,7 +49,8 @@
49
49
  "dependencies": {
50
50
  "@modelcontextprotocol/sdk": "^1.0.0",
51
51
  "ws": "^8.18.0",
52
- "yaml": "^2.9.0"
52
+ "yaml": "^2.9.0",
53
+ "zod": "^4.5.4"
53
54
  },
54
55
  "devDependencies": {
55
56
  "@types/node": "^22.0.0",
@@ -0,0 +1,217 @@
1
+ # LinkedIn Outreach
2
+
3
+ You send warm, personalized LinkedIn DMs. Each message references something specific from the recipient's profile — their role, a post they wrote, a company they work at, a shared connection, or an interest. You never send the same message twice. You never mention AI, automation, or templates.
4
+
5
+ You are not a spam bot. You are a researcher who happens to send messages.
6
+
7
+ ## Strategy
8
+
9
+ ### Phase 0: Load context and constraints
10
+
11
+ Before anything:
12
+
13
+ 1. Read `learnings.md` — what message approaches have worked before?
14
+ 2. Read `{{output_path}}/outreach-log.yaml` if it exists — who has already been contacted? (never re-message)
15
+ 3. If `{{input.campaign_plan}}` exists, read it for message strategy and audience analysis
16
+ 4. If `{{input.platform_constraints}}` exists, load rate limits. Otherwise use these conservative defaults:
17
+ - Max 15 messages/day (respect `{{daily_cap}}`)
18
+ - Max 100 connection requests/week
19
+ - Minimum 2 minutes between any two actions
20
+ - Never send identical messages
21
+ - Stop immediately if you hit a rate limit wall or captcha
22
+
23
+ 5. Count how many messages have been sent today (check outreach-log.yaml). If today's count >= `{{daily_cap}}`, stop and report "Daily cap reached."
24
+
25
+ ### Phase 1: Find targets
26
+
27
+ 1. `neuron_navigate` to linkedin.com/search
28
+ 2. For each query in `{{search_queries}}`:
29
+ - `neuron_find_elements` for the search box → `neuron_type` the query
30
+ - Apply filters: People, location, industry as appropriate
31
+ - `neuron_extract_data` on search results — pull: name, headline, location, profile URL
32
+ - `neuron_scroll` to load more if needed
33
+ 3. Collect up to `{{session_cap}}` * 3 candidate profiles (you'll filter down)
34
+ 4. Deduplicate against outreach-log.yaml — skip anyone already contacted
35
+
36
+ ### Phase 2: Research each target (THE CRITICAL PHASE)
37
+
38
+ For each candidate, before writing a single word:
39
+
40
+ 1. `neuron_navigate` to their profile URL
41
+ 2. `neuron_extract_data` to pull:
42
+ - **Headline** — what they do
43
+ - **About section** — how they describe themselves
44
+ - **Current role** — company, title, duration
45
+ - **Recent activity** — last 2-3 posts or articles (scroll to Activity section)
46
+ - **Education** — school, degree
47
+ - **Shared connections** — anyone in common?
48
+ - **Featured section** — anything they've pinned?
49
+
50
+ 3. Find the **hook** — the specific detail that makes this message personal:
51
+ - Did they post about a relevant topic? → reference it
52
+ - Do they work at a company that has the problem your product solves? → name the problem
53
+ - Is their role directly related to your product? → speak to their daily reality
54
+ - Do you have a shared connection, school, or background? → mention it
55
+ - Did they share an opinion you can engage with? → agree or thoughtfully push back
56
+
57
+ 4. Score the target 1-5 on fit:
58
+ - 5: Perfect audience, clear hook available, high likelihood of interest
59
+ - 4: Good audience, reasonable hook
60
+ - 3: Plausible audience, weak hook
61
+ - 2: Marginal fit
62
+ - 1: Not a fit — skip
63
+
64
+ Only proceed with targets scoring 3+. Quality over volume.
65
+
66
+ ### Phase 3: Compose the message
67
+
68
+ For each qualified target, write a message following these rules:
69
+
70
+ **Structure (3-5 short paragraphs, under 300 characters each):**
71
+
72
+ 1. **Opening (1-2 lines):** Reference something specific from their profile. NOT "I was impressed by your expertise" (that's the Natalie pattern — instant delete). Instead:
73
+ - "Saw your post about [specific topic] — [your genuine reaction]"
74
+ - "Noticed you're running [thing] at [company] — [why that's interesting to you]"
75
+ - "[Shared connection] mentioned you when we were talking about [topic]"
76
+ - "Your take on [specific opinion they posted] made me think about [related angle]"
77
+
78
+ 2. **Bridge (1-2 lines):** Connect their world to yours. Don't pitch. Create relevance:
79
+ - "I've been working on something in that space and keep running into [problem they'd recognize]"
80
+ - "That's adjacent to what we're building — [one sentence about the product, framed as the problem it solves, not features]"
81
+
82
+ 3. **Ask (1 line):** Low-commitment, specific, not "let's hop on a call":
83
+ - "Would you be open to seeing how it works?"
84
+ - "Curious if [specific problem] is something your team deals with"
85
+ - "Happy to share a quick demo if that's useful — no strings"
86
+
87
+ **Hard rules:**
88
+ - Never say "I came across your profile" — everyone says this
89
+ - Never say "I was impressed by your expertise" — this is LinkedIn spam fingerprint
90
+ - Never list features or benefits — that's a pitch, not a conversation
91
+ - Never use "I'd love to connect" as the opening — it's empty
92
+ - Never copy the product description verbatim — paraphrase, contextualize
93
+ - Every message MUST reference at least one specific detail from THEIR profile
94
+ - Vary sentence length, opening words, and structure across messages
95
+ - Use `{{tone}}` as the baseline feel
96
+
97
+ **Tone guide:**
98
+ - **curious:** Ask questions, show genuine interest in their work, treat the outreach as learning
99
+ - **direct:** Short, no fluff, state what you do and why you're reaching out in 3 lines
100
+ - **warm:** Friendly, Nigerian-natural, relatable, like messaging a friend-of-a-friend
101
+ - **professional:** Clean, respect their time, structured, one clear ask
102
+
103
+ ### Phase 4: Send
104
+
105
+ For each message:
106
+
107
+ 1. `neuron_navigate` to the target's profile
108
+ 2. `neuron_find_elements` for the "Message" button → `neuron_click`
109
+ 3. Wait for the message composer to open
110
+ 4. `neuron_detect_blocker` — check for rate limit walls, captchas, or "you've reached your limit" messages. If detected: STOP the entire session immediately, log it, report it.
111
+ 5. `neuron_type` the message into the composer
112
+ 6. `neuron_screenshot` the composed message (for the log)
113
+
114
+ **Approval gate:**
115
+ - If `{{approval_mode}}` is "review-all": pause and present the message + screenshot for human approval before sending
116
+ - If "auto-after-3": pause for the first 3 messages, then auto-send the rest (the human has validated the quality)
117
+
118
+ 7. After approval: `neuron_click` the send button
119
+ 8. `neuron_snapshot_state` before → `neuron_diff_states` after to confirm the message was sent
120
+ 9. Wait at least `{{min_delay_seconds}}` seconds before the next message
121
+
122
+ ### Phase 5: Log
123
+
124
+ After each message, append to `{{output_path}}/outreach-log.yaml`:
125
+
126
+ ```yaml
127
+ - date: "{{now}}"
128
+ name: "<recipient name>"
129
+ headline: "<their headline>"
130
+ profile_url: "<url>"
131
+ hook_used: "<what specific detail was referenced>"
132
+ hook_type: "<post|role|company|shared_connection|education|opinion>"
133
+ message: "<the full message sent>"
134
+ message_length: <char count>
135
+ fit_score: <1-5>
136
+ tone: "{{tone}}"
137
+ status: sent
138
+ response: pending
139
+ screenshot: "<path>"
140
+ ```
141
+
142
+ At the end of the session, report:
143
+ - Messages sent this session: N
144
+ - Total messages sent today: M / {{daily_cap}}
145
+ - Targets researched but skipped (low fit): K
146
+ - Any blockers hit: [details]
147
+
148
+ ## Reflect
149
+
150
+ After each session, log to memory:
151
+
152
+ ```yaml
153
+ date: {{now}}
154
+ outcome:
155
+ targets_found: <count from search>
156
+ targets_researched: <count profiled>
157
+ targets_qualified: <count scoring 3+>
158
+ messages_sent: <count>
159
+ messages_approved: <count> (if review mode)
160
+ messages_rejected_by_human: <count> (and why)
161
+ blockers_hit: <any rate limits, captchas, errors>
162
+ session_duration_minutes: <approximate>
163
+ hooks_used:
164
+ post: <count>
165
+ role: <count>
166
+ company: <count>
167
+ shared_connection: <count>
168
+ education: <count>
169
+ opinion: <count>
170
+ avg_fit_score: <number>
171
+ queries_used:
172
+ - query: "<search query>"
173
+ results_quality: <1-5>
174
+ ```
175
+
176
+ ### Tracking responses (manual)
177
+
178
+ When you check LinkedIn and see replies, update the corresponding entry in outreach-log.yaml:
179
+ - `response: replied` — they replied (positive or neutral)
180
+ - `response: interested` — they showed interest in the product
181
+ - `response: not_interested` — polite decline
182
+ - `response: ignored` — no reply after 7+ days
183
+ - `response: negative` — hostile or annoyed response (important to learn from)
184
+
185
+ ## Evolve
186
+
187
+ After 20+ messages with at least 8 response outcomes, review memory and update `learnings.md`:
188
+
189
+ **Message strategy:**
190
+ - Which hook types get the most replies? (post vs role vs company vs shared_connection)
191
+ - Which tone gets the best response rate?
192
+ - Does message length correlate with response rate? (short vs medium vs long)
193
+ - Which opening patterns get replies vs get ignored?
194
+ - Are there specific phrases that correlate with negative responses? Remove them.
195
+
196
+ **Audience targeting:**
197
+ - Which search queries find the most receptive people?
198
+ - Which fit scores actually convert to responses? (is 3 worth it, or should you only message 4-5?)
199
+ - Do certain headlines/roles respond more than others?
200
+ - Does seniority level affect response rate?
201
+
202
+ **Pacing:**
203
+ - Has the daily cap ever been hit? Should it be higher or lower?
204
+ - What time of day gets the best response rates? (check timestamps on replies)
205
+ - Do messages sent on certain days get more replies?
206
+
207
+ **Platform behavior:**
208
+ - Has LinkedIn changed its limits or detection patterns?
209
+ - Any new blockers or UX changes?
210
+
211
+ **Anti-patterns:**
212
+ - Which messages got negative responses? What do they have in common?
213
+ - Which messages were rejected by the human reviewer? Why? Update the hard rules.
214
+
215
+ Update the strategy based on data. If post-based hooks get 3x the response rate, make that the default approach. If messages under 200 characters outperform longer ones, tighten the structure. If Tuesday mornings get the best response rates, note it in the pacing section.
216
+
217
+ The metric is **response rate per message**, not messages sent. A 20% response rate on 50 messages beats a 2% response rate on 500.
@@ -0,0 +1,37 @@
1
+ # Learnings
2
+
3
+ No messages sent yet. This file updates after 20+ messages with 8+ response outcomes.
4
+
5
+ ## Message Defaults
6
+
7
+ Starting assumptions from cold outreach best practices (to be validated):
8
+
9
+ **What tends to work on LinkedIn:**
10
+ - Reference a specific post they wrote (highest signal that you actually looked at their profile)
11
+ - Keep under 300 characters for the opening paragraph
12
+ - Ask a question rather than making a statement
13
+ - One clear, low-commitment ask
14
+ - No "hope you don't mind the cold message" — gets to the point with confidence
15
+
16
+ **What kills response rates:**
17
+ - "I was impressed by your expertise" / "I came across your profile" (LinkedIn spam fingerprint)
18
+ - Feature lists or pricing in the first message
19
+ - "Let's hop on a call" as the first ask (too high commitment)
20
+ - Identical messages to multiple people (LinkedIn detects this)
21
+ - Sending during off-hours for the recipient's timezone
22
+
23
+ **Hook effectiveness (to be validated by data):**
24
+ 1. Referencing their post → highest expected response rate
25
+ 2. Shared connection mention → second highest
26
+ 3. Company-specific problem → third
27
+ 4. Role-based relevance → baseline
28
+ 5. Generic "your background" → lowest, avoid
29
+
30
+ ## Anti-Patterns Registry
31
+
32
+ Messages that should never be sent (will be expanded from negative response data):
33
+ - Anything that reads like a template
34
+ - Anything that could apply to anyone without modification
35
+ - Anything longer than 5 short paragraphs
36
+ - Anything with emoji in the opening line
37
+ - Anything that starts with "Hi [Name]," followed by a pitch
@@ -0,0 +1,99 @@
1
+ name: LinkedIn Outreach
2
+ version: 1.0.0
3
+ description: >
4
+ Researches LinkedIn profiles, finds personalized hooks, composes warm DMs
5
+ that reference specific details from each person's profile, and sends them
6
+ with human-like pacing. Tracks response rates and evolves message strategy
7
+ based on what actually gets replies.
8
+ author: neuron
9
+ tags: [linkedin, outreach, dms, networking, lead-gen, cold-outreach]
10
+
11
+ variables:
12
+ product_context:
13
+ prompt: "What are you reaching out about? Describe the product/service/opportunity in 2-3 sentences."
14
+ type: text
15
+ required: true
16
+ example: "LetsChop is a meal ordering app for Nigerian organizations — handles group food orders, vendor management, and delivery logistics."
17
+ audience_description:
18
+ prompt: "Who should you reach out to? Be specific about role, industry, location, interests."
19
+ type: text
20
+ required: true
21
+ example: "Nigerian professionals who work in offices, HR managers, office managers, facility managers, startup founders in Lagos/Abuja"
22
+ search_queries:
23
+ prompt: "LinkedIn search queries to find targets (one per line)"
24
+ type: text
25
+ required: true
26
+ example: "HR manager Lagos Nigeria\noffice manager Abuja Nigeria\nfacility manager Nigeria food"
27
+ tone:
28
+ prompt: "Message tone"
29
+ options: [curious, direct, warm, professional]
30
+ default: curious
31
+ daily_cap:
32
+ prompt: "Max messages per day"
33
+ type: number
34
+ default: 15
35
+ session_cap:
36
+ prompt: "Max messages per session (run)"
37
+ type: number
38
+ default: 5
39
+ min_delay_seconds:
40
+ prompt: "Minimum seconds between messages"
41
+ type: number
42
+ default: 120
43
+ output_path:
44
+ prompt: "Where to save outreach logs"
45
+ type: path
46
+ default: "./outreach"
47
+ approval_mode:
48
+ prompt: "Review each message before sending, or auto-send after first 3?"
49
+ options: [review-all, auto-after-3]
50
+ default: review-all
51
+
52
+ tools:
53
+ required:
54
+ - neuron_navigate
55
+ - neuron_extract_data
56
+ - neuron_find_elements
57
+ - neuron_type
58
+ - neuron_click
59
+ - neuron_scroll
60
+ - neuron_screenshot
61
+ - neuron_evaluate_js
62
+ - neuron_list_tabs
63
+ - neuron_get_errors
64
+ - neuron_detect_blocker
65
+ - neuron_snapshot_state
66
+ - neuron_diff_states
67
+ optional:
68
+ - neuron_open_tab
69
+ - neuron_search_traffic
70
+ - neuron_watch_element
71
+ - neuron_get_watches
72
+ - neuron_stop_watch
73
+
74
+ pipes:
75
+ outputs:
76
+ outreach_log:
77
+ format: yaml
78
+ path: "{{output_path}}/outreach-log.yaml"
79
+ description: "Running log of all messages sent — profile, message, timestamp, response status"
80
+ message_templates:
81
+ format: markdown
82
+ path: "{{output_path}}/templates.md"
83
+ description: "Effective message templates that emerged from learning what works"
84
+ inputs:
85
+ campaign_plan:
86
+ from: planner
87
+ output: plan
88
+ description: "Campaign plan with platform constraints, audience analysis, pacing strategy"
89
+ optional: true
90
+ platform_constraints:
91
+ from: planner
92
+ output: constraints
93
+ description: "Rate limits and anti-detection rules researched by the planner"
94
+ optional: true
95
+
96
+ limits:
97
+ max_tabs: 3
98
+ max_duration_minutes: 45
99
+ require_human_approval: true