@neat.is/mcp 0.9.1 → 0.9.2-dev.20260821
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/dist/index.cjs +76 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +76 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -42,10 +42,12 @@ function readDaemonRecord(path) {
|
|
|
42
42
|
}
|
|
43
43
|
return `http://localhost:${rest}`;
|
|
44
44
|
}
|
|
45
|
-
function
|
|
45
|
+
function resolveBaseUrlWithSource(env = process.env, cwd = process.cwd()) {
|
|
46
46
|
const override = env.NEAT_CORE_URL ?? env.NEAT_API_URL;
|
|
47
|
-
if (override) return override;
|
|
48
|
-
|
|
47
|
+
if (override) return { url: override, source: "env" };
|
|
48
|
+
const fromRecord = resolveFromDaemonRecord(cwd);
|
|
49
|
+
if (fromRecord !== void 0) return { url: fromRecord, source: "daemon-record" };
|
|
50
|
+
return { url: DEFAULT_BASE_URL, source: "default" };
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
// src/client.ts
|
|
@@ -72,10 +74,10 @@ async function fetchWithTimeout(url, init, timeoutMs, method, path) {
|
|
|
72
74
|
throw err;
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
|
-
function createHttpClient(baseUrl2,
|
|
77
|
+
function createHttpClient(baseUrl2, bearerToken2, timeoutMs) {
|
|
76
78
|
const root = baseUrl2.replace(/\/$/, "");
|
|
77
79
|
const deadline = resolveTimeoutMs(timeoutMs);
|
|
78
|
-
const authHeader =
|
|
80
|
+
const authHeader = bearerToken2 && bearerToken2.length > 0 ? { authorization: `Bearer ${bearerToken2}` } : {};
|
|
79
81
|
return {
|
|
80
82
|
async get(path) {
|
|
81
83
|
const res = await fetchWithTimeout(
|
|
@@ -149,6 +151,61 @@ var RequestTimeoutError = class extends Error {
|
|
|
149
151
|
}
|
|
150
152
|
};
|
|
151
153
|
|
|
154
|
+
// src/endpoint-check.ts
|
|
155
|
+
var PROBE_TIMEOUT_MS = 2500;
|
|
156
|
+
async function checkEndpointIsNeat(baseUrl2, opts = {}) {
|
|
157
|
+
const root = baseUrl2.replace(/\/$/, "");
|
|
158
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
159
|
+
const headers = opts.bearerToken && opts.bearerToken.length > 0 ? { authorization: `Bearer ${opts.bearerToken}` } : {};
|
|
160
|
+
let res;
|
|
161
|
+
try {
|
|
162
|
+
res = await doFetch(`${root}/health`, {
|
|
163
|
+
headers,
|
|
164
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? PROBE_TIMEOUT_MS)
|
|
165
|
+
});
|
|
166
|
+
} catch (err) {
|
|
167
|
+
return { kind: "unreachable", detail: errMessage(err) };
|
|
168
|
+
}
|
|
169
|
+
if (res.status === 401 || res.status === 403 || res.status >= 500) {
|
|
170
|
+
return { kind: "unreachable", detail: `HTTP ${res.status}` };
|
|
171
|
+
}
|
|
172
|
+
const contentType = res.headers.get("content-type") ?? "unknown";
|
|
173
|
+
const body = await res.text().catch(() => "");
|
|
174
|
+
if (isNeatHealth(body)) return { kind: "neat" };
|
|
175
|
+
return { kind: "foreign", status: res.status, contentType };
|
|
176
|
+
}
|
|
177
|
+
function isNeatHealth(body) {
|
|
178
|
+
let parsed;
|
|
179
|
+
try {
|
|
180
|
+
parsed = JSON.parse(body);
|
|
181
|
+
} catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
if (parsed === null || typeof parsed !== "object") return false;
|
|
185
|
+
const rec = parsed;
|
|
186
|
+
return rec.ok === true && typeof rec.uptimeMs === "number";
|
|
187
|
+
}
|
|
188
|
+
function errMessage(err) {
|
|
189
|
+
return err instanceof Error ? err.message : String(err);
|
|
190
|
+
}
|
|
191
|
+
function describeForeignEndpoint(url, source, check) {
|
|
192
|
+
const how = {
|
|
193
|
+
env: "from NEAT_CORE_URL / NEAT_API_URL",
|
|
194
|
+
"daemon-record": "from a neat-out/daemon.json record found while walking up from the working directory",
|
|
195
|
+
default: "from the default http://localhost:8080 \u2014 no NEAT_CORE_URL was set and no neat-out/daemon.json was found walking up from the working directory"
|
|
196
|
+
};
|
|
197
|
+
const fix = {
|
|
198
|
+
env: "Check that NEAT_CORE_URL points at a running NEAT daemon.",
|
|
199
|
+
"daemon-record": "The REST port recorded in that daemon.json is now answered by something else \u2014 the record is stale. Restart the project daemon, or set NEAT_CORE_URL to its address.",
|
|
200
|
+
default: "Another service \u2014 not NEAT \u2014 is answering on :8080. Run the MCP server from inside a NEAT project so it can discover neat-out/daemon.json, or set NEAT_CORE_URL to your daemon's address."
|
|
201
|
+
};
|
|
202
|
+
return [
|
|
203
|
+
`NEAT MCP server: resolved the daemon at ${url} (${how[source]}), but it does not look like NEAT \u2014 a probe of ${url}/health returned HTTP ${check.status} (${check.contentType}), not NEAT's health JSON.`,
|
|
204
|
+
fix[source],
|
|
205
|
+
"If this really is your NEAT daemon (for example behind a proxy that rewrites /health), set NEAT_SKIP_ENDPOINT_CHECK=1 to bypass this check."
|
|
206
|
+
].join("\n\n");
|
|
207
|
+
}
|
|
208
|
+
|
|
152
209
|
// src/resources.ts
|
|
153
210
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
154
211
|
var NODE_RESOURCE_MIME = "application/json";
|
|
@@ -1067,9 +1124,11 @@ async function neatRollbackExtension(client2, input) {
|
|
|
1067
1124
|
}
|
|
1068
1125
|
|
|
1069
1126
|
// src/index.ts
|
|
1070
|
-
var
|
|
1127
|
+
var resolved = resolveBaseUrlWithSource();
|
|
1128
|
+
var baseUrl = resolved.url;
|
|
1071
1129
|
var authToken = process.env.NEAT_AUTH_TOKEN;
|
|
1072
|
-
var
|
|
1130
|
+
var bearerToken = authToken && authToken.length > 0 ? authToken : void 0;
|
|
1131
|
+
var client = createHttpClient(baseUrl, bearerToken);
|
|
1073
1132
|
var defaultProject = process.env.NEAT_DEFAULT_PROJECT;
|
|
1074
1133
|
var projectFor = (input) => input.project ?? defaultProject;
|
|
1075
1134
|
var projectField = import_zod.z.string().optional().describe(
|
|
@@ -1282,7 +1341,17 @@ var resourceRegistration = registerResources(server, client, {
|
|
|
1282
1341
|
...incidentsPollMs !== void 0 ? { incidentsPollMs } : {},
|
|
1283
1342
|
...defaultProject ? { project: defaultProject } : {}
|
|
1284
1343
|
});
|
|
1344
|
+
async function guardEndpoint() {
|
|
1345
|
+
const skip = process.env.NEAT_SKIP_ENDPOINT_CHECK;
|
|
1346
|
+
if (skip === "1" || skip === "true") return;
|
|
1347
|
+
const check = await checkEndpointIsNeat(baseUrl, { bearerToken });
|
|
1348
|
+
if (check.kind === "foreign") {
|
|
1349
|
+
console.error(describeForeignEndpoint(baseUrl, resolved.source, check));
|
|
1350
|
+
process.exit(1);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1285
1353
|
async function main() {
|
|
1354
|
+
await guardEndpoint();
|
|
1286
1355
|
const transport = new import_stdio.StdioServerTransport();
|
|
1287
1356
|
await server.connect(transport);
|
|
1288
1357
|
}
|