@pauhu/mcp 3.0.5 → 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.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/bridge.mjs +32 -12
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Machines ask, Pauhu answers with the source. Try free, pay per result.
4
4
 
5
- Connect any MCP client to Pauhu. Every answer carries its source, or says plainly that none was found.
5
+ Connect any MCP client to Pauhu. Every answer carries its source.
6
6
 
7
7
  ## Use
8
8
 
@@ -14,7 +14,7 @@ Connect any MCP client to Pauhu. Every answer carries its source, or says plainl
14
14
  }
15
15
  ```
16
16
 
17
- On first use you sign in with your browser. Or set `PAUHU_TOKEN` to a key from https://pauhu.eu/account.
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, else OAuth Device Flow.
9
- * Sign-in is lazy: the stdio server answers initialize at once, and the first request starts the
10
- * sign-in, waits a bounded time, and otherwise returns the sign-in link as the error so the host
11
- * can show it. Every credential the bridge holds is redacted from any text it writes and from
12
- * every result it passes on; sign-in links must be on the auth origin.
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.5";
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();
156
- const c = new Client({ name: "pauhu-mcpb", version: VERSION }, { capabilities: {} });
159
+ const bearer = token || refreshToken || signInAsked ? await ensureToken() : null;
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
- return { content: [{ type: "text", text: JSON.stringify({ error: publicMessage(e) }) }], isError: true };
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.5",
4
- "description": "Pauhu: sourced answers over MCP. Every answer carries its source or says plainly it found none.",
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)",