snapy-mcp 1.0.1 → 1.1.2

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 +26 -48
  2. package/index.js +85 -75
  3. package/package.json +39 -33
package/README.md CHANGED
@@ -1,14 +1,24 @@
1
1
  # snapy-mcp
2
2
 
3
- Give an AI assistant somewhere to put what it builds. This is an [MCP](https://modelcontextprotocol.io) server that adds one `publish` tool: the assistant sends content, snapy hosts it, and a live link comes back in the conversation.
3
+ An MCP server that gives an AI assistant (Claude and other MCP clients) a `publish` tool. The assistant can publish text or HTML to Snapy.host and get back a shareable link.
4
4
 
5
- Useful because assistants are good at writing a page and bad at putting it anywhere. Instead of a wall of HTML in the chat, you get a URL you can open on your phone or send to someone.
5
+ ## Breaking change in 1.1.0: an API key is required
6
6
 
7
- ## Install
7
+ **Versions before 1.1.0 no longer work.** They publish without a credential, and the
8
+ Snapy.host publish API no longer accepts that — for any file type.
8
9
 
9
- Add it to your MCP client's config. You need Node 18 or newer on the machine running the client.
10
+ Publishing anonymously was a contradiction in the product: the website has always made
11
+ you verify an email address before your link goes live, while the API did not. It has
12
+ been closed.
10
13
 
11
- **Claude Desktop** — `claude_desktop_config.json`:
14
+ **The key is free.** Create one at [snapy.host](https://snapy.host/dashboard) under
15
+ **API keys** with a verified email address. There is no paid plan behind it — what a free
16
+ account can publish is bounded by the one-live-link allowance, exactly as it is on the
17
+ website.
18
+
19
+ ## Use it
20
+
21
+ Add this to your MCP client config (for example Claude Desktop), with your key:
12
22
 
13
23
  ```json
14
24
  {
@@ -16,57 +26,25 @@ Add it to your MCP client's config. You need Node 18 or newer on the machine run
16
26
  "snapy": {
17
27
  "command": "npx",
18
28
  "args": ["-y", "snapy-mcp"],
19
- "env": { "SNAPY_API_KEY": "snapy_live_your_key_here" }
29
+ "env": { "SNAPY_API_KEY": "your key here" }
20
30
  }
21
31
  }
22
32
  }
23
33
  ```
24
34
 
25
- **Claude Code**:
35
+ Or run it directly from this folder:
26
36
 
27
- ```sh
28
- claude mcp add snapy --env SNAPY_API_KEY=snapy_live_your_key_here -- npx -y snapy-mcp
37
+ ```
38
+ npm install
39
+ node index.js
29
40
  ```
30
41
 
31
- **Cursor** — `.cursor/mcp.json`, same shape as the Claude Desktop block above.
32
-
33
- Restart the client and the `publish` tool appears.
34
-
35
- ## The API key
36
-
37
- Publishing a **web page** needs a key. Create one at [snapy.host](https://snapy.host) under **API keys** in your dashboard, then pass it as `SNAPY_API_KEY`.
38
-
39
- Plain text formats — `.txt`, `.md`, `.csv`, `.json` — publish with no key and no account, so you can try it without signing up and add the key when you want to host a page.
40
-
41
- The reason for the difference is not arbitrary: a web page anyone could host anonymously is the format phishing uses, so pages are tied to an account that can be held responsible. Every published link is served with `noindex`, so nothing you publish turns up in search results.
42
-
43
- ## The tool
44
-
45
- **`publish`**
46
-
47
- | argument | required | what it does |
48
- |:---|:---|:---|
49
- | `content` | yes | the text or HTML to publish |
50
- | `filename` | no | defaults to `index.html`; the extension decides how it is served |
51
- | `name` | no | the subdomain you want, e.g. `my-page` → `my-page.snapy.page` |
52
-
53
- Returns the live link and a private analytics link. Ask for a name and you get it if it is free; leave it out and you get a random one.
54
-
55
- ## What you get back
56
-
57
- A link on `*.snapy.page` that opens in any browser, plus a private page showing views. Links persist. You can add a password, an expiry or view tracking afterwards from the dashboard.
58
-
59
- ## Configuration
60
-
61
- | variable | default | purpose |
62
- |:---|:---|:---|
63
- | `SNAPY_API_KEY` | none | required to publish a web page |
64
- | `SNAPY_API` | `https://api.snapy.host` | override the API host |
42
+ The tool calls the Snapy.host publish API at `https://api.snapy.host`. Set `SNAPY_API` to
43
+ override the host, and `SNAPY_API_KEY` to supply your key (required).
65
44
 
66
- ## Notes
45
+ Without a key the `publish` tool returns an error telling you to create one, rather than
46
+ passing a bare `401` back to the assistant.
67
47
 
68
- - Keep the key server-side. Anyone who has it can publish to your account. Revoke it in the dashboard if it leaks.
69
- - Don't publish secrets: anyone with the link can open the page.
70
- - Binary files are not handled here. The [HTTP API](https://snapy.host/developers) takes base64 for that.
48
+ ## Tool
71
49
 
72
- Docs: [snapy.host/integrations/mcp](https://snapy.host/integrations/mcp) · [snapy.host/developers](https://snapy.host/developers)
50
+ - **publish** — args: `content` (required text/HTML), `name` (optional link name), `filename` (optional, default index.html). Returns the live link and a private analytics link.
package/index.js CHANGED
@@ -1,75 +1,85 @@
1
- #!/usr/bin/env node
2
- // snapy MCP server: gives an assistant (Claude, etc.) a "publish" tool that uploads
3
- // text or HTML to snapy and returns a shareable link. Talks to the public publish API.
4
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
- import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
7
-
8
- const API = process.env.SNAPY_API || "https://api.snapy.host";
9
-
10
- // Publishing a web page needs an API key; plain text formats do not. Left optional
11
- // so the connector still works with no setup at all, and gains page publishing the
12
- // moment a key is present. Create one in the Snapy dashboard under API keys.
13
- const API_KEY = process.env.SNAPY_API_KEY || "";
14
-
15
- const server = new Server(
16
- { name: "snapy", version: "1.0.0" },
17
- { capabilities: { tools: {} } }
18
- );
19
-
20
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
21
- tools: [
22
- {
23
- name: "publish",
24
- description:
25
- "Publish text or HTML content to snapy and get back a shareable link that opens in any browser. Use this to host a page or file you created and return the link.",
26
- inputSchema: {
27
- type: "object",
28
- properties: {
29
- content: { type: "string", description: "The text or HTML to publish." },
30
- name: { type: "string", description: "Optional custom link name (the subdomain)." },
31
- filename: { type: "string", description: "Optional file name, defaults to index.html." },
32
- },
33
- required: ["content"],
34
- },
35
- },
36
- ],
37
- }));
38
-
39
- server.setRequestHandler(CallToolRequestSchema, async (req) => {
40
- if (req.params.name !== "publish") {
41
- return { content: [{ type: "text", text: "Unknown tool" }], isError: true };
42
- }
43
- const { content, name, filename } = req.params.arguments || {};
44
- try {
45
- const headers = { "content-type": "application/json" };
46
- if (API_KEY) headers.authorization = `Bearer ${API_KEY}`;
47
- const res = await fetch(`${API}/api/publish`, {
48
- method: "POST",
49
- headers,
50
- body: JSON.stringify({ content, name, filename }),
51
- });
52
- const data = await res.json();
53
- if (!res.ok) {
54
- // The most likely failure by far: publishing a page with no key set. The API's
55
- // own message is clearer than anything generic, so pass it through and add the
56
- // one instruction it cannot know, which is where the key comes from.
57
- const hint = !API_KEY && (res.status === 401 || res.status === 403)
58
- ? " Set SNAPY_API_KEY to publish a web page: create a key in your Snapy dashboard under API keys."
59
- : "";
60
- return {
61
- content: [{ type: "text", text: "snapy error: " + (data.error || res.status) + hint }],
62
- isError: true,
63
- };
64
- }
65
- return {
66
- content: [{ type: "text", text: `Published. Link: ${data.url}\nPrivate analytics: ${data.stats_url}` }],
67
- };
68
- } catch (e) {
69
- return { content: [{ type: "text", text: "Request failed: " + e.message }], isError: true };
70
- }
71
- });
72
-
73
- const transport = new StdioServerTransport();
74
- await server.connect(transport);
75
- console.error("snapy MCP server running on stdio");
1
+ #!/usr/bin/env node
2
+ // snapy MCP server: gives an assistant (Claude, etc.) a "publish" tool that uploads
3
+ // text or HTML to Snapy.host and returns a shareable link. Talks to the public publish API.
4
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
7
+
8
+ const API = process.env.SNAPY_API || "https://api.snapy.host";
9
+
10
+ // ⛔ A KEY IS NOW REQUIRED. /api/publish used to accept an anonymous call for anything
11
+ // that was not a web page, which is why earlier versions of this server sent no
12
+ // credential at all. Publishing anonymously is the contradiction payment processors have
13
+ // twice cited against Snapy -- the website already makes people verify an email -- so the
14
+ // endpoint now wants a verified account for every file type.
15
+ //
16
+ // THE KEY IS FREE. Create one at snapy.host under Dashboard -> API keys with a verified
17
+ // email; there is no paid plan behind it. What a free account can do is bounded by the
18
+ // one-live-link limit, exactly as it is on the website.
19
+ //
20
+ // ⛔ ANY INSTALL OLDER THAN 1.1.0 STOPS WORKING, and there is no version of this change
21
+ // that spares them: they send no credential, and the endpoint no longer accepts that.
22
+ // Hence the clear message below rather than whatever the API happens to return.
23
+ const KEY = process.env.SNAPY_API_KEY || "";
24
+
25
+ const server = new Server(
26
+ { name: "snapy", title: "Snapy.host", version: "1.1.1",
27
+ websiteUrl: "https://snapy.host" },
28
+ { capabilities: { tools: {} } }
29
+ );
30
+
31
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
32
+ tools: [
33
+ {
34
+ name: "publish",
35
+ description:
36
+ "Publish text or HTML content to Snapy.host and get back a shareable link that opens in any browser. Use this to host a page or file you created and return the link.",
37
+ inputSchema: {
38
+ type: "object",
39
+ properties: {
40
+ content: { type: "string", description: "The text or HTML to publish." },
41
+ name: { type: "string", description: "Optional custom link name (the subdomain)." },
42
+ filename: { type: "string", description: "Optional file name, defaults to index.html." },
43
+ },
44
+ required: ["content"],
45
+ },
46
+ },
47
+ ],
48
+ }));
49
+
50
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
51
+ if (req.params.name !== "publish") {
52
+ return { content: [{ type: "text", text: "Unknown tool" }], isError: true };
53
+ }
54
+ const { content, name, filename } = req.params.arguments || {};
55
+ if (!KEY) {
56
+ return {
57
+ content: [{ type: "text", text:
58
+ "Snapy.host: set SNAPY_API_KEY before publishing. Create a free key at " +
59
+ "https://snapy.host/dashboard (API keys) with a verified email address, then put " +
60
+ "it in this server's env as SNAPY_API_KEY. Publishing without an account is no " +
61
+ "longer possible." }],
62
+ isError: true,
63
+ };
64
+ }
65
+ try {
66
+ const res = await fetch(`${API}/api/publish`, {
67
+ method: "POST",
68
+ headers: { "content-type": "application/json", authorization: `Bearer ${KEY}` },
69
+ body: JSON.stringify({ content, name, filename }),
70
+ });
71
+ const data = await res.json();
72
+ if (!res.ok) {
73
+ return { content: [{ type: "text", text: "Snapy.host error: " + (data.error || res.status) }], isError: true };
74
+ }
75
+ return {
76
+ content: [{ type: "text", text: `Published. Link: ${data.url}\nPrivate analytics: ${data.stats_url}` }],
77
+ };
78
+ } catch (e) {
79
+ return { content: [{ type: "text", text: "Request failed: " + e.message }], isError: true };
80
+ }
81
+ });
82
+
83
+ const transport = new StdioServerTransport();
84
+ await server.connect(transport);
85
+ console.error("Snapy.host MCP server running on stdio");
package/package.json CHANGED
@@ -1,33 +1,39 @@
1
- {
2
- "name": "snapy-mcp",
3
- "version": "1.0.1",
4
- "description": "MCP server that lets Claude, Cursor or any AI assistant publish an HTML page or text file and get back a live shareable link.",
5
- "homepage": "https://snapy.host/integrations/mcp",
6
- "keywords": [
7
- "mcp",
8
- "mcp-server",
9
- "model-context-protocol",
10
- "modelcontextprotocol",
11
- "claude",
12
- "claude-desktop",
13
- "cursor",
14
- "ai",
15
- "llm",
16
- "publish",
17
- "hosting",
18
- "static-hosting",
19
- "html-hosting",
20
- "share-link",
21
- "snapy"
22
- ],
23
- "type": "module",
24
- "bin": {
25
- "snapy-mcp": "index.js"
26
- },
27
- "engines": {
28
- "node": ">=18"
29
- },
30
- "dependencies": {
31
- "@modelcontextprotocol/sdk": "^1.0.0"
32
- }
33
- }
1
+ {
2
+ "name": "snapy-mcp",
3
+ "version": "1.1.2",
4
+ "description": "MCP server to publish files and pages to Snapy.host and get a shareable link.",
5
+ "homepage": "https://snapy.host/integrations/mcp",
6
+ "author": "Snapy",
7
+ "mcpName": "host.snapy/snapy",
8
+ "keywords": [
9
+ "mcp",
10
+ "mcp-server",
11
+ "model-context-protocol",
12
+ "modelcontextprotocol",
13
+ "claude",
14
+ "claude-desktop",
15
+ "cursor",
16
+ "ai",
17
+ "llm",
18
+ "publish",
19
+ "hosting",
20
+ "static-hosting",
21
+ "html-hosting",
22
+ "share-link",
23
+ "snapy"
24
+ ],
25
+ "type": "module",
26
+ "bin": {
27
+ "snapy-mcp": "index.js"
28
+ },
29
+ "files": [
30
+ "index.js",
31
+ "README.md"
32
+ ],
33
+ "engines": {
34
+ "node": ">=18"
35
+ },
36
+ "dependencies": {
37
+ "@modelcontextprotocol/sdk": "^1.0.0"
38
+ }
39
+ }