mcp-optimizer 0.0.1-alpha.1 → 0.0.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 CHANGED
@@ -26,6 +26,21 @@ curl -X POST http://localhost:3000/audit -H "Content-Type: application/json" -d
26
26
  ```
27
27
 
28
28
  Notes
29
- - `src/runner/lighthouseRunner.ts` runs Lighthouse via `chrome-launcher` and returns the LHR.
30
- - `src/fix/fixer.ts` — placeholder to convert LHR into actionable fixes; integrate LLM (e.g., via `@modelcontextprotocol/sdk`) here.
29
+ This section provides an example of launching via an MCP host (stdio).
30
+
31
+ If you want to configure an MCP host to spawn the optimizer via `npx`, add a server entry like the following to your host config:
32
+
33
+ ```json
34
+ {
35
+ "mcpServers": {
36
+ "mcp-optimizer": {
37
+ "command": "npx",
38
+ "args": ["-y", "mcp-optimizer@0.0.6-alpha.1", "--","--port", "5000"]
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ This will instruct the host to spawn `npx -y mcp-optimizer@0.0.6-alpha.1 -- --port 5000` and communicate with the child over stdio.
45
+
31
46
 
@@ -1,9 +1,37 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // Lightweight CLI wrapper for the MCP Optimizer package.
4
- // This file is intentionally small so `npx mcp-optimizer` can run it.
4
+ // Accept `--port <n>` or `--port=<n>` (or `--audit-port`) to configure the server
5
+ // port when launching via `npx` (e.g. `npx -y mcp-optimizer -- --port 6000`).
5
6
 
6
7
  try {
8
+ const args = process.argv.slice(2);
9
+ for (let i = 0; i < args.length; i++) {
10
+ const a = args[i];
11
+ // Accept environment-style args like PORT=6000
12
+ if (a.includes('=') && !a.startsWith('--')) {
13
+ const [k, v] = a.split('=', 2);
14
+ if (k && v !== undefined) {
15
+ process.env[k] = v;
16
+ continue;
17
+ }
18
+ }
19
+ if (a.startsWith('--port=')) {
20
+ process.env.PORT = a.split('=')[1];
21
+ }
22
+ else if (a === '--port' && args[i + 1]) {
23
+ process.env.PORT = args[i + 1];
24
+ i++;
25
+ }
26
+ else if (a.startsWith('--audit-port=')) {
27
+ process.env.AUDIT_PORT = a.split('=')[1];
28
+ }
29
+ else if (a === '--audit-port' && args[i + 1]) {
30
+ process.env.AUDIT_PORT = args[i + 1];
31
+ i++;
32
+ }
33
+ }
34
+
7
35
  // Require built output. When installed via npm the package root
8
36
  // will contain `dist/index.js` after `npm pack` / `npm publish`.
9
37
  require('../dist/index.js');
package/dist/mcpServer.js CHANGED
@@ -39,6 +39,7 @@ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
39
39
  const zod_1 = require("zod");
40
40
  const lighthouseRunner_1 = require("./runner/lighthouseRunner");
41
41
  const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
42
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
42
43
  const http = __importStar(require("http"));
43
44
  const fixer_1 = require("./fix/fixer");
44
45
  class LighthouseMcpServer {
@@ -205,9 +206,72 @@ exports.LighthouseMcpServer = LighthouseMcpServer;
205
206
  exports.default = LighthouseMcpServer;
206
207
  async function startMcpServer() {
207
208
  // Run HTTP/SSE server
208
- const port = Number(process.env.PORT || process.env.AUDIT_PORT || 5000);
209
+ // Prefer explicit environment variables, but also allow parsing CLI args
210
+ // (e.g. when launched directly without the lightweight `bin` wrapper).
211
+ let portEnv = process.env.PORT || process.env.AUDIT_PORT;
212
+ if (!portEnv) {
213
+ const args = process.argv.slice(2);
214
+ for (let i = 0; i < args.length; i++) {
215
+ const a = args[i];
216
+ if (a.includes('=') && !a.startsWith('--')) {
217
+ const [k, v] = a.split('=', 2);
218
+ if ((k === 'PORT' || k === 'AUDIT_PORT') && v !== undefined) {
219
+ portEnv = v;
220
+ break;
221
+ }
222
+ }
223
+ if (a.startsWith('--port=')) {
224
+ portEnv = a.split('=')[1];
225
+ break;
226
+ }
227
+ else if (a === '--port' && args[i + 1]) {
228
+ portEnv = args[i + 1];
229
+ break;
230
+ }
231
+ else if (a.startsWith('--audit-port=')) {
232
+ portEnv = a.split('=')[1];
233
+ break;
234
+ }
235
+ else if (a === '--audit-port' && args[i + 1]) {
236
+ portEnv = args[i + 1];
237
+ break;
238
+ }
239
+ }
240
+ }
241
+ const port = Number(portEnv || 5000);
209
242
  const mcp = new LighthouseMcpServer();
210
243
  let sseTransport = null;
244
+ let stdioTransport = null;
245
+ // If process stdin/stdout appear to be non-TTY pipes, assume we're being
246
+ // launched as a stdio child by an MCP host and use the stdio transport.
247
+ const shouldUseStdio = (process.stdin && !process.stdin.isTTY) && (process.stdout && !process.stdout.isTTY);
248
+ if (shouldUseStdio) {
249
+ // Redirect console output to stderr to avoid corrupting the JSON stdio protocol
250
+ const _orig = { log: console.log, info: console.info, warn: console.warn };
251
+ console.log = (...args) => { process.stderr.write(args.map(String).join(' ') + '\n'); };
252
+ console.info = console.log;
253
+ console.warn = console.log;
254
+ try {
255
+ stdioTransport = new stdio_js_1.StdioServerTransport(process.stdin, process.stdout);
256
+ await mcp.connect(stdioTransport);
257
+ console.error('Stdio: connected to parent process over stdio');
258
+ // When running over stdio we do not start the HTTP server; stay alive
259
+ // and let the parent coordinate messages. Return a promise that
260
+ // resolves when the transport closes.
261
+ return new Promise((resolve, reject) => {
262
+ stdioTransport.onclose = () => resolve();
263
+ stdioTransport.onerror = (err) => reject(err);
264
+ });
265
+ }
266
+ catch (err) {
267
+ console.error('Stdio: failed to start transport, falling back to HTTP:', err);
268
+ stdioTransport = null;
269
+ // restore console in case we fall back to HTTP server mode
270
+ console.log = _orig.log;
271
+ console.info = _orig.info;
272
+ console.warn = _orig.warn;
273
+ }
274
+ }
211
275
  const pendingPosts = [];
212
276
  const { Readable, Writable } = await (async () => {
213
277
  const mod = await Promise.resolve().then(() => __importStar(require('stream')));
@@ -30,7 +30,9 @@ async function runLighthouseAudit(url, opts) {
30
30
  // Run via npx to ensure local package is used
31
31
  const cmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
32
32
  const reportJson = await new Promise((resolve, reject) => {
33
- execFile(cmd, args, { maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
33
+ // Use a shell on Windows to ensure .cmd/.bat wrappers are executed
34
+ // correctly (avoids spawn EINVAL in some environments).
35
+ execFile(cmd, args, { maxBuffer: 10 * 1024 * 1024, shell: true, env: process.env }, (err, stdout, stderr) => {
34
36
  if (err) {
35
37
  const message = stderr || (err && err.message) || String(err);
36
38
  return reject(new Error(message));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-optimizer",
3
- "version": "0.0.1-alpha.1",
3
+ "version": "0.0.1",
4
4
  "description": "An MCP server that runs Lighthouse audits and enables LLMs to automatically optimize your code.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
package/src/mcpServer.ts CHANGED
@@ -3,6 +3,7 @@ import { z } from "zod";
3
3
  import { runLighthouseAudit } from "./runner/lighthouseRunner";
4
4
  import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
5
5
  import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
6
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
7
  import * as http from 'http';
7
8
  import { IncomingMessage, ServerResponse } from 'http';
8
9
  import { autoFixFromReport } from './fix/fixer';
@@ -192,9 +193,69 @@ export default LighthouseMcpServer;
192
193
 
193
194
  export async function startMcpServer(): Promise<void> {
194
195
  // Run HTTP/SSE server
195
- const port = Number(process.env.PORT || process.env.AUDIT_PORT || 5000);
196
+ // Prefer explicit environment variables, but also allow parsing CLI args
197
+ // (e.g. when launched directly without the lightweight `bin` wrapper).
198
+ let portEnv = process.env.PORT || process.env.AUDIT_PORT;
199
+ if (!portEnv) {
200
+ const args = process.argv.slice(2);
201
+ for (let i = 0; i < args.length; i++) {
202
+ const a = args[i];
203
+ if (a.includes('=') && !a.startsWith('--')) {
204
+ const [k, v] = a.split('=', 2);
205
+ if ((k === 'PORT' || k === 'AUDIT_PORT') && v !== undefined) {
206
+ portEnv = v;
207
+ break;
208
+ }
209
+ }
210
+ if (a.startsWith('--port=')) {
211
+ portEnv = a.split('=')[1];
212
+ break;
213
+ } else if (a === '--port' && args[i + 1]) {
214
+ portEnv = args[i + 1];
215
+ break;
216
+ } else if (a.startsWith('--audit-port=')) {
217
+ portEnv = a.split('=')[1];
218
+ break;
219
+ } else if (a === '--audit-port' && args[i + 1]) {
220
+ portEnv = args[i + 1];
221
+ break;
222
+ }
223
+ }
224
+ }
225
+
226
+ const port = Number(portEnv || 5000);
196
227
  const mcp = new LighthouseMcpServer();
197
228
  let sseTransport: SSEServerTransport | null = null;
229
+ let stdioTransport: StdioServerTransport | null = null;
230
+ // If process stdin/stdout appear to be non-TTY pipes, assume we're being
231
+ // launched as a stdio child by an MCP host and use the stdio transport.
232
+ const shouldUseStdio = (process.stdin && !process.stdin.isTTY) && (process.stdout && !process.stdout.isTTY);
233
+ if (shouldUseStdio) {
234
+ // Redirect console output to stderr to avoid corrupting the JSON stdio protocol
235
+ const _orig = { log: console.log, info: console.info, warn: console.warn };
236
+ console.log = (...args: any[]) => { process.stderr.write(args.map(String).join(' ') + '\n'); };
237
+ console.info = console.log;
238
+ console.warn = console.log;
239
+ try {
240
+ stdioTransport = new StdioServerTransport(process.stdin, process.stdout);
241
+ await mcp.connect(stdioTransport as unknown as Transport);
242
+ console.error('Stdio: connected to parent process over stdio');
243
+ // When running over stdio we do not start the HTTP server; stay alive
244
+ // and let the parent coordinate messages. Return a promise that
245
+ // resolves when the transport closes.
246
+ return new Promise((resolve, reject) => {
247
+ stdioTransport!.onclose = () => resolve();
248
+ stdioTransport!.onerror = (err: any) => reject(err);
249
+ });
250
+ } catch (err) {
251
+ console.error('Stdio: failed to start transport, falling back to HTTP:', err);
252
+ stdioTransport = null;
253
+ // restore console in case we fall back to HTTP server mode
254
+ console.log = _orig.log;
255
+ console.info = _orig.info;
256
+ console.warn = _orig.warn;
257
+ }
258
+ }
198
259
  const pendingPosts: Array<{ body: string; url: string | undefined; headers: any }> = [];
199
260
 
200
261
  const { Readable, Writable } = await (async () => {
@@ -31,7 +31,9 @@ export async function runLighthouseAudit(
31
31
  const cmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
32
32
 
33
33
  const reportJson: string = await new Promise((resolve, reject) => {
34
- execFile(cmd, args, { maxBuffer: 10 * 1024 * 1024 }, (err: any, stdout: string, stderr: string) => {
34
+ // Use a shell on Windows to ensure .cmd/.bat wrappers are executed
35
+ // correctly (avoids spawn EINVAL in some environments).
36
+ execFile(cmd, args, { maxBuffer: 10 * 1024 * 1024, shell: true, env: process.env }, (err: any, stdout: string, stderr: string) => {
35
37
  if (err) {
36
38
  const message = stderr || (err && err.message) || String(err);
37
39
  return reject(new Error(message));
package/dist/server.js DELETED
@@ -1,79 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.startHttpServer = startHttpServer;
37
- const http = __importStar(require("http"));
38
- const lighthouseRunner_1 = require("./runner/lighthouseRunner");
39
- const fixer_1 = require("./fix/fixer");
40
- async function startHttpServer(port) {
41
- const p = Number(port ?? process.env.PORT ?? process.env.AUDIT_PORT ?? 5000);
42
- const requestHandler = async (req, res) => {
43
- if (req.method === 'POST' && req.url === '/audit') {
44
- let body = '';
45
- for await (const chunk of req) {
46
- body += chunk;
47
- }
48
- const parsed = JSON.parse(body || '{}');
49
- const url = parsed.url;
50
- if (!url) {
51
- res.writeHead(400);
52
- res.end('missing url');
53
- return;
54
- }
55
- try {
56
- const report = await (0, lighthouseRunner_1.runLighthouseAudit)(url);
57
- const fix = await (0, fixer_1.autoFixFromReport)(report);
58
- res.writeHead(200, { 'Content-Type': 'application/json' });
59
- res.end(JSON.stringify({ summary: report.lhr?.categories || null, fix }));
60
- }
61
- catch (err) {
62
- res.writeHead(500);
63
- res.end(String(err));
64
- }
65
- }
66
- else {
67
- res.writeHead(200, { 'Content-Type': 'text/plain' });
68
- res.end('MCP Optimizer running — POST /audit { "url": "https://..." }');
69
- }
70
- };
71
- const server = http.createServer(requestHandler);
72
- return new Promise((resolve, reject) => {
73
- server.listen(p, () => {
74
- console.log(`Server listening on ${p}`);
75
- resolve(server);
76
- });
77
- server.on('error', reject);
78
- });
79
- }