mcp-image-previewer 0.1.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.
package/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # mcp-image-previewer
2
+
3
+ An MCP (Model Context Protocol) server that previews base64-encoded images in a native GUI window using [Slint](https://slint.dev/).
4
+
5
+ ## Features
6
+
7
+ - **Native GUI** — Slint-based window, no Python or external runtime needed
8
+ - **Window reuse** — Same window updates in-place across multiple images
9
+ - **Always-on-top** — Optionally pin the preview window above other windows
10
+ - **Show/hide** — Toggle window visibility without losing state
11
+ - **Format support** — PNG, JPEG, WebP, GIF, TIFF, etc. (via sharp)
12
+
13
+ ## Requirements
14
+
15
+ - Node.js 18+
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install
21
+ npm run build
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ Add to your MCP client configuration:
27
+
28
+ ```json
29
+ {
30
+ "mcpServers": {
31
+ "image-previewer": {
32
+ "command": "node",
33
+ "args": ["/path/to/mcp-image-previewer/dist/index.js"]
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ ### Tools
40
+
41
+ #### `preview_image`
42
+
43
+ Display a base64-encoded image in the viewer window.
44
+
45
+ | Parameter | Type | Required | Description |
46
+ |-----------|--------|----------|-------------|
47
+ | `data` | string | Yes | Base64-encoded image data |
48
+ | `mimeType`| string | Yes | MIME type (e.g. `image/png`, `image/jpeg`) |
49
+
50
+ #### `show_window`
51
+
52
+ Show or hide the preview window.
53
+
54
+ | Parameter | Type | Required | Description |
55
+ |-----------|---------|----------|-------------|
56
+ | `visible` | boolean | Yes | `true` to show, `false` to hide |
57
+
58
+ #### `stay_on_top`
59
+
60
+ Set whether the preview window stays on top of other windows.
61
+
62
+ | Parameter | Type | Required | Description |
63
+ |-----------|---------|----------|-------------|
64
+ | `enabled` | boolean | Yes | `true` to enable, `false` to disable |
65
+
66
+ ## License
67
+
68
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ import * as slint from "slint-ui";
6
+ import { showImage, setStayOnTop, showWindow, hideWindow, cleanup, } from "./viewer.js";
7
+ const server = new McpServer({
8
+ name: "mcp-image-previewer",
9
+ version: "0.1.0",
10
+ });
11
+ server.tool("preview_image", "Preview a base64-encoded image. Opens a GUI viewer window that is reused across calls.", {
12
+ data: z.string().describe("Base64-encoded image data"),
13
+ mimeType: z
14
+ .string()
15
+ .describe("MIME type of the image (e.g. image/png, image/jpeg)"),
16
+ }, async ({ data, mimeType }) => {
17
+ try {
18
+ await showImage(data, mimeType);
19
+ return {
20
+ content: [
21
+ {
22
+ type: "text",
23
+ text: "Image displayed.",
24
+ },
25
+ ],
26
+ };
27
+ }
28
+ catch (error) {
29
+ const message = error instanceof Error ? error.message : String(error);
30
+ return {
31
+ content: [
32
+ { type: "text", text: `Preview failed: ${message}` },
33
+ ],
34
+ isError: true,
35
+ };
36
+ }
37
+ });
38
+ server.tool("show_window", "Show or hide the image preview window.", {
39
+ visible: z.boolean().describe("true to show, false to hide"),
40
+ }, async ({ visible }) => {
41
+ try {
42
+ if (visible) {
43
+ showWindow();
44
+ }
45
+ else {
46
+ hideWindow();
47
+ }
48
+ return {
49
+ content: [
50
+ {
51
+ type: "text",
52
+ text: `Window ${visible ? "shown" : "hidden"}.`,
53
+ },
54
+ ],
55
+ };
56
+ }
57
+ catch (error) {
58
+ const message = error instanceof Error ? error.message : String(error);
59
+ return {
60
+ content: [
61
+ { type: "text", text: `Failed: ${message}` },
62
+ ],
63
+ isError: true,
64
+ };
65
+ }
66
+ });
67
+ server.tool("stay_on_top", "Set whether the image preview window stays on top of other windows.", {
68
+ enabled: z.boolean().describe("true to enable always-on-top, false to disable"),
69
+ }, async ({ enabled }) => {
70
+ try {
71
+ setStayOnTop(enabled);
72
+ return {
73
+ content: [
74
+ {
75
+ type: "text",
76
+ text: `Stay-on-top ${enabled ? "enabled" : "disabled"}.`,
77
+ },
78
+ ],
79
+ };
80
+ }
81
+ catch (error) {
82
+ const message = error instanceof Error ? error.message : String(error);
83
+ return {
84
+ content: [
85
+ { type: "text", text: `Failed: ${message}` },
86
+ ],
87
+ isError: true,
88
+ };
89
+ }
90
+ });
91
+ process.on("exit", cleanup);
92
+ process.on("SIGINT", () => {
93
+ cleanup();
94
+ slint.quitEventLoop();
95
+ process.exit(0);
96
+ });
97
+ process.on("SIGTERM", () => {
98
+ cleanup();
99
+ slint.quitEventLoop();
100
+ process.exit(0);
101
+ });
102
+ async function main() {
103
+ const transport = new StdioServerTransport();
104
+ await server.connect(transport);
105
+ // Slint event loop merges with Node.js event loop at ~16ms intervals,
106
+ // so MCP stdio I/O continues to work while the GUI is running.
107
+ await slint.runEventLoop({ quitOnLastWindowClosed: false });
108
+ }
109
+ main().catch((err) => {
110
+ console.error("Fatal:", err);
111
+ process.exit(1);
112
+ });
113
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export declare function setStayOnTop(value: boolean): void;
2
+ export declare function showImage(data: string, _mimeType: string): Promise<void>;
3
+ export declare function showWindow(): void;
4
+ export declare function hideWindow(): void;
5
+ export declare function cleanup(): void;
package/dist/viewer.js ADDED
@@ -0,0 +1,71 @@
1
+ import * as slint from "slint-ui";
2
+ import sharp from "sharp";
3
+ let component = null;
4
+ let ui = null;
5
+ function ensureUI() {
6
+ if (!ui) {
7
+ ui = slint.loadFile(new URL("../ui/main.slint", import.meta.url));
8
+ }
9
+ return ui;
10
+ }
11
+ function ensureWindow() {
12
+ const mod = ensureUI();
13
+ let needNew = !component;
14
+ if (!needNew) {
15
+ try {
16
+ needNew = !component.window.visible;
17
+ }
18
+ catch {
19
+ needNew = true;
20
+ }
21
+ }
22
+ if (needNew) {
23
+ const Ctor = mod.MainWindow;
24
+ component = new Ctor();
25
+ component.show();
26
+ }
27
+ return component;
28
+ }
29
+ export function setStayOnTop(value) {
30
+ const win = ensureWindow();
31
+ win.stay_on_top = value;
32
+ }
33
+ export async function showImage(data, _mimeType) {
34
+ const buf = Buffer.from(data, "base64");
35
+ const { data: rgba, info } = await sharp(buf)
36
+ .ensureAlpha()
37
+ .raw()
38
+ .toBuffer({ resolveWithObject: true });
39
+ const win = ensureWindow();
40
+ win.current_image = {
41
+ width: info.width,
42
+ height: info.height,
43
+ data: new Uint8ClampedArray(rgba.buffer, rgba.byteOffset, rgba.byteLength),
44
+ };
45
+ win.image_info = `${info.width} x ${info.height}`;
46
+ }
47
+ export function showWindow() {
48
+ ensureWindow();
49
+ }
50
+ export function hideWindow() {
51
+ if (component) {
52
+ try {
53
+ component.hide();
54
+ }
55
+ catch {
56
+ // ignore
57
+ }
58
+ }
59
+ }
60
+ export function cleanup() {
61
+ if (component) {
62
+ try {
63
+ component.hide();
64
+ }
65
+ catch {
66
+ // ignore errors during cleanup
67
+ }
68
+ component = null;
69
+ }
70
+ }
71
+ //# sourceMappingURL=viewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viewer.js","sourceRoot":"","sources":["../src/viewer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,UAAU,CAAC;AAClC,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,IAAI,SAAS,GAA+B,IAAI,CAAC;AACjD,IAAI,EAAE,GAA+B,IAAI,CAAC;AAE1C,SAAS,QAAQ;IACf,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,EAAE,GAAG,KAAK,CAAC,QAAQ,CACjB,IAAI,GAAG,CAAC,kBAAkB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CACtB,CAAC;IAC3B,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,YAAY;IACnB,MAAM,GAAG,GAAG,QAAQ,EAAE,CAAC;IACvB,IAAI,OAAO,GAAG,CAAC,SAAS,CAAC;IACzB,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,IAAI,CAAC;YACH,OAAO,GAAG,CAAE,SAAiC,CAAC,MAAM,CAAC,OAAO,CAAC;QAC/D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;IACH,CAAC;IACD,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,GAAG,CAAC,UAA2C,CAAC;QAC7D,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC;QACtB,SAAiC,CAAC,IAAI,EAAE,CAAC;IAC5C,CAAC;IACD,OAAO,SAAgC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,MAAM,GAAG,GAAG,YAAY,EAAE,CAAC;IAC3B,GAAG,CAAC,WAAW,GAAG,KAAK,CAAC;AAC1B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,IAAY,EACZ,SAAiB;IAEjB,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACxC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC;SAC1C,WAAW,EAAE;SACb,GAAG,EAAE;SACL,QAAQ,CAAC,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC;IAEzC,MAAM,GAAG,GAAG,YAAY,EAAE,CAAC;IAC3B,GAAG,CAAC,aAAa,GAAG;QAClB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,IAAI,EAAE,IAAI,iBAAiB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC;KAC3E,CAAC;IACF,GAAG,CAAC,UAAU,GAAG,GAAG,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,YAAY,EAAE,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,IAAI,SAAS,EAAE,CAAC;QACd,IAAI,CAAC;YACF,SAAiC,CAAC,IAAI,EAAE,CAAC;QAC5C,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,OAAO;IACrB,IAAI,SAAS,EAAE,CAAC;QACd,IAAI,CAAC;YACF,SAAiC,CAAC,IAAI,EAAE,CAAC;QAC5C,CAAC;QAAC,MAAM,CAAC;YACP,+BAA+B;QACjC,CAAC;QACD,SAAS,GAAG,IAAI,CAAC;IACnB,CAAC;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "mcp-image-previewer",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for previewing base64-encoded images from other MCP servers",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "mcp-image-previewer": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "start": "node dist/index.js"
13
+ },
14
+ "dependencies": {
15
+ "@modelcontextprotocol/sdk": "^1.12.1",
16
+ "sharp": "^0.34.5",
17
+ "slint-ui": "^1.14.1"
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "^22.0.0",
21
+ "typescript": "^5.7.0"
22
+ }
23
+ }