@hubex/mcp 0.6.0 → 0.7.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/dist/auth.js CHANGED
@@ -175,7 +175,12 @@ class RefreshingAuth {
175
175
  }
176
176
  /**
177
177
  * A service-token login never carries a refresh token, so ask for one.
178
- * Best-effort: without it we simply re-authenticate when the access token expires.
178
+ * Best-effort for "service"/"login": credentials were already proven by the
179
+ * preceding login call, so a failure here just means retrying at expiry.
180
+ * Fatal for "token": the user-typed token is otherwise never round-tripped
181
+ * against the server, so this request is the only thing that can catch a
182
+ * token issued for the wrong environment (dev/stg/prod) — swallowing it
183
+ * would report success on a token that doesn't actually work.
179
184
  */
180
185
  async acquireRefreshToken() {
181
186
  try {
@@ -186,6 +191,8 @@ class RefreshingAuth {
186
191
  this.absorb(result);
187
192
  }
188
193
  catch (err) {
194
+ if (this.config.authMode === "token")
195
+ throw err;
189
196
  console.error("[hubex-mcp] could not obtain a refresh token; will re-authenticate on expiry: " +
190
197
  `${err.message}`);
191
198
  }
@@ -201,7 +208,13 @@ class RefreshingAuth {
201
208
  const res = await fetch(url, init);
202
209
  if (!res.ok) {
203
210
  const body = await res.text().catch(() => "");
204
- throw new Error(`${init.method ?? "GET"} ${url} failed (HTTP ${res.status}): ${body.slice(0, 500)}`);
211
+ let message = `${init.method ?? "GET"} ${url} failed (HTTP ${res.status}): ${body.slice(0, 500)}`;
212
+ if (res.status === 401 || res.status === 403) {
213
+ message +=
214
+ ` Сейчас выбрано окружение HUBEX_ENV=${this.config.env}. Если токен получен в другом ` +
215
+ `окружении (dev/stg/prod), авторизация не пройдёт — проверь, что токен именно из ${this.config.env}.`;
216
+ }
217
+ throw new Error(message);
205
218
  }
206
219
  return (await res.json());
207
220
  }
package/dist/http.js CHANGED
@@ -3,6 +3,7 @@
3
3
  * injects auth + `X-Application-ID`, and normalizes responses and errors.
4
4
  */
5
5
  import { maskPii } from "./pii/mask.js";
6
+ import { SET_TOKEN_TOOL } from "./token-prompt.js";
6
7
  /**
7
8
  * Заголовки, которые клиент проставляет сам. Подмена `Authorization` или
8
9
  * `X-Application-ID` сменила бы того, от чьего имени идёт запрос, поэтому
@@ -20,15 +21,15 @@ export class HubexApiError extends Error {
20
21
  service;
21
22
  path;
22
23
  payload;
23
- constructor(status, service, path, payload) {
24
- super(HubexApiError.format(status, service, path, payload));
24
+ constructor(status, service, path, payload, env) {
25
+ super(HubexApiError.format(status, service, path, payload, env));
25
26
  this.status = status;
26
27
  this.service = service;
27
28
  this.path = path;
28
29
  this.payload = payload;
29
30
  this.name = "HubexApiError";
30
31
  }
31
- static format(status, service, path, payload) {
32
+ static format(status, service, path, payload, env) {
32
33
  let detail = "";
33
34
  if (typeof payload === "string") {
34
35
  detail = payload;
@@ -41,7 +42,17 @@ export class HubexApiError extends Error {
41
42
  else if (payload && typeof payload === "object") {
42
43
  detail = JSON.stringify(payload).slice(0, 800);
43
44
  }
44
- return `HubEx ${service} ${path} -> HTTP ${status}${detail ? `: ${detail}` : ""}`;
45
+ let message = `HubEx ${service} ${path} -> HTTP ${status}${detail ? `: ${detail}` : ""}`;
46
+ // 401 здесь чаще всего не «токен протух», а «токен не из этого окружения»:
47
+ // dev/stg/prod выдают JWT с разными ключами подписи, и токен из другого
48
+ // контура падает именно так, без более внятной причины от самого HubEx.
49
+ if (status === 401 && env) {
50
+ message +=
51
+ ` Сейчас выбрано окружение HUBEX_ENV=${env}. Если токен был получен в другом окружении ` +
52
+ `(dev/stg/prod), авторизация не пройдёт — убедись, что токен именно из ${env}, и при ` +
53
+ `необходимости передай новый через ${SET_TOKEN_TOOL}.`;
54
+ }
55
+ return message;
45
56
  }
46
57
  }
47
58
  function buildQueryString(query) {
@@ -111,7 +122,7 @@ export class HubexClient {
111
122
  exposed[name] = value;
112
123
  }
113
124
  if (!res.ok) {
114
- throw new HubexApiError(res.status, req.service, req.path, visible ?? text);
125
+ throw new HubexApiError(res.status, req.service, req.path, visible ?? text, this.config.env);
115
126
  }
116
127
  return { status: res.status, data: visible, headers: exposed };
117
128
  }
@@ -30,7 +30,9 @@ export function createElicitPrompter(server) {
30
30
  },
31
31
  });
32
32
  if (result.action !== "accept") {
33
- throw new Error("Ввод токена отменён — авторизоваться в HubEx не удалось.");
33
+ throw new Error("Ввод токена отменён — авторизоваться в HubEx не удалось. " +
34
+ "Если клиент заявляет поддержку elicitation, но не показывает форму ввода " +
35
+ `(так себя ведёт часть хостов), вызови инструмент ${SET_TOKEN_TOOL} и передай access-токен HubEx напрямую.`);
34
36
  }
35
37
  const token = result.content?.token;
36
38
  if (typeof token !== "string" || token.trim() === "") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubex/mcp",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "description": "MCP server for managing HubEx test data (tasks, assets, companies, users) via the HubEx REST API",
6
6
  "author": "HubEx Team",