@pauhu/mcp 3.0.6 → 3.0.7
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 +1 -1
- package/bridge.mjs +31 -11
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Connect any MCP client to Pauhu. Every answer carries its source.
|
|
|
14
14
|
}
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
No key needed to start: without one the daily try applies. When it is used, the reply says how to continue (a prepaid balance token in `PAUHU_TOKEN`, with the endpoint it names in `PAUHU_MCP_URL`). Set `PAUHU_TOKEN` to a key you already hold to use it from the start. If the service asks for an account, Pauhu prints a sign-in link to open in your browser.
|
|
18
18
|
|
|
19
19
|
Prices per delivered result: https://api.pauhu.ai/v1/lds/outputs
|
|
20
20
|
|
package/bridge.mjs
CHANGED
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
* forwarded over StreamableHTTP to the Pauhu MCP service at PAUHU_MCP_URL, which runs them; what
|
|
6
6
|
* the service lists appears here at once, with no bundle rebuild.
|
|
7
7
|
*
|
|
8
|
-
* Auth: PAUHU_TOKEN, else PAUHU_REFRESH_TOKEN exchanged at auth.pauhu.ai
|
|
9
|
-
*
|
|
10
|
-
* sign-in
|
|
11
|
-
* can show it.
|
|
12
|
-
*
|
|
8
|
+
* Auth: PAUHU_TOKEN, else PAUHU_REFRESH_TOKEN exchanged at auth.pauhu.ai; with neither, the bridge
|
|
9
|
+
* connects with no key and the service's daily try applies. OAuth Device Flow starts only after the
|
|
10
|
+
* service answers 401. That sign-in waits a bounded time and otherwise returns the sign-in link as
|
|
11
|
+
* the error so the host can show it. When the daily try is used (HTTP 429, JSON-RPC -32029, nothing
|
|
12
|
+
* run or billed) the service's own next steps are passed on, never retried. Every credential the
|
|
13
|
+
* bridge holds is redacted from any text it writes and from every result it passes on; sign-in
|
|
14
|
+
* links must be on the auth origin.
|
|
13
15
|
*
|
|
14
16
|
* Retry: list/read/get requests are read-only and are retried once after any failure. tools/call
|
|
15
17
|
* is retried once only when the service cannot have run it: the connect step failed, or the service
|
|
@@ -31,7 +33,7 @@ const MCP_URL = process.env.PAUHU_MCP_URL ?? "https://api.pauhu.ai/mcp";
|
|
|
31
33
|
const CALL_MS = Number(process.env.PAUHU_CALL_TIMEOUT_MS ?? 120_000);
|
|
32
34
|
const AUTH_MS = 15_000; // one token or device-code HTTP call
|
|
33
35
|
const WAIT_MS = 45_000; // how long one request waits for a browser sign-in in progress
|
|
34
|
-
const VERSION = "3.0.
|
|
36
|
+
const VERSION = "3.0.7";
|
|
35
37
|
|
|
36
38
|
// An omitted optional user_config can arrive as the literal "${user_config.x}"; that is no value.
|
|
37
39
|
const given = (v) => (v && !v.startsWith("${") ? v : null);
|
|
@@ -148,15 +150,17 @@ async function ensureToken() {
|
|
|
148
150
|
" and then try again"); // built at the deadline, once the device flow has set the URL
|
|
149
151
|
}
|
|
150
152
|
|
|
153
|
+
let signInAsked = false; // set once the service answered 401; until then no key is sent
|
|
154
|
+
|
|
151
155
|
let conn = null; // promise of the one upstream client; shared by concurrent requests
|
|
152
156
|
function client() {
|
|
153
157
|
if (conn) return conn;
|
|
154
158
|
const mine = (async () => {
|
|
155
|
-
const bearer = await ensureToken();
|
|
159
|
+
const bearer = token || refreshToken || signInAsked ? await ensureToken() : null;
|
|
156
160
|
const c = new Client({ name: "pauhu", version: VERSION }, { capabilities: {} });
|
|
157
161
|
try {
|
|
158
162
|
await withTimeout(c.connect(new StreamableHTTPClientTransport(new URL(MCP_URL), {
|
|
159
|
-
requestInit: { headers: { Authorization: `Bearer ${bearer}` } },
|
|
163
|
+
requestInit: { headers: bearer ? { Authorization: `Bearer ${bearer}` } : {} },
|
|
160
164
|
})), 60_000, "Pauhu service did not answer in time");
|
|
161
165
|
} catch (e) {
|
|
162
166
|
try { await c.close(); } catch { /* not open */ }
|
|
@@ -183,9 +187,22 @@ async function on(c, fn) { // run one request on clien
|
|
|
183
187
|
|
|
184
188
|
const status = (e) => (e instanceof UnauthorizedError ? 401 : e instanceof StreamableHTTPError && e.code > 0 ? e.code : null);
|
|
185
189
|
|
|
190
|
+
// The service's daily-try refusal: HTTP 429 carrying JSON-RPC -32029 and its next steps; nothing was run or billed.
|
|
191
|
+
function tryUsed(e) {
|
|
192
|
+
if (status(e) !== 429) return null;
|
|
193
|
+
const at = e.message.indexOf("{");
|
|
194
|
+
let b = null;
|
|
195
|
+
try { b = JSON.parse(e.message.slice(at)); } catch { /* not the JSON-RPC body */ }
|
|
196
|
+
return at >= 0 && b?.error?.code === -32029 && typeof b.error.message === "string" ? b.error : null;
|
|
197
|
+
}
|
|
198
|
+
const BRIDGE_HINT = " With this bridge, put the token in PAUHU_TOKEN and the endpoint in PAUHU_MCP_URL.";
|
|
199
|
+
|
|
186
200
|
function publicMessage(e) {
|
|
187
201
|
if (e instanceof BridgeError) return e.message;
|
|
202
|
+
const used = tryUsed(e);
|
|
203
|
+
if (used) return scrub(used.message).slice(0, 600) + BRIDGE_HINT; // scrub, not redact: its "Bearer <token>" is a placeholder
|
|
188
204
|
const s = status(e);
|
|
205
|
+
if (s === 429) return "Pauhu service is busy (HTTP 429); nothing was retried, try again later";
|
|
189
206
|
if (s === 401 || s === 403) return "Pauhu sign-in expired or not permitted; restart the Pauhu extension to sign in again";
|
|
190
207
|
if (s) return `Pauhu service error (HTTP ${s})`;
|
|
191
208
|
if (e instanceof McpError) return redact(e.message); // the Pauhu server's own JSON-RPC error, e.g. an unknown tool
|
|
@@ -205,8 +222,9 @@ async function forward(fn, readOnly) {
|
|
|
205
222
|
const s = status(first);
|
|
206
223
|
const lost = [ErrorCode.ConnectionClosed, ErrorCode.RequestTimeout]; // no answer arrived: a transport fault
|
|
207
224
|
const answered = first instanceof McpError && !lost.includes(first.code);
|
|
208
|
-
const safe = !(first instanceof BridgeError) && !answered && (readOnly || first.connectPhase || s === 401 || s === 404);
|
|
209
|
-
if (!safe) throw first;
|
|
225
|
+
const safe = !(first instanceof BridgeError) && !answered && s !== 429 && (readOnly || first.connectPhase || s === 401 || s === 404);
|
|
226
|
+
if (!safe) throw first; // a 429 is a stop: the daily try is used or the service is busy
|
|
227
|
+
if (s === 401) signInAsked = true; // the service wants an account: the retry signs in
|
|
210
228
|
if (s === 401 && (first.bearer ?? bearer) === token) { // refused the current token: refresh once (a refused refresh
|
|
211
229
|
token = null; // token is dropped, so the retry goes to browser sign-in)
|
|
212
230
|
await refresh();
|
|
@@ -242,7 +260,9 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
242
260
|
try {
|
|
243
261
|
return scrub(await forward((c) => withTimeout(c.callTool(req.params), CALL_MS, "Pauhu tool call timed out"), false));
|
|
244
262
|
} catch (e) {
|
|
245
|
-
|
|
263
|
+
const steps = tryUsed(e)?.data?.next_steps; // machine-readable continuation from the service, passed on as is
|
|
264
|
+
const out = Array.isArray(steps) ? { error: publicMessage(e), next_steps: scrub(steps) } : { error: publicMessage(e) };
|
|
265
|
+
return { content: [{ type: "text", text: JSON.stringify(out) }], isError: true };
|
|
246
266
|
}
|
|
247
267
|
});
|
|
248
268
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pauhu/mcp",
|
|
3
|
-
"version": "3.0.
|
|
4
|
-
"description": "Pauhu: sourced answers over MCP.
|
|
3
|
+
"version": "3.0.7",
|
|
4
|
+
"description": "Pauhu: sourced answers over MCP. Try with no key (daily try); every answer carries its source.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"author": "Pauhu AI Ltd (https://pauhu.ai)",
|