@imenam/mcp-feature-analyzer 1.0.0 → 1.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 +4 -4
- package/dist/src/gui-worker.js +4 -18
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/gui/dist/assets/index-Db7Hu3tM.js +72 -0
- package/gui/dist/index.html +1 -1
- package/package.json +4 -3
- package/dist/src/core/request-guard.d.ts +0 -32
- package/dist/src/core/request-guard.js +0 -72
- package/gui/dist/assets/index-CU9pNYDp.js +0 -72
package/gui/dist/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Feature Analyzer</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/index-
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-Db7Hu3tM.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="./assets/index-Du5eL78F.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imenam/mcp-feature-analyzer",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "MCP server that helps a human review a feature developed by an AI agent: summary, changed files, frozen diff, findings ranked by severity, diagrams, and structured feedback the agent reads back.",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"build": "npm run build:backend && npm run build:gui",
|
|
12
12
|
"start": "node dist/src/index.js",
|
|
13
13
|
"prepublishOnly": "npm run build",
|
|
14
|
-
"release": "npm version patch && npm publish && git push --follow-tags"
|
|
14
|
+
"release": "npm version patch && npm publish && git push --follow-tags",
|
|
15
|
+
"prebuild": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\""
|
|
15
16
|
},
|
|
16
17
|
"keywords": [
|
|
17
18
|
"mcp",
|
|
@@ -43,7 +44,7 @@
|
|
|
43
44
|
],
|
|
44
45
|
"dependencies": {
|
|
45
46
|
"@hono/node-server": "^2.1.3",
|
|
46
|
-
"@imenam/mcp-gui-interface": "^1.0.
|
|
47
|
+
"@imenam/mcp-gui-interface": "^1.0.13",
|
|
47
48
|
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
48
49
|
"dotenv": "^18.0.4",
|
|
49
50
|
"hono": "^4.13.11",
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/** Interface d'écoute du GUI worker : la boucle locale uniquement. */
|
|
2
|
-
export declare const LISTEN_HOST = "127.0.0.1";
|
|
3
|
-
/** Refus d'une requête : code HTTP et message renvoyé au client. */
|
|
4
|
-
export interface GuardRejection {
|
|
5
|
-
status: 403 | 415;
|
|
6
|
-
error: string;
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* Nom d'hôte d'un en-tête `Host` ou d'une URL d'origine, sans port ni crochets,
|
|
10
|
-
* en minuscules. Renvoie null quand la valeur n'est pas exploitable.
|
|
11
|
-
*/
|
|
12
|
-
export declare function hostnameOf(hostHeader: string): string | null;
|
|
13
|
-
/**
|
|
14
|
-
* Noms d'hôte sous lesquels la GUI est légitimement appelée : boucle locale,
|
|
15
|
-
* nom et adresses de la machine (accès par le proxy depuis le réseau, qui
|
|
16
|
-
* transmet l'en-tête `Host` d'origine) et hôte du proxy.
|
|
17
|
-
*/
|
|
18
|
-
export declare function allowedHostnames(proxyUrl: string | undefined): Set<string>;
|
|
19
|
-
/**
|
|
20
|
-
* Contrôle d'une requête entrante.
|
|
21
|
-
* - L'en-tête `Host` doit désigner un hôte autorisé : une page tierce qui fait
|
|
22
|
-
* pointer son domaine vers 127.0.0.1 (DNS rebinding) est refusée.
|
|
23
|
-
* - Une requête de modification exige `Content-Type: application/json`, qu'un
|
|
24
|
-
* navigateur ne peut envoyer vers une autre origine sans requête préalable
|
|
25
|
-
* CORS, refusée ici ; une origine différente de l'hôte appelé est refusée.
|
|
26
|
-
*/
|
|
27
|
-
export declare function checkRequest(request: {
|
|
28
|
-
method: string;
|
|
29
|
-
host: string | undefined;
|
|
30
|
-
origin: string | undefined;
|
|
31
|
-
contentType: string | undefined;
|
|
32
|
-
}, allowed: Set<string>): GuardRejection | null;
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import os from "os";
|
|
2
|
-
/** Interface d'écoute du GUI worker : la boucle locale uniquement. */
|
|
3
|
-
export const LISTEN_HOST = "127.0.0.1";
|
|
4
|
-
/** Méthodes HTTP sans effet de bord, exemptées du contrôle des modifications. */
|
|
5
|
-
const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
|
|
6
|
-
/**
|
|
7
|
-
* Nom d'hôte d'un en-tête `Host` ou d'une URL d'origine, sans port ni crochets,
|
|
8
|
-
* en minuscules. Renvoie null quand la valeur n'est pas exploitable.
|
|
9
|
-
*/
|
|
10
|
-
export function hostnameOf(hostHeader) {
|
|
11
|
-
const value = hostHeader.trim().toLowerCase();
|
|
12
|
-
if (value === "")
|
|
13
|
-
return null;
|
|
14
|
-
try {
|
|
15
|
-
return new URL(`http://${value}`).hostname.replace(/^\[(.*)\]$/, "$1");
|
|
16
|
-
}
|
|
17
|
-
catch {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Noms d'hôte sous lesquels la GUI est légitimement appelée : boucle locale,
|
|
23
|
-
* nom et adresses de la machine (accès par le proxy depuis le réseau, qui
|
|
24
|
-
* transmet l'en-tête `Host` d'origine) et hôte du proxy.
|
|
25
|
-
*/
|
|
26
|
-
export function allowedHostnames(proxyUrl) {
|
|
27
|
-
const names = new Set(["localhost", "127.0.0.1", "::1"]);
|
|
28
|
-
names.add(os.hostname().toLowerCase());
|
|
29
|
-
for (const addresses of Object.values(os.networkInterfaces())) {
|
|
30
|
-
for (const address of addresses ?? [])
|
|
31
|
-
names.add(address.address.toLowerCase());
|
|
32
|
-
}
|
|
33
|
-
if (proxyUrl) {
|
|
34
|
-
const proxyHost = hostnameOf(new URL(proxyUrl).host);
|
|
35
|
-
if (proxyHost !== null)
|
|
36
|
-
names.add(proxyHost);
|
|
37
|
-
}
|
|
38
|
-
return names;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Contrôle d'une requête entrante.
|
|
42
|
-
* - L'en-tête `Host` doit désigner un hôte autorisé : une page tierce qui fait
|
|
43
|
-
* pointer son domaine vers 127.0.0.1 (DNS rebinding) est refusée.
|
|
44
|
-
* - Une requête de modification exige `Content-Type: application/json`, qu'un
|
|
45
|
-
* navigateur ne peut envoyer vers une autre origine sans requête préalable
|
|
46
|
-
* CORS, refusée ici ; une origine différente de l'hôte appelé est refusée.
|
|
47
|
-
*/
|
|
48
|
-
export function checkRequest(request, allowed) {
|
|
49
|
-
const hostname = request.host === undefined ? null : hostnameOf(request.host);
|
|
50
|
-
if (hostname === null || !allowed.has(hostname)) {
|
|
51
|
-
return { status: 403, error: `Host "${request.host ?? ""}" is not allowed: open the GUI through the proxy or http://localhost.` };
|
|
52
|
-
}
|
|
53
|
-
if (SAFE_METHODS.has(request.method.toUpperCase()))
|
|
54
|
-
return null;
|
|
55
|
-
const mediaType = request.contentType?.split(";")[0].trim().toLowerCase();
|
|
56
|
-
if (mediaType !== "application/json") {
|
|
57
|
-
return { status: 415, error: `Content-Type must be application/json, got "${request.contentType ?? ""}".` };
|
|
58
|
-
}
|
|
59
|
-
if (request.origin === undefined)
|
|
60
|
-
return null;
|
|
61
|
-
let originHost;
|
|
62
|
-
try {
|
|
63
|
-
originHost = new URL(request.origin).host.toLowerCase();
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
return { status: 403, error: `Cross-origin request refused: origin "${request.origin}" is not a valid URL.` };
|
|
67
|
-
}
|
|
68
|
-
if (originHost !== request.host.trim().toLowerCase()) {
|
|
69
|
-
return { status: 403, error: `Cross-origin request refused: origin "${request.origin}" does not match host "${request.host}".` };
|
|
70
|
-
}
|
|
71
|
-
return null;
|
|
72
|
-
}
|