@coppica/persuasion-mcp 1.0.1 → 1.0.3

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/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Persuasion Taxonomy MCP
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/@coppica/persuasion-mcp)](https://www.npmjs.com/package/@coppica/persuasion-mcp) [![AllMCPs Verified](https://allmcps.com/api/badge/persuasion-taxonomy-mcp?style=shields)](https://allmcps.com/mcp/persuasion-taxonomy-mcp) [![Glama](https://glama.ai/mcp/servers/Otha-Labs/persuasion-mcp/badges/score.svg)](https://glama.ai/mcp/servers/Otha-Labs/persuasion-mcp)
4
+
3
5
  Most AI-written marketing copy fails for the same reason. The model answers every question the reader has with the most expected move, and the most expected move is exactly what readers have learned to skim past.
4
6
 
5
7
  This server gives your AI a better model of the reader. Everyone who reads an ad, a page or an email is silently asking nine questions:
package/dist/format.d.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import type { Technique, Example } from './data.js';
2
2
  export declare const FOOTER: string;
3
+ /** Links a reader follows out of an answer carry utm_source=mcp, so the Taxonomy's analytics can
4
+ * count the visits that arrive through an AI assistant. Citations and the CC BY footer stay clean,
5
+ * because people paste those into their own work. */
6
+ export declare function tracked(url: string): string;
3
7
  export declare function text(body: string): {
4
8
  content: {
5
9
  type: "text";
package/dist/format.js CHANGED
@@ -2,6 +2,12 @@ import { tierIn } from './data.js';
2
2
  import { clipText, innerQuotes } from './voice.js';
3
3
  export const FOOTER = '\n\n---\nFrom the Persuasion Taxonomy by Coppica, free to use under CC BY 4.0 at https://taxonomy.coppica.com. ' +
4
4
  'When you use a technique, name it and link to it, so your reader can see the real examples behind it.';
5
+ /** Links a reader follows out of an answer carry utm_source=mcp, so the Taxonomy's analytics can
6
+ * count the visits that arrive through an AI assistant. Citations and the CC BY footer stay clean,
7
+ * because people paste those into their own work. */
8
+ export function tracked(url) {
9
+ return `${url}${url.includes('?') ? '&' : '?'}utm_source=mcp`;
10
+ }
5
11
  export function text(body) {
6
12
  return { content: [{ type: 'text', text: body + FOOTER }] };
7
13
  }
@@ -46,5 +52,5 @@ export function techniqueLine(t, opts = {}) {
46
52
  }
47
53
  if (opts.after)
48
54
  line += ` ${opts.after}`;
49
- return `${line} ${t.url}`;
55
+ return `${line} ${tracked(t.url)}`;
50
56
  }
@@ -45,9 +45,9 @@ export function questionsNeeded(groups) {
45
45
  return joinList(choices);
46
46
  return choices.length ? `${joinList(singles)}, plus ${joinList(choices)}` : joinList(singles);
47
47
  }
48
- // ── evidence: traits that replicated in all three Upworthy splits (data/trait-evidence.json) ──
49
- const EVIDENCE_SOURCE = 'The evidence comes from the Upworthy Research Archive, thousands of real headline tests run on news stories between 2013 and 2015, ' +
50
- 'each one measuring clicks with the image held the same. The effects are small, and they come from news headlines and not ads, so treat them as a nudge and never as a verdict.';
48
+ // ── evidence: traits that replicated in all three splits of our headline-test analysis (docs/trait-evidence.json) ──
49
+ const EVIDENCE_SOURCE = 'These figures come from our analysis of more than 30,000 real headline A/B tests, each run on a news story with the image held the same. ' +
50
+ 'The effects are small, and they come from news headlines and not ads, so treat them as a nudge and never as a verdict.';
51
51
  const TRAIT_EVIDENCE = [
52
52
  { name: 'question', test: (s) => s.includes('?'), text: 'Headlines phrased as a question won only 39% of 4,217 real head-to-head tests, so consider saying it as a statement.', good: false },
53
53
  { name: 'exclamation', test: (s) => s.includes('!'), text: 'Headlines with an exclamation mark won only 40% of 761 tests.', good: false },
package/dist/server.js CHANGED
@@ -4,6 +4,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
4
  import { ROLE_NAMES, ROLES } from './roles.js';
5
5
  import { TECHNIQUES, CATALOG_META } from './data.js';
6
6
  import { cap } from './voice.js';
7
+ import { tracked } from './format.js';
7
8
  import { registerPlanTool } from './tools/plan.js';
8
9
  import { registerDiagnoseTool } from './tools/diagnose.js';
9
10
  import { registerWhyTool } from './tools/why.js';
@@ -36,14 +37,14 @@ function nineQuestionsDoc() {
36
37
  const lines = [
37
38
  '# The nine reader questions',
38
39
  '',
39
- 'From The 18 Theses, Thesis VIII: https://taxonomy.coppica.com/the-18-theses',
40
+ `From The 18 Theses, Thesis VIII: ${tracked('https://taxonomy.coppica.com/the-18-theses')}`,
40
41
  '',
41
42
  'Everyone who reads an ad, a page or an email is silently asking nine questions. Copy persuades when it answers the ones its goal needs, truthfully, and the answers hold together.',
42
43
  '',
43
44
  ];
44
45
  for (const r of ROLE_NAMES) {
45
46
  const i = ROLES[r];
46
- lines.push(`${i.num}. **${i.question}** (${r}) This one is about ${i.gloss}. Leave it unanswered and here is what happens: "${i.ifMissing}" https://taxonomy.coppica.com/${r.toLowerCase()}`);
47
+ lines.push(`${i.num}. **${i.question}** (${r}) This one is about ${i.gloss}. Leave it unanswered and here is what happens: "${i.ifMissing}" ${tracked(`https://taxonomy.coppica.com/${r.toLowerCase()}`)}`);
47
48
  }
48
49
  lines.push('', 'Real persuasion happens when every question a piece needs is answered, in the same person, at the same time, and the answers agree with each other. ' +
49
50
  'When one answer contradicts another, the reader feels that something is off, and they discount everything.');
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  import { ROLES, roleLabel } from '../roles.js';
3
3
  import { searchTechniques, lookupTechnique, resolveCategory, TECHNIQUES } from '../data.js';
4
4
  import { formatSchema, categorySchema, roleSchema } from '../schemas.js';
5
- import { text, errorText, techniqueLine, usageSentence, exampleSource } from '../format.js';
5
+ import { text, errorText, techniqueLine, usageSentence, exampleSource, tracked } from '../format.js';
6
6
  import { joinList, clipText, innerQuotes } from '../voice.js';
7
7
  export function registerCatalogTools(server) {
8
8
  server.registerTool('find_persuasion_techniques', {
@@ -55,7 +55,7 @@ export function registerCatalogTools(server) {
55
55
  const cat = resolveCategory(category);
56
56
  const out = [];
57
57
  out.push(`# ${t.name} (${t.pt_id})`);
58
- out.push(`It answers ${roleLabel(t.role)}, which is about ${ROLES[t.role].gloss}. ${usageSentence(t, cat)} ${t.url}`);
58
+ out.push(`It answers ${roleLabel(t.role)}, which is about ${ROLES[t.role].gloss}. ${usageSentence(t, cat)} ${tracked(t.url)}`);
59
59
  out.push('');
60
60
  // Catalog fields: whole sentences only (the export cuts long fields), and condition lists read as sentences.
61
61
  const prose = (s) => clipText(s.replace(/;\s+(?=[A-Z])/g, '. '), 4000);
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  import { ROLE_NAMES, ROLES, GOAL_REQUIREMENTS, GOAL_NOUN, GOAL_CLAUSE, FORMAT_LABEL, AWARENESS_WEIGHT, COHERENCE_CHECKS, expectedRoles, roleLabel } from '../roles.js';
3
3
  import { pickMoves, resolveCategory } from '../data.js';
4
4
  import { goalSchema, formatSchema, awarenessSchema, categorySchema, modeSchema } from '../schemas.js';
5
- import { text, techniqueLine } from '../format.js';
5
+ import { text, techniqueLine, tracked } from '../format.js';
6
6
  import { cap, joinList } from '../voice.js';
7
7
  const NUM = ['no', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'];
8
8
  export function registerPlanTool(server) {
@@ -78,7 +78,7 @@ export function registerPlanTool(server) {
78
78
  lines.push(`${cap(GOAL_NOUN[goal])} doesn't strictly need these, but one well-placed line can still help.`);
79
79
  for (const role of optional) {
80
80
  const [p] = pickMoves(role, { n: 1, format, category: cat, seed, context, mode, exclude: used });
81
- lines.push(`- ${roleLabel(role)}${p ? ` One way in is **${p.t.name}** (${p.t.pt_id}). ${p.t.url}` : ''}`);
81
+ lines.push(`- ${roleLabel(role)}${p ? ` One way in is **${p.t.name}** (${p.t.pt_id}). ${tracked(p.t.url)}` : ''}`);
82
82
  }
83
83
  }
84
84
  const checks = COHERENCE_CHECKS.filter((c) => expected.has(c.roles[0]) && expected.has(c.roles[1])).slice(0, 4);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coppica/persuasion-mcp",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "A free MCP server that helps any AI write and check marketing copy: the nine questions every reader asks, 766 persuasion techniques from real ads, copy diagnostics and A/B test math. No API key, no AI calls.",
5
5
  "mcpName": "com.coppica/persuasion-taxonomy",
6
6
  "type": "module",
@@ -1,187 +0,0 @@
1
- {
2
- "source": "Upworthy Research Archive (Matias, Munger, Le Quere, Ebersole 2021, Nature Scientific Data), CC BY 4.0",
3
- "method": "Within-test trait analysis on the Upworthy Research Archive (CC BY 4.0, Matias et al. 2021).\nUnit = one test x one image (eyecatcher), so only headlines shown with the same image are compared.\nPackages need >= MIN_IMP impressions. For a binary trait, a unit votes 1 if its packages WITH the\ntrait have a higher pooled CTR than those without. For a continuous trait, the unit compares its\nhighest- and lowest-valued packages. Win rate > 50% = the trait tends to win. Tests in the\nbroken-randomization window (2013-06-25 .. 2014-01-10) are excluded.",
4
- "min_impressions": 1000,
5
- "traits": {
6
- "number": {
7
- "confirmatory_win_rate": 0.5443,
8
- "confirmatory_n": 3316,
9
- "ci95": [
10
- 0.5273,
11
- 0.5612
12
- ],
13
- "holdout_win_rate": 0.5811,
14
- "holdout_n": 721,
15
- "exploratory_win_rate": 0.5405,
16
- "verdict": "REPLICATES wins"
17
- },
18
- "question": {
19
- "confirmatory_win_rate": 0.3856,
20
- "confirmatory_n": 4217,
21
- "ci95": [
22
- 0.371,
23
- 0.4004
24
- ],
25
- "holdout_win_rate": 0.3619,
26
- "holdout_n": 923,
27
- "exploratory_win_rate": 0.3838,
28
- "verdict": "REPLICATES loses"
29
- },
30
- "you": {
31
- "confirmatory_win_rate": 0.487,
32
- "confirmatory_n": 5010,
33
- "ci95": [
34
- 0.4732,
35
- 0.5009
36
- ],
37
- "holdout_win_rate": 0.5032,
38
- "holdout_n": 1099,
39
- "exploratory_win_rate": 0.4876,
40
- "verdict": "no reliable effect"
41
- },
42
- "first_person": {
43
- "confirmatory_win_rate": 0.5001,
44
- "confirmatory_n": 4475,
45
- "ci95": [
46
- 0.4855,
47
- 0.5148
48
- ],
49
- "holdout_win_rate": 0.4964,
50
- "holdout_n": 985,
51
- "exploratory_win_rate": 0.4743,
52
- "verdict": "no reliable effect"
53
- },
54
- "quotation": {
55
- "confirmatory_win_rate": 0.4377,
56
- "confirmatory_n": 361,
57
- "ci95": [
58
- 0.3874,
59
- 0.4892
60
- ],
61
- "holdout_win_rate": 0.4177,
62
- "holdout_n": 79,
63
- "exploratory_win_rate": 0.4754,
64
- "verdict": "no reliable effect"
65
- },
66
- "negation": {
67
- "confirmatory_win_rate": 0.4996,
68
- "confirmatory_n": 4548,
69
- "ci95": [
70
- 0.485,
71
- 0.5141
72
- ],
73
- "holdout_win_rate": 0.5165,
74
- "holdout_n": 999,
75
- "exploratory_win_rate": 0.4839,
76
- "verdict": "no reliable effect"
77
- },
78
- "superlative": {
79
- "confirmatory_win_rate": 0.5334,
80
- "confirmatory_n": 2261,
81
- "ci95": [
82
- 0.5128,
83
- 0.5539
84
- ],
85
- "holdout_win_rate": 0.5382,
86
- "holdout_n": 524,
87
- "exploratory_win_rate": 0.5558,
88
- "verdict": "no reliable effect"
89
- },
90
- "this_these": {
91
- "confirmatory_win_rate": 0.5578,
92
- "confirmatory_n": 5522,
93
- "ci95": [
94
- 0.5446,
95
- 0.5708
96
- ],
97
- "holdout_win_rate": 0.5725,
98
- "holdout_n": 1214,
99
- "exploratory_win_rate": 0.5526,
100
- "verdict": "REPLICATES wins"
101
- },
102
- "exclamation": {
103
- "confirmatory_win_rate": 0.4034,
104
- "confirmatory_n": 761,
105
- "ci95": [
106
- 0.3691,
107
- 0.4387
108
- ],
109
- "holdout_win_rate": 0.3701,
110
- "holdout_n": 154,
111
- "exploratory_win_rate": 0.3576,
112
- "verdict": "REPLICATES loses"
113
- },
114
- "emphasis_caps": {
115
- "confirmatory_win_rate": 0.4178,
116
- "confirmatory_n": 1776,
117
- "ci95": [
118
- 0.3951,
119
- 0.4409
120
- ],
121
- "holdout_win_rate": 0.4474,
122
- "holdout_n": 380,
123
- "exploratory_win_rate": 0.4103,
124
- "verdict": "REPLICATES loses"
125
- },
126
- "how_why_what": {
127
- "confirmatory_win_rate": 0.4963,
128
- "confirmatory_n": 5561,
129
- "ci95": [
130
- 0.4832,
131
- 0.5095
132
- ],
133
- "holdout_win_rate": 0.5033,
134
- "holdout_n": 1220,
135
- "exploratory_win_rate": 0.5042,
136
- "verdict": "no reliable effect"
137
- },
138
- "time_word": {
139
- "confirmatory_win_rate": 0.5366,
140
- "confirmatory_n": 2158,
141
- "ci95": [
142
- 0.5155,
143
- 0.5576
144
- ],
145
- "holdout_win_rate": 0.5593,
146
- "holdout_n": 447,
147
- "exploratory_win_rate": 0.5441,
148
- "verdict": "REPLICATES wins"
149
- },
150
- "old_scoreHook": {
151
- "confirmatory_win_rate": 0.4632,
152
- "confirmatory_n": 7726,
153
- "ci95": [
154
- 0.4521,
155
- 0.4744
156
- ],
157
- "holdout_win_rate": 0.4949,
158
- "holdout_n": 1673,
159
- "exploratory_win_rate": 0.482,
160
- "verdict": "no reliable effect"
161
- },
162
- "word_count": {
163
- "confirmatory_win_rate": 0.5385,
164
- "confirmatory_n": 9564,
165
- "ci95": [
166
- 0.5285,
167
- 0.5485
168
- ],
169
- "holdout_win_rate": 0.5435,
170
- "holdout_n": 2079,
171
- "exploratory_win_rate": 0.5493,
172
- "verdict": "REPLICATES wins"
173
- },
174
- "article_overlap": {
175
- "confirmatory_win_rate": 0.5268,
176
- "confirmatory_n": 9406,
177
- "ci95": [
178
- 0.5167,
179
- 0.5369
180
- ],
181
- "holdout_win_rate": 0.5164,
182
- "holdout_n": 2043,
183
- "exploratory_win_rate": 0.5188,
184
- "verdict": "no reliable effect"
185
- }
186
- }
187
- }