@layerporter/image-optimizer-mcp 0.1.0 → 0.1.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 CHANGED
@@ -1,124 +1,116 @@
1
1
  # LayerPorter Website Image Optimizer MCP
2
2
 
3
- Technical candidate for page-aware website image optimization through Model Context Protocol.
3
+ Public MCP server for website image analysis and safe image optimization.
4
4
 
5
- ## Status
5
+ ## Install
6
6
 
7
- The package is release-prepared but is **not yet claimed as a public npm or Official MCP Registry release**. Publication status changes only after the exact npm version and Registry entry are externally verified.
8
-
9
- Registry identity:
7
+ ```bash
8
+ npx -y @layerporter/image-optimizer-mcp@latest
9
+ ```
10
10
 
11
11
  - npm package: `@layerporter/image-optimizer-mcp`
12
12
  - MCP server: `com.layerporter/website-image-optimizer`
13
13
  - transport: `stdio`
14
14
  - Node.js: `>=22.12.0`
15
+ - repository: `https://github.com/OlegStrateg/lp-site`
16
+ - product page: `https://layerporter.com/mcp/website-image-optimizer/`
15
17
 
16
- The npm package carries `mcpName: com.layerporter/website-image-optimizer`, matching `server.json`, so the Official MCP Registry can verify package ownership after the package exists publicly on npm.
18
+ The npm package is publicly available. Official MCP Registry publication is a separate distribution step and is not implied by npm publication.
17
19
 
18
- ## Tools
20
+ ## What it does
19
21
 
20
- The current server registers seven bounded tools:
22
+ The server registers seven bounded tools:
21
23
 
22
- - `analyze_page_images`
23
- - `optimize_image`
24
- - `generate_responsive_variants`
25
- - `compare_image_versions`
26
- - `optimize_page_images`
27
- - `analyze_url_images`
28
- - `optimize_url_images`
24
+ - `analyze_page_images` — analyze caller-provided page/image facts;
25
+ - `optimize_image` — optimize one image;
26
+ - `generate_responsive_variants` — generate resized image variants without upscaling;
27
+ - `compare_image_versions` — compare image versions;
28
+ - `optimize_page_images` — optimize a bounded set of page images;
29
+ - `analyze_url_images` — inspect images referenced by static HTML at a public URL;
30
+ - `optimize_url_images` — fetch and optimize a bounded set of raster images referenced by static HTML.
29
31
 
30
- The first five operate on caller-provided image data or normalized page facts. The two URL tools perform bounded read-only HTTP(S) fetching through a dedicated SSRF-protected network boundary. URL mode is intentionally a fast static-HTML mode: it does not claim browser-rendered size, `currentSrc`, LCP, CSS-background discovery, or JavaScript-driven lazy content.
32
+ Accepted optimized binaries are returned through temporary MCP `resource_link` values and read separately with `resources/read`.
31
33
 
32
- Accepted optimized binaries are exposed as temporary MCP `resource_link` values and are read separately with `resources/read`; they are not embedded in the initial tool text response.
34
+ ## Input and output boundary
33
35
 
34
- ## Runtime
36
+ Untrusted image input is fail-closed:
35
37
 
36
- - Node.js `>=22.12.0`
37
- - MCP v2 server package
38
- - stdio transport
39
- - image processing through the shared image core / Sharp / libvips
38
+ - accepted input decoders: JPEG, PNG, WebP;
39
+ - blocked input decoders: HEIF/AVIF, TIFF, GIF, SVG, and unknown formats;
40
+ - AVIF remains available as an output format.
40
41
 
41
- ## Local source setup
42
+ The optimizer applies bounded transformations and does not upscale images.
42
43
 
43
- A source checkout does not contain the generated `src/image-core` copy. Sync it explicitly before MCP tests or direct server startup:
44
+ ## URL mode limitations
44
45
 
45
- ```bash
46
- cd packages/image-core
47
- npm install --no-audit --no-fund
48
- npm test
49
-
50
- cd ../image-optimizer-mcp
51
- npm install --ignore-scripts --no-audit --no-fund
52
- npm run release:preflight
53
- npm run sync:core
54
- npm test
55
- node src/server.js
56
- ```
46
+ URL mode is intentionally a fast static-HTML path. It does **not** claim to provide a browser-rendered audit.
57
47
 
58
- The last command starts the stdio MCP server and waits for a client connection. `npm pack` also runs `sync:core` through `prepack`; source setup and packed-artifact setup are verified separately.
48
+ It does not discover or measure:
59
49
 
60
- ## Installation after public release
50
+ - browser-selected `currentSrc`;
51
+ - real LCP;
52
+ - CSS background images;
53
+ - JavaScript-injected or lazy-loaded content that is absent from the fetched HTML;
54
+ - production page write/apply behavior.
61
55
 
62
- Do not treat this command as available until the npm package has actually been published and verified:
56
+ Use it for bounded static discovery and optimization, not as a replacement for a browser runtime or Core Web Vitals measurement.
63
57
 
64
- ```bash
65
- npx -y @layerporter/image-optimizer-mcp
66
- ```
67
-
68
- ## License
58
+ ## Safety
69
59
 
70
- This package is proprietary LayerPorter software. The official unmodified package may be downloaded, installed, and used under the terms in `LICENSE`. Copying beyond technically necessary installation/runtime copies, modification, derivative works, repackaging, redistribution, sublicensing, resale, and unauthorized hosting are prohibited.
60
+ The package:
71
61
 
72
- The software is licensed, not sold. Third-party dependencies remain governed by their own licenses.
73
-
74
- ## Release model
75
-
76
- The first npm version is a bootstrap release: npm staged publishing and Trusted Publisher configuration require the package to already exist. The first public package therefore must be published by the package owner with npm account 2FA. After that bootstrap, GitHub Actions Trusted Publishing can be configured for `.github/workflows/image-mcp-release.yml`, and later versions can use the staged OIDC release path.
77
-
78
- Official MCP Registry publication happens only **after** the matching npm version is publicly available. The Registry namespace uses ownership of `layerporter.com` and the server name `com.layerporter/website-image-optimizer`.
62
+ - performs public HTTP(S) reads only through the two URL tools;
63
+ - blocks unsafe URL schemes, credentials, localhost/private/link-local/reserved destinations, unsafe DNS answers, and redirects to prohibited addresses;
64
+ - caps page bytes, image bytes, total accepted bytes, redirects, image count, timeouts, and concurrency;
65
+ - writes accepted output only to an isolated temporary artifact store;
66
+ - does not execute shell commands;
67
+ - does not perform arbitrary filesystem writes;
68
+ - does not modify a website or production environment.
79
69
 
80
- ## Safety boundary
70
+ Temporary artifacts are process-local and ephemeral. The default TTL is 30 minutes; cleanup is performed during store operations/disposal rather than at an exact wall-clock instant.
81
71
 
82
- The current MCP package:
72
+ ## Quick usage examples
83
73
 
84
- - performs bounded public HTTP(S) reads only through `analyze_url_images` and `optimize_url_images`;
85
- - blocks unsafe URL schemes, URL credentials, localhost/private/link-local/reserved targets, unsafe DNS answers and redirects to prohibited addresses;
86
- - caps page bytes, per-image bytes, accepted total image bytes, redirects, image count, timeouts and concurrency;
87
- - writes accepted image artifacts only to its isolated temporary artifact store;
88
- - does not perform arbitrary filesystem write/delete/rename operations;
89
- - does not execute shell commands;
90
- - does not write to a website or production environment.
74
+ After connecting the MCP server to a compatible client, requests can be framed around the task:
91
75
 
92
- Local transform tools consume caller-provided image buffers or normalized page facts. URL tools are read-only open-world operations. Hard safety comes from implementation controls, not only from MCP metadata.
76
+ - “Analyze the images referenced by this page URL.”
77
+ - “Optimize this JPEG for web delivery without upscaling.”
78
+ - “Generate responsive variants for this image.”
79
+ - “Compare these two image versions and report the byte difference.”
93
80
 
94
- Temporary artifacts are process-local and ephemeral. Their default TTL is 30 minutes, but cleanup occurs during store operations/disposal rather than as a guarantee of physical deletion at an exact wall-clock instant. `resources/read` returns the resource blob separately; whether a client places that blob into model context is client-specific.
81
+ Client-specific MCP configuration differs by product, so use the MCP client’s current documentation for its exact server configuration format.
95
82
 
96
83
  ## Verification
97
84
 
98
- Release preparation verifies:
85
+ The release process verifies:
99
86
 
100
- - package/server identity consistency;
101
- - proprietary license metadata and packaged `LICENSE` presence;
87
+ - package/server identity and version consistency;
102
88
  - image-core and MCP tests;
103
89
  - packed npm artifact metadata;
104
90
  - clean installation of the packed tarball;
105
- - real stdio startup and client handshake from the packed artifact;
106
- - `tools/list`, transform calls, temporary `resource_link` and `resources/read`;
107
- - Official MCP Registry `server.json` validation;
108
- - full LayerPorter Astro build in the integrated repository tree.
91
+ - stdio startup and MCP client handshake;
92
+ - exactly seven expected tools;
93
+ - real tool calls;
94
+ - temporary `resource_link` and `resources/read`;
95
+ - public-package source equivalence after publication.
96
+
97
+ ## License
98
+
99
+ This package is proprietary LayerPorter software. The official unmodified package may be downloaded, installed, and used under the terms in `LICENSE`.
109
100
 
110
- ## Public documentation
101
+ Copying beyond technically necessary installation/runtime copies, modification, derivative works, repackaging, redistribution, sublicensing, resale, and unauthorized hosting are prohibited. Third-party dependencies remain governed by their own licenses.
111
102
 
112
- Canonical product pages after site release:
103
+ ## Links
113
104
 
114
- - `https://layerporter.com/mcp/website-image-optimizer/`
115
- - `https://layerporter.com/docs/mcp/website-image-optimizer/`
105
+ - Product: `https://layerporter.com/mcp/website-image-optimizer/`
106
+ - Documentation: `https://layerporter.com/docs/mcp/website-image-optimizer/`
107
+ - Repository: `https://github.com/OlegStrateg/lp-site`
108
+ - Issues: `https://github.com/OlegStrateg/lp-site/issues`
116
109
 
117
- ## Not yet included
110
+ ## Not included
118
111
 
119
- - confirmed public npm release;
120
- - confirmed Official MCP Registry publication;
121
- - hosted endpoint;
112
+ - Official MCP Registry publication;
113
+ - hosted remote endpoint;
122
114
  - full browser crawler/runtime metrics collection;
123
- - production apply/write tool;
124
- - universal performance or quality claims.
115
+ - production apply/write tools;
116
+ - universal performance, quality, or Core Web Vitals guarantees.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@layerporter/image-optimizer-mcp",
3
- "version": "0.1.0",
4
- "description": "Page-aware MCP server for deterministic website image analysis and safe optimization.",
3
+ "version": "0.1.2",
4
+ "description": "MCP server for website image analysis, safe compression, resizing, responsive variants, and WebP/AVIF output.",
5
5
  "mcpName": "com.layerporter/website-image-optimizer",
6
6
  "type": "module",
7
7
  "license": "SEE LICENSE IN LICENSE",
@@ -30,12 +30,12 @@
30
30
  },
31
31
  "repository": {
32
32
  "type": "git",
33
- "url": "git+https://github.com/OlegStrateg/layerporter-site.git",
33
+ "url": "git+https://github.com/OlegStrateg/lp-site.git",
34
34
  "directory": "packages/image-optimizer-mcp"
35
35
  },
36
36
  "homepage": "https://layerporter.com/mcp/website-image-optimizer/",
37
37
  "bugs": {
38
- "url": "https://github.com/OlegStrateg/layerporter-site/issues"
38
+ "url": "https://github.com/OlegStrateg/lp-site/issues"
39
39
  },
40
40
  "keywords": [
41
41
  "mcp",
package/server.json CHANGED
@@ -2,19 +2,19 @@
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
3
  "name": "com.layerporter/website-image-optimizer",
4
4
  "title": "LayerPorter Website Image Optimizer",
5
- "description": "Page-aware website image analysis and safe optimization MCP server.",
5
+ "description": "Website image analysis and safe optimization for compression, resizing, and responsive variants.",
6
6
  "websiteUrl": "https://layerporter.com/mcp/website-image-optimizer/",
7
7
  "repository": {
8
- "url": "https://github.com/OlegStrateg/layerporter-site",
8
+ "url": "https://github.com/OlegStrateg/lp-site",
9
9
  "source": "github",
10
10
  "subfolder": "packages/image-optimizer-mcp"
11
11
  },
12
- "version": "0.1.0",
12
+ "version": "0.1.2",
13
13
  "packages": [
14
14
  {
15
15
  "registryType": "npm",
16
16
  "identifier": "@layerporter/image-optimizer-mcp",
17
- "version": "0.1.0",
17
+ "version": "0.1.2",
18
18
  "transport": {
19
19
  "type": "stdio"
20
20
  }
@@ -1,12 +1,48 @@
1
1
  import sharp from 'sharp';
2
2
  import { chooseOutputFormat, validatePolicy } from './policy.js';
3
3
 
4
+ const ALLOWED_UNTRUSTED_BUFFER_LOADERS = Object.freeze([
5
+ 'VipsForeignLoadJpegBuffer',
6
+ 'VipsForeignLoadPngBuffer',
7
+ 'VipsForeignLoadWebpBuffer',
8
+ ]);
9
+
10
+ const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
11
+
12
+ // Native decoder boundary: fail closed for all foreign input, then reopen only
13
+ // Buffer loaders that are part of LayerPorter's supported input contract.
14
+ // HEIF/AVIF decoding stays disabled until the bundled runtime is independently
15
+ // verified with libheif >=1.23.4, the current advisory set is re-reviewed, and
16
+ // input support is explicitly re-approved.
17
+ sharp.block({ operation: ['VipsForeignLoad'] });
18
+ sharp.unblock({ operation: ALLOWED_UNTRUSTED_BUFFER_LOADERS });
19
+
4
20
  function assertBuffer(input) {
5
21
  if (!Buffer.isBuffer(input) || input.length === 0) {
6
22
  throw new TypeError('input must be a non-empty Buffer');
7
23
  }
8
24
  }
9
25
 
26
+ function detectAllowedInputFormat(input) {
27
+ if (input.length >= 2 && input[0] === 0xff && input[1] === 0xd8) return 'jpeg';
28
+ if (input.length >= PNG_SIGNATURE.length && input.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)) return 'png';
29
+ if (
30
+ input.length >= 12
31
+ && input.toString('ascii', 0, 4) === 'RIFF'
32
+ && input.toString('ascii', 8, 12) === 'WEBP'
33
+ ) return 'webp';
34
+ return null;
35
+ }
36
+
37
+ function assertAllowedInputFormat(input) {
38
+ const format = detectAllowedInputFormat(input);
39
+ if (format) return format;
40
+
41
+ const error = new TypeError('Unsupported or security-blocked input format; allowed input formats: JPEG, PNG, WebP');
42
+ error.code = 'INPUT_FORMAT_NOT_ALLOWED';
43
+ throw error;
44
+ }
45
+
10
46
  function assertTarget(target = {}) {
11
47
  for (const key of ['width', 'height']) {
12
48
  if (target[key] !== undefined && (!Number.isInteger(target[key]) || target[key] < 1)) {
@@ -42,8 +78,32 @@ function encoder(pipeline, format, policy) {
42
78
  }
43
79
  }
44
80
 
81
+ async function inspectEncodedOutput(data, info, outputFormat, policy) {
82
+ // HEIF/AVIF input is intentionally blocked in this process. For an AVIF we
83
+ // just encoded from an already validated input, use Sharp's encode result
84
+ // metadata instead of reopening the HEIF decoder. Independent fresh-process
85
+ // decode remains part of the regression/release gate.
86
+ if (outputFormat === 'avif') {
87
+ return {
88
+ format: 'avif',
89
+ hasAlpha: info.channels === 4,
90
+ orientation: null,
91
+ space: info.space ?? null,
92
+ };
93
+ }
94
+
95
+ const metadata = await sharp(data, { limitInputPixels: policy.maxPixels }).metadata();
96
+ return {
97
+ format: normalizeFormat(metadata.format, metadata.compression),
98
+ hasAlpha: Boolean(metadata.hasAlpha),
99
+ orientation: metadata.orientation ?? null,
100
+ space: metadata.space ?? null,
101
+ };
102
+ }
103
+
45
104
  export async function inspectImage(input, options = {}) {
46
105
  assertBuffer(input);
106
+ assertAllowedInputFormat(input);
47
107
  const policy = validatePolicy(options.policy);
48
108
  const metadata = await sharp(input, { limitInputPixels: policy.maxPixels }).metadata();
49
109
 
@@ -139,7 +199,7 @@ export async function optimizeImage(input, options = {}) {
139
199
  };
140
200
  }
141
201
 
142
- const outputMetadata = await sharp(data, { limitInputPixels: policy.maxPixels }).metadata();
202
+ const outputMetadata = await inspectEncodedOutput(data, info, outputFormat, policy);
143
203
  if (policy.preserveAlpha && original.hasAlpha && !outputMetadata.hasAlpha) {
144
204
  throw new Error('alpha preservation guard failed');
145
205
  }
@@ -150,12 +210,12 @@ export async function optimizeImage(input, options = {}) {
150
210
  original,
151
211
  output: {
152
212
  bytes: data.length,
153
- format: normalizeFormat(outputMetadata.format, outputMetadata.compression),
213
+ format: outputMetadata.format,
154
214
  width: info.width,
155
215
  height: info.height,
156
- hasAlpha: Boolean(outputMetadata.hasAlpha),
157
- orientation: outputMetadata.orientation ?? null,
158
- space: outputMetadata.space ?? null,
216
+ hasAlpha: outputMetadata.hasAlpha,
217
+ orientation: outputMetadata.orientation,
218
+ space: outputMetadata.space,
159
219
  },
160
220
  outputFormat,
161
221
  savingsBytes,
package/src/server.js CHANGED
@@ -24,7 +24,7 @@ const OPEN_WORLD_READ_ANNOTATIONS = Object.freeze({
24
24
  export function createServer({ artifactStore = new ArtifactStore() } = {}) {
25
25
  const server = new McpServer({
26
26
  name: 'layerporter-image-optimizer',
27
- version: '0.1.0',
27
+ version: '0.1.1',
28
28
  title: 'LayerPorter Website Image Optimizer',
29
29
  websiteUrl: 'https://layerporter.com/mcp/website-image-optimizer/',
30
30
  });