snapy-mcp 1.0.0 → 1.1.1

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 +50 -36
  2. package/index.js +84 -75
  3. package/package.json +34 -25
package/README.md CHANGED
@@ -1,36 +1,50 @@
1
- # snapy-mcp
2
-
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 and get back a shareable link.
4
-
5
- ## Use it
6
-
7
- Add this to your MCP client config (for example Claude Desktop):
8
-
9
- ```json
10
- {
11
- "mcpServers": {
12
- "snapy": {
13
- "command": "npx",
14
- "args": ["-y", "snapy-mcp"],
15
- "env": { "SNAPY_API_KEY": "snapy_live_..." }
16
- }
17
- }
18
- }
19
- ```
20
-
21
- Or run it directly from this folder:
22
-
23
- ```
24
- npm install
25
- node index.js
26
- ```
27
-
28
- The tool calls the public snapy publish API at `https://api.snapy.host`. Set `SNAPY_API` to override.
29
-
30
- ## Tool
31
-
32
- - **publish** — args: `content` (required text/HTML), `name` (optional link name), `filename` (optional, default index.html). Returns the live link and a private analytics link.
33
-
34
- ## The API key
35
-
36
- Publishing a **web page** needs an API key. Create one in your snapy dashboard under **API keys** and pass it as `SNAPY_API_KEY`. Plain text formats (`.txt`, `.md`, `.csv`, `.json`) publish without one, so the connector works with no setup and gains page publishing when a key is present.
1
+ # snapy-mcp
2
+
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 and get back a shareable link.
4
+
5
+ ## Breaking change in 1.1.0: an API key is required
6
+
7
+ **Versions before 1.1.0 no longer work.** They publish without a credential, and the
8
+ snapy publish API no longer accepts that — for any file type.
9
+
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.
13
+
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:
22
+
23
+ ```json
24
+ {
25
+ "mcpServers": {
26
+ "snapy": {
27
+ "command": "npx",
28
+ "args": ["-y", "snapy-mcp"],
29
+ "env": { "SNAPY_API_KEY": "your key here" }
30
+ }
31
+ }
32
+ }
33
+ ```
34
+
35
+ Or run it directly from this folder:
36
+
37
+ ```
38
+ npm install
39
+ node index.js
40
+ ```
41
+
42
+ The tool calls the snapy publish API at `https://api.snapy.host`. Set `SNAPY_API` to
43
+ override the host, and `SNAPY_API_KEY` to supply your key (required).
44
+
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.
47
+
48
+ ## Tool
49
+
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,84 @@
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 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", version: "1.1.0" },
27
+ { capabilities: { tools: {} } }
28
+ );
29
+
30
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
31
+ tools: [
32
+ {
33
+ name: "publish",
34
+ description:
35
+ "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.",
36
+ inputSchema: {
37
+ type: "object",
38
+ properties: {
39
+ content: { type: "string", description: "The text or HTML to publish." },
40
+ name: { type: "string", description: "Optional custom link name (the subdomain)." },
41
+ filename: { type: "string", description: "Optional file name, defaults to index.html." },
42
+ },
43
+ required: ["content"],
44
+ },
45
+ },
46
+ ],
47
+ }));
48
+
49
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
50
+ if (req.params.name !== "publish") {
51
+ return { content: [{ type: "text", text: "Unknown tool" }], isError: true };
52
+ }
53
+ const { content, name, filename } = req.params.arguments || {};
54
+ if (!KEY) {
55
+ return {
56
+ content: [{ type: "text", text:
57
+ "snapy: set SNAPY_API_KEY before publishing. Create a free key at " +
58
+ "https://snapy.host/dashboard (API keys) with a verified email address, then put " +
59
+ "it in this server's env as SNAPY_API_KEY. Publishing without an account is no " +
60
+ "longer possible." }],
61
+ isError: true,
62
+ };
63
+ }
64
+ try {
65
+ const res = await fetch(`${API}/api/publish`, {
66
+ method: "POST",
67
+ headers: { "content-type": "application/json", authorization: `Bearer ${KEY}` },
68
+ body: JSON.stringify({ content, name, filename }),
69
+ });
70
+ const data = await res.json();
71
+ if (!res.ok) {
72
+ return { content: [{ type: "text", text: "snapy error: " + (data.error || res.status) }], isError: true };
73
+ }
74
+ return {
75
+ content: [{ type: "text", text: `Published. Link: ${data.url}\nPrivate analytics: ${data.stats_url}` }],
76
+ };
77
+ } catch (e) {
78
+ return { content: [{ type: "text", text: "Request failed: " + e.message }], isError: true };
79
+ }
80
+ });
81
+
82
+ const transport = new StdioServerTransport();
83
+ await server.connect(transport);
84
+ console.error("snapy MCP server running on stdio");
package/package.json CHANGED
@@ -1,25 +1,34 @@
1
- {
2
- "name": "snapy-mcp",
3
- "version": "1.0.0",
4
- "description": "MCP server to publish files and pages to snapy and get a shareable link.",
5
- "homepage": "https://snapy.host/integrations/mcp",
6
- "keywords": [
7
- "mcp",
8
- "modelcontextprotocol",
9
- "snapy",
10
- "publish",
11
- "hosting",
12
- "claude",
13
- "ai"
14
- ],
15
- "type": "module",
16
- "bin": {
17
- "snapy-mcp": "index.js"
18
- },
19
- "engines": {
20
- "node": ">=18"
21
- },
22
- "dependencies": {
23
- "@modelcontextprotocol/sdk": "^1.0.0"
24
- }
25
- }
1
+ {
2
+ "name": "snapy-mcp",
3
+ "version": "1.1.1",
4
+ "description": "MCP server to publish files and pages to snapy and get a shareable link.",
5
+ "homepage": "https://snapy.host/integrations/mcp",
6
+ "author": "Snapy",
7
+ "keywords": [
8
+ "mcp",
9
+ "mcp-server",
10
+ "model-context-protocol",
11
+ "modelcontextprotocol",
12
+ "claude",
13
+ "claude-desktop",
14
+ "cursor",
15
+ "ai",
16
+ "llm",
17
+ "publish",
18
+ "hosting",
19
+ "static-hosting",
20
+ "html-hosting",
21
+ "share-link",
22
+ "snapy"
23
+ ],
24
+ "type": "module",
25
+ "bin": {
26
+ "snapy-mcp": "index.js"
27
+ },
28
+ "engines": {
29
+ "node": ">=18"
30
+ },
31
+ "dependencies": {
32
+ "@modelcontextprotocol/sdk": "^1.0.0"
33
+ }
34
+ }