@coppica/persuasion-mcp 1.0.2 → 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/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
  }
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.2",
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",