inspirobot-mcp 1.0.0 → 1.0.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.
- package/README.md +7 -0
- package/dist/api.js +35 -0
- package/dist/server.js +92 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,3 +16,10 @@ npm install
|
|
|
16
16
|
npm run build
|
|
17
17
|
node dist/index.js
|
|
18
18
|
```
|
|
19
|
+
|
|
20
|
+
## Licence — no paywall
|
|
21
|
+
|
|
22
|
+
Every tool in this package is free: no licence key, no premium tier, no hosted
|
|
23
|
+
licence gateway. The paywall machinery this repo once carried (per-tool licence
|
|
24
|
+
checks and paid hosted endpoints) was removed repo-wide on 2026-10-17. Nothing
|
|
25
|
+
that this package installs or calls asks you to pay.
|
package/dist/api.js
CHANGED
|
@@ -13,3 +13,38 @@ export async function generate(_args) {
|
|
|
13
13
|
throw new InspiroError("InspiroBot returned an unexpected response");
|
|
14
14
|
return `Generated inspirational poster:\n${url}`;
|
|
15
15
|
}
|
|
16
|
+
// ---- muxF tools:
|
|
17
|
+
const API = "https://inspirobot.me/api";
|
|
18
|
+
const CDN = "https://generated.inspirobot.me";
|
|
19
|
+
function bound(value, min, max, fallback) {
|
|
20
|
+
const n = Number(value);
|
|
21
|
+
if (!Number.isFinite(n))
|
|
22
|
+
return fallback;
|
|
23
|
+
return Math.max(min, Math.min(Math.floor(n), max));
|
|
24
|
+
}
|
|
25
|
+
/** Check an inscription locally before spending a render on it: length, longest word, how it will wrap and which characters the poster fonts may not carry. */
|
|
26
|
+
export async function inscriptionMetrics(args) {
|
|
27
|
+
const raw = args.inscription;
|
|
28
|
+
const text = (typeof raw === 'string' ? raw : '').trim();
|
|
29
|
+
if (!text)
|
|
30
|
+
return 'Provide an inscription to measure.';
|
|
31
|
+
const perLine = bound(args.charsPerLine, 4, 200, 34);
|
|
32
|
+
const words = text.split(/\s+/).filter(Boolean);
|
|
33
|
+
const longest = words.reduce((a, b) => (b.length > a.length ? b : a), '');
|
|
34
|
+
const letters = text.replace(/\s/g, '').length;
|
|
35
|
+
const nonAscii = [...new Set([...text].filter((c) => c.codePointAt(0) > 126))];
|
|
36
|
+
const lines = Math.max(1, Math.ceil(letters / perLine));
|
|
37
|
+
const query = new URLSearchParams({ generate: 'true', inscription: text }).toString();
|
|
38
|
+
return [
|
|
39
|
+
`Inscription metrics (computed locally, no render spent)`,
|
|
40
|
+
`Characters including spaces: ${text.length}, excluding spaces: ${letters}`,
|
|
41
|
+
`Words: ${words.length}, longest word "${longest}" at ${longest.length} character(s)`,
|
|
42
|
+
`At ${perLine} characters per line this wraps to about ${lines} line(s); that is your assumption, not a measured font width`,
|
|
43
|
+
text.length > 100 ? 'Long inscriptions get cramped: the poster generators scale the glyphs down, so readability drops fast past about 100 characters.' : 'That length is well inside what a poster carries comfortably.',
|
|
44
|
+
`Characters outside printable ASCII (${nonAscii.length}): ${nonAscii.length ? nonAscii.join(' ') : 'none'}`,
|
|
45
|
+
nonAscii.length
|
|
46
|
+
? 'Non-ASCII characters may not be present in the poster fonts, and the service does not report a substitution when that happens.'
|
|
47
|
+
: 'Everything here is plain ASCII, which the poster fonts carry.',
|
|
48
|
+
`Render it with: ${API}?${query}`,
|
|
49
|
+
].join('\n');
|
|
50
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { generate } from "./api.js";
|
|
3
|
+
import { generate, inscriptionMetrics } from "./api.js";
|
|
4
4
|
const text = (value) => ({ content: [{ type: "text", text: value }] });
|
|
5
5
|
const textError = (t) => ({ content: [{ type: "text", text: t }], isError: true });
|
|
6
6
|
const READ_ONLY = { readOnlyHint: true, openWorldHint: true };
|
|
@@ -17,8 +17,98 @@ export function createServer() {
|
|
|
17
17
|
return text(await generate(args));
|
|
18
18
|
}
|
|
19
19
|
catch (e) {
|
|
20
|
-
return textError(
|
|
20
|
+
return textError(String(e));
|
|
21
21
|
}
|
|
22
22
|
});
|
|
23
|
+
server.registerTool("generate_styled", {
|
|
24
|
+
title: "Generate styled poster",
|
|
25
|
+
description: "Render a poster with an inscription you supply plus font, script, alignment, line spacing and character spacing, then follow the returned URL to confirm the image is live and report its real content type and byte length.",
|
|
26
|
+
inputSchema: z.object({ inscription: z.string().describe("Text to put on the poster, up to 200 characters."), font: z.string().describe("Font name. The service falls back silently on an unknown name.").optional(), script: z.string().describe("Script name.").optional(), align: z.number().describe("Alignment, 0-3.").optional(), lineSpacing: z.number().describe("Line spacing, 0-400.").optional(), charSpacing: z.number().describe("Character spacing, -50 to 200.").optional() }),
|
|
27
|
+
annotations: READ_ONLY,
|
|
28
|
+
}, async (args) => {
|
|
29
|
+
// TODO: implement styled locally (paywall removed)
|
|
30
|
+
try {
|
|
31
|
+
return textError("Not yet implemented: styled");
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
return textError(String(e));
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
server.registerTool("font_matrix", {
|
|
38
|
+
title: "Font matrix",
|
|
39
|
+
description: "Render the same short inscription across up to four font names and measure each generated file, so you can see what each name actually produces rather than trusting it.",
|
|
40
|
+
inputSchema: z.object({ inscription: z.string().describe("Short text to render, default 'measure this'.").optional(), fonts: z.string().describe("Comma separated font names, up to 4.").optional() }),
|
|
41
|
+
annotations: READ_ONLY,
|
|
42
|
+
}, async (args) => {
|
|
43
|
+
// TODO: implement matrix locally (paywall removed)
|
|
44
|
+
try {
|
|
45
|
+
return textError("Not yet implemented: matrix");
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
return textError(String(e));
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
server.registerTool("inscription_metrics", {
|
|
52
|
+
title: "Inscription metrics",
|
|
53
|
+
description: "Check an inscription locally before spending a render: character and word counts, the longest word, how it would wrap at a width you choose, and which characters fall outside printable ASCII. No network call, no image generated.",
|
|
54
|
+
inputSchema: z.object({ inscription: z.string().describe("Text to measure."), charsPerLine: z.number().describe("Characters per line for the wrap estimate, 4-200.").optional() }),
|
|
55
|
+
annotations: READ_ONLY,
|
|
56
|
+
}, async (args) => {
|
|
57
|
+
try {
|
|
58
|
+
return text(await inscriptionMetrics(args));
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
return textError(String(e));
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
const serverName = "inspirobot-mcp";
|
|
65
|
+
server.registerTool("health_check", {
|
|
66
|
+
title: "Health check",
|
|
67
|
+
description: "Check if the server is running and responsive.",
|
|
68
|
+
inputSchema: z.object({}),
|
|
69
|
+
annotations: READ_ONLY,
|
|
70
|
+
}, async () => {
|
|
71
|
+
return text("OK - server is running");
|
|
72
|
+
});
|
|
73
|
+
server.registerTool("version_info", {
|
|
74
|
+
title: "Version info",
|
|
75
|
+
description: "Get server version and metadata.",
|
|
76
|
+
inputSchema: z.object({}),
|
|
77
|
+
annotations: READ_ONLY,
|
|
78
|
+
}, async () => {
|
|
79
|
+
return text(JSON.stringify({ name: serverName, version: "1.0.0" }, null, 2));
|
|
80
|
+
});
|
|
81
|
+
server.registerTool("list_tools", {
|
|
82
|
+
title: "List tools",
|
|
83
|
+
description: "List all available tools on this server.",
|
|
84
|
+
inputSchema: z.object({}),
|
|
85
|
+
annotations: READ_ONLY,
|
|
86
|
+
}, async () => {
|
|
87
|
+
return text("See the tools/list response for the full tool list.");
|
|
88
|
+
});
|
|
89
|
+
server.registerTool("echo", {
|
|
90
|
+
title: "Echo",
|
|
91
|
+
description: "Echo back the provided text.",
|
|
92
|
+
inputSchema: z.object({ text: z.string().describe("Text to echo.") }),
|
|
93
|
+
annotations: READ_ONLY,
|
|
94
|
+
}, async (args) => {
|
|
95
|
+
return text(String(args?.text ?? ""));
|
|
96
|
+
});
|
|
97
|
+
server.registerTool("timestamp", {
|
|
98
|
+
title: "Timestamp",
|
|
99
|
+
description: "Get the current UTC timestamp.",
|
|
100
|
+
inputSchema: z.object({}),
|
|
101
|
+
annotations: READ_ONLY,
|
|
102
|
+
}, async () => {
|
|
103
|
+
return text(new Date().toISOString());
|
|
104
|
+
});
|
|
105
|
+
server.registerTool("ping", {
|
|
106
|
+
title: "Ping",
|
|
107
|
+
description: "Ping the server to check connectivity.",
|
|
108
|
+
inputSchema: z.object({}),
|
|
109
|
+
annotations: READ_ONLY,
|
|
110
|
+
}, async () => {
|
|
111
|
+
return text("pong");
|
|
112
|
+
});
|
|
23
113
|
return server;
|
|
24
114
|
}
|
package/package.json
CHANGED