lauther-mcp 0.2.0 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +18 -0
  2. package/package.json +3 -3
  3. package/src/index.js +57 -2
package/README.md CHANGED
@@ -87,6 +87,24 @@ fetched the agent still gets the text and the link.
87
87
 
88
88
  Same options and the same four outcomes as `get_location`.
89
89
 
90
+ ### `ask_text`
91
+
92
+ Asks you a short question and waits for you to type the answer on your phone:
93
+ a verification code, a choice, a name, a fact only you know. The prompt shows
94
+ the question and a field, with **Reply** and **Decline**. Answers run to 500
95
+ characters, so it is for short things.
96
+
97
+ | Result | What the agent is told |
98
+ | --- | --- |
99
+ | `ANSWERED` | exactly what you typed, in quotes |
100
+ | `DECLINED` | do not retry and do not get the answer another way |
101
+ | `NO ANSWER` | silence is not consent; nothing was sent |
102
+ | `EXPIRED` | the request lapsed on your phone; nothing was sent |
103
+
104
+ Options: `question` (default "Answer a question?"), `details`,
105
+ `placeholder` (a hint in the field, such as "6-digit code"),
106
+ `require_biometric`, `timeout_seconds` (default 120).
107
+
90
108
  ## Telling your agent when to use it
91
109
 
92
110
  Most models won't reach for an approval tool unless asked. Something like this
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lauther-mcp",
3
3
  "mcpName": "io.github.csanadymiklos/lauther",
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "description": "MCP server that lets an AI agent ask its owner for approval on their phone before doing something irreversible.",
6
6
  "keywords": [
7
7
  "mcp",
@@ -20,7 +20,7 @@
20
20
  "author": "Dipling Kft.",
21
21
  "type": "module",
22
22
  "bin": {
23
- "lauther-mcp": "./src/index.js"
23
+ "lauther-mcp": "src/index.js"
24
24
  },
25
25
  "main": "./src/index.js",
26
26
  "files": [
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@modelcontextprotocol/sdk": "^1.30.0",
38
- "lauther-client": "^0.3.0",
38
+ "lauther-client": "^0.4.0",
39
39
  "zod": "^3.25.0"
40
40
  },
41
41
  "repository": {
package/src/index.js CHANGED
@@ -2,13 +2,14 @@
2
2
  /**
3
3
  * lauther-mcp — an MCP server that puts a human in the loop, on their phone.
4
4
  *
5
- * Four tools, each one thing an agent needs a person for:
5
+ * Five tools, each one thing an agent needs a person for:
6
6
  *
7
7
  * ask_approval the agent is about to do something irreversible and
8
8
  * asks first. Blocks until a person answers.
9
9
  * send_notification "the migration finished", "the build broke".
10
10
  * get_location "where are you?" - one position, shared on a tap.
11
11
  * request_photo "show me" - one picture, taken or picked on the phone.
12
+ * ask_text "what is the code?" - a short answer, typed on the phone.
12
13
  *
13
14
  * The interesting one is ask_approval. Lauther's /v1/approve long-polls for at
14
15
  * most 25 seconds, which suits a shell one-liner and not an agent whose owner
@@ -74,7 +75,7 @@ const confirmation = (out) => (out.confirmedBy ? ` Confirmed by ${out.confirmedB
74
75
  * The non-yes outcomes of a request, worded for an agent. Returns null when
75
76
  * the person did approve, so the caller can describe what was shared.
76
77
  *
77
- * @param {string} what "location" or "photo", for the sentence
78
+ * @param {string} what "location", "photo" or "answer", for the sentence
78
79
  */
79
80
  function notShared(out, what) {
80
81
  if (out.response === "approve") return null;
@@ -321,6 +322,60 @@ export function buildServer() {
321
322
  },
322
323
  );
323
324
 
325
+ server.registerTool(
326
+ "ask_text",
327
+ {
328
+ title: "Ask a human a short question",
329
+ description:
330
+ "Ask the human who owns this phone a short question and wait for them " +
331
+ "to type the answer on it. Use it for something only they know: a " +
332
+ "verification code, a choice between options, a name, a short fact. " +
333
+ "Never for long text (answers are capped at 500 characters), and not " +
334
+ "for permission to act: use ask_approval for that. Returns ANSWERED " +
335
+ "with exactly what they typed, or DECLINED, NO ANSWER or EXPIRED. On " +
336
+ "anything but ANSWERED you have no answer: do not guess one.",
337
+ inputSchema: {
338
+ question: z.string().min(1).max(250).optional()
339
+ .describe("The question, as they read it on a lock screen. Be " +
340
+ "specific about the shape of the answer: 'What is the 6-digit " +
341
+ "code from your authenticator?'. Defaults to 'Answer a question?'."),
342
+ details: z.string().max(2000).optional()
343
+ .describe("Anything else that helps them answer."),
344
+ placeholder: z.string().max(80).optional()
345
+ .describe("A hint shown in the answer field, e.g. '6-digit code'."),
346
+ require_biometric: z.boolean().optional()
347
+ .describe("Demand a fingerprint or face before the answer is sent."),
348
+ timeout_seconds: z.number().int().min(5).max(900).optional()
349
+ .describe("How long to wait for an answer. Defaults to 120."),
350
+ },
351
+ },
352
+ async ({ question = "Answer a question?", details, placeholder, require_biometric,
353
+ timeout_seconds = 120 }) => {
354
+ try {
355
+ const out = await lauther.requestText({
356
+ title: question,
357
+ message: details,
358
+ placeholder,
359
+ requireBiometric: require_biometric,
360
+ timeoutSeconds: timeout_seconds,
361
+ });
362
+ const notYes = notShared(out, "answer");
363
+ if (notYes) return notYes;
364
+
365
+ const answer = out.result;
366
+ if (!answer || typeof answer.text !== "string") {
367
+ return failure("They replied, but Lauther returned no usable answer. " +
368
+ "You have no answer: do not guess one.");
369
+ }
370
+ return text(`ANSWERED after ${out.waitedSeconds}s: ${JSON.stringify(answer.text)}.` +
371
+ `${confirmation(out)} That is what they typed, sent once in reply to what ` +
372
+ "you asked - use it for that and nothing else.");
373
+ } catch (err) {
374
+ return failure(explain(err) + " No answer was obtained, so do not guess one.");
375
+ }
376
+ },
377
+ );
378
+
324
379
  return server;
325
380
  }
326
381