metaphore-mcp 1.0.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.
Files changed (3) hide show
  1. package/README.md +77 -0
  2. package/index.js +151 -0
  3. package/package.json +32 -0
package/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # METAPHORE MCP Server
2
+
3
+ **Turn any technical concept into a clear human story — right inside Claude Code.**
4
+
5
+ [![License: CC BY-NC-SA 4.0](https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by-nc-sa/4.0/)
6
+
7
+ ## Setup
8
+
9
+ ### 1. Get your METAPHORE key
10
+
11
+ Sign up at [metaphore.app](https://metaphore.app) → Account → copy your `mkey`.
12
+
13
+ ### 2. Add to Claude Code
14
+
15
+ Run:
16
+
17
+ ```bash
18
+ claude mcp add metaphore -e METAPHORE_KEY=mkey_xxxxx -- npx -y metaphore-mcp
19
+ ```
20
+
21
+ Or manually add to your `.claude/mcp.json`:
22
+
23
+ ```json
24
+ {
25
+ "mcpServers": {
26
+ "metaphore": {
27
+ "command": "npx",
28
+ "args": ["-y", "metaphore-mcp"],
29
+ "env": {
30
+ "METAPHORE_KEY": "mkey_xxxxx"
31
+ }
32
+ }
33
+ }
34
+ }
35
+ ```
36
+
37
+ Replace `mkey_xxxxx` with your actual key.
38
+
39
+ ### 3. Use it
40
+
41
+ In Claude Code, ask it to use the metaphore tool:
42
+
43
+ ```
44
+ Use metaphore to explain what a reverse proxy is
45
+ ```
46
+
47
+ Or with parameters:
48
+
49
+ ```
50
+ Use metaphore to explain our microservices architecture --audience ceo --depth short
51
+ ```
52
+
53
+ ## Parameters
54
+
55
+ | Parameter | Values | Description |
56
+ |-----------|--------|-------------|
57
+ | `input` | any text | **Required.** The technical concept to transform |
58
+ | `audience` | `ceo`, `junior-dev`, `12-year-old`, `founder`, `operator`, `salesperson` | Who the explanation is for |
59
+ | `style` | `building`, `restaurant`, `postal`, `hospital`, `workshop`, `theater` | Analogy domain |
60
+ | `depth` | `short`, `full` (default) | One paragraph vs. full 3-act story |
61
+ | `objective` | `understand`, `decide`, `debug`, `explain-to-someone`, `learn` | What the reader needs to do next |
62
+ | `language` | Any language code (`fr`, `es`, `de`, ...) | Override output language |
63
+
64
+ ## Pricing
65
+
66
+ Your METAPHORE key includes **300 free IDE transforms/month**. Upgrade to Pro ($5/month) for unlimited access at [metaphore.app](https://metaphore.app).
67
+
68
+ ## Requirements
69
+
70
+ - Node.js 18+
71
+ - A METAPHORE key ([get one free](https://metaphore.app))
72
+
73
+ ## License
74
+
75
+ [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)
76
+
77
+ Created by [Philippe Nerette](https://metaphore.app).
package/index.js ADDED
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { z } from "zod";
6
+
7
+ const API_URL = process.env.METAPHORE_API_URL || "https://api.metaphore.app";
8
+ const METAPHORE_KEY = process.env.METAPHORE_KEY;
9
+
10
+ if (!METAPHORE_KEY) {
11
+ process.stderr.write(
12
+ "Error: METAPHORE_KEY environment variable is required.\n" +
13
+ "Get your key at https://metaphore.app → Account → METAPHORE Key (mkey)\n"
14
+ );
15
+ process.exit(1);
16
+ }
17
+
18
+ const server = new McpServer({
19
+ name: "metaphore",
20
+ version: "1.0.0",
21
+ });
22
+
23
+ server.registerTool(
24
+ "metaphore",
25
+ {
26
+ title: "METAPHORE",
27
+ description:
28
+ "Turn any technical concept, architecture, incident, data flow, or LLM output into a clear human story. " +
29
+ "Returns a structured narrative with hero, conflict, resolution, reality mapping, and moral.",
30
+ inputSchema: {
31
+ input: z
32
+ .string()
33
+ .describe(
34
+ "The technical concept or input to transform into a human story"
35
+ ),
36
+ audience: z
37
+ .enum([
38
+ "ceo",
39
+ "junior-dev",
40
+ "12-year-old",
41
+ "founder",
42
+ "operator",
43
+ "salesperson",
44
+ ])
45
+ .optional()
46
+ .describe("Who the explanation is for"),
47
+ style: z
48
+ .enum([
49
+ "building",
50
+ "restaurant",
51
+ "postal",
52
+ "hospital",
53
+ "workshop",
54
+ "theater",
55
+ ])
56
+ .optional()
57
+ .describe("Analogy domain / narrative world"),
58
+ depth: z
59
+ .enum(["short", "full"])
60
+ .optional()
61
+ .describe("Short (1 paragraph) or full (3-act story). Default: full"),
62
+ objective: z
63
+ .enum(["understand", "decide", "debug", "explain-to-someone", "learn"])
64
+ .optional()
65
+ .describe("What the reader needs to do next"),
66
+ language: z
67
+ .string()
68
+ .optional()
69
+ .describe(
70
+ "Output language code (e.g. fr, es, de). Auto-detected from input by default"
71
+ ),
72
+ },
73
+ },
74
+ async ({ input, audience, style, depth, objective, language }) => {
75
+ const body = {
76
+ input,
77
+ mkey: METAPHORE_KEY,
78
+ channel: "ide",
79
+ };
80
+
81
+ if (audience) body.audience = audience;
82
+ if (style) body.style = style;
83
+ if (depth) body.depth = depth;
84
+ if (objective) body.objective = objective;
85
+ if (language) body.language = language;
86
+
87
+ let response;
88
+ try {
89
+ response = await fetch(`${API_URL}/transform`, {
90
+ method: "POST",
91
+ headers: { "Content-Type": "application/json" },
92
+ body: JSON.stringify(body),
93
+ });
94
+ } catch (err) {
95
+ return {
96
+ content: [
97
+ {
98
+ type: "text",
99
+ text: `Connection error: could not reach ${API_URL}. Check your network.`,
100
+ },
101
+ ],
102
+ isError: true,
103
+ };
104
+ }
105
+
106
+ if (!response.ok) {
107
+ const status = response.status;
108
+ let message;
109
+ try {
110
+ const data = await response.json();
111
+ message = data.error || data.message || response.statusText;
112
+ } catch {
113
+ message = response.statusText;
114
+ }
115
+
116
+ if (status === 401) {
117
+ message = `Invalid METAPHORE_KEY. Check your key at https://metaphore.app → Account.`;
118
+ } else if (status === 403) {
119
+ message = `Quota exceeded. Upgrade to Pro at https://metaphore.app or add your own LLM key.`;
120
+ }
121
+
122
+ return {
123
+ content: [{ type: "text", text: `Error ${status}: ${message}` }],
124
+ isError: true,
125
+ };
126
+ }
127
+
128
+ const data = await response.json();
129
+
130
+ const meta = [];
131
+ if (data.model) meta.push(`Model: ${data.model}`);
132
+ if (data.provider) meta.push(`Provider: ${data.provider}`);
133
+ if (data.usage) {
134
+ const { inputTokens, outputTokens } = data.usage;
135
+ if (inputTokens || outputTokens) {
136
+ meta.push(`Tokens: ${inputTokens || 0} in / ${outputTokens || 0} out`);
137
+ }
138
+ }
139
+
140
+ const text = meta.length
141
+ ? `${data.result}\n\n---\n_${meta.join(" · ")}_`
142
+ : data.result;
143
+
144
+ return {
145
+ content: [{ type: "text", text }],
146
+ };
147
+ }
148
+ );
149
+
150
+ const transport = new StdioServerTransport();
151
+ await server.connect(transport);
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "metaphore-mcp",
3
+ "version": "1.0.0",
4
+ "description": "METAPHORE MCP server — turn any technical concept into a clear human story, right inside Claude Code",
5
+ "type": "module",
6
+ "bin": {
7
+ "metaphore-mcp": "index.js"
8
+ },
9
+ "main": "index.js",
10
+ "license": "CC-BY-NC-SA-4.0",
11
+ "author": "Philippe Nerette",
12
+ "keywords": [
13
+ "metaphore",
14
+ "mcp",
15
+ "claude",
16
+ "model-context-protocol",
17
+ "technical-writing",
18
+ "analogy",
19
+ "explanation"
20
+ ],
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/metaphore-app/metaphore-mcp"
24
+ },
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
28
+ "dependencies": {
29
+ "@modelcontextprotocol/sdk": "^1.12.1",
30
+ "zod": "^3.25.0"
31
+ }
32
+ }