pixelate-mcp 0.1.0 → 0.3.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 (2) hide show
  1. package/index.js +45 -4
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { readFile, writeFile } from "node:fs/promises";
4
+ import os from "node:os";
5
+ import crypto from "node:crypto";
4
6
  import sharp from "sharp";
5
7
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
8
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -13,18 +15,36 @@ if (!API_URL || !API_KEY) {
13
15
  process.exit(1);
14
16
  }
15
17
 
18
+ // Stable per-machine id. A key binds to the first machine that uses it, so a
19
+ // shared key won't work on someone else's device. Derived from host/user/home
20
+ // (deterministic, no extra files); override with PIXELATE_MACHINE_ID if needed.
21
+ let _uname = "";
22
+ try { _uname = os.userInfo().username; } catch { /* ignore */ }
23
+ const MACHINE_ID =
24
+ process.env.PIXELATE_MACHINE_ID ||
25
+ crypto.createHash("sha256").update([os.hostname(), _uname, os.homedir(), os.platform()].join("|")).digest("hex").slice(0, 24);
26
+
16
27
  const stripDataUrl = (s) => {
17
28
  const i = s.indexOf(",");
18
29
  return s.startsWith("data:") && i !== -1 ? s.slice(i + 1) : s;
19
30
  };
20
31
  const errText = (text) => ({ content: [{ type: "text", text }], isError: true });
32
+ const usd = (cents) => `$${(cents / 100).toFixed(2)}`;
33
+ // One-line billing summary from a /job done response (credits_cents may be null
34
+ // for unlimited subscription keys).
35
+ function billingLine(res) {
36
+ const price = res.price_cents != null ? `${usd(res.price_cents)}/image` : null;
37
+ if (res.credits_cents == null) return price ? ` Plan: unlimited (${price}).` : "";
38
+ const imgs = res.price_cents ? Math.floor(res.credits_cents / res.price_cents) : null;
39
+ return ` Charged ${price}; ${usd(res.credits_cents)} credit left${imgs != null ? ` (~${imgs} images)` : ""}.`;
40
+ }
21
41
 
22
42
  async function jobReq(query, body) {
43
+ const headers = { "x-api-key": API_KEY, "x-machine-id": MACHINE_ID };
44
+ if (body) headers["content-type"] = "application/octet-stream";
23
45
  const res = await fetch(`${API_URL}/job?${query}`, {
24
46
  method: "POST",
25
- headers: body
26
- ? { "content-type": "application/octet-stream", "x-api-key": API_KEY }
27
- : { "x-api-key": API_KEY },
47
+ headers,
28
48
  body: body ?? undefined,
29
49
  });
30
50
  const j = await res.json().catch(() => ({}));
@@ -33,7 +53,27 @@ async function jobReq(query, body) {
33
53
  return j;
34
54
  }
35
55
 
36
- const server = new McpServer({ name: "pixelate-mcp", version: "0.1.0" });
56
+ const server = new McpServer({ name: "pixelate-mcp", version: "0.3.0" });
57
+
58
+ server.tool(
59
+ "pixelate_account",
60
+ "Show your Pixelate Cloud credit balance (how much you have left) and the cost per image.",
61
+ {},
62
+ async () => {
63
+ try {
64
+ const res = await fetch(`${API_URL}/account`, { headers: { "x-api-key": API_KEY, "x-machine-id": MACHINE_ID } });
65
+ const j = await res.json().catch(() => ({}));
66
+ if (!res.ok || !j.success) return errText(`account error (${res.status}): ${j.error || "unknown"}`);
67
+ const text =
68
+ j.plan === "subscription"
69
+ ? `Plan: unlimited subscription. Cost per image: ${usd(j.price_per_image_cents)} (not charged on your plan).`
70
+ : `Balance: $${j.credits_usd} (~${j.images_remaining} images left). Cost per image: $${j.price_per_image_usd}.`;
71
+ return { content: [{ type: "text", text }] };
72
+ } catch (e) {
73
+ return errText(`pixelate-mcp error: ${e?.message || e}`);
74
+ }
75
+ },
76
+ );
37
77
 
38
78
  server.tool(
39
79
  "pixelate_image",
@@ -80,6 +120,7 @@ server.tool(
80
120
  await writeFile(a.output_path, Buffer.from(res.image_base64, "base64"));
81
121
  summary += ` Saved to ${a.output_path}.`;
82
122
  }
123
+ summary += billingLine(res);
83
124
  return {
84
125
  content: [
85
126
  { type: "text", text: summary },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixelate-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "MCP server: turn any image into grid-perfect pixel art via the Pixelate Cloud API. Works in Codex, Claude Desktop, Cursor.",
5
5
  "type": "module",
6
6
  "bin": {