snapy-mcp 1.0.1 → 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.
- package/README.md +26 -48
- package/index.js +84 -75
- package/package.json +34 -33
package/README.md
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
# snapy-mcp
|
|
2
2
|
|
|
3
|
-
|
|
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
4
|
|
|
5
|
-
|
|
5
|
+
## Breaking change in 1.1.0: an API key is required
|
|
6
6
|
|
|
7
|
-
|
|
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.
|
|
8
9
|
|
|
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.
|
|
10
13
|
|
|
11
|
-
**
|
|
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": "
|
|
29
|
+
"env": { "SNAPY_API_KEY": "your key here" }
|
|
20
30
|
}
|
|
21
31
|
}
|
|
22
32
|
}
|
|
23
33
|
```
|
|
24
34
|
|
|
25
|
-
|
|
35
|
+
Or run it directly from this folder:
|
|
26
36
|
|
|
27
|
-
```
|
|
28
|
-
|
|
37
|
+
```
|
|
38
|
+
npm install
|
|
39
|
+
node index.js
|
|
29
40
|
```
|
|
30
41
|
|
|
31
|
-
|
|
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 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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,33 +1,34 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "snapy-mcp",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "MCP server
|
|
5
|
-
"homepage": "https://snapy.host/integrations/mcp",
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
"mcp
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"claude
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
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
|
+
}
|