@shipvo/mcp 0.0.6 → 0.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 CHANGED
@@ -8,7 +8,7 @@ Lets AI agents (Claude, Cursor, Claude Code, PiPilot…) **use a Shipvo app with
8
8
  "env": { "PN_SIM": "http://localhost:4545", "PN_OWNER_TOKEN": "…optional…" } } } }
9
9
  ```
10
10
 
11
- Start the simulator first (`shipvo sim` in the app). The hosted version needs no local setup: `{ "url": "https://<host>/mcp/<key>" }`.
11
+ Start the simulator first (`shipvo sim` in the app). The server asks the simulator which app it serves, so `open_app("<your app id>")` works; set `PN_APPS` (JSON) to override. If the simulator cannot be reached, only the sample app opens. The hosted version needs no local setup: `{ "url": "https://<host>/mcp/<key>" }`.
12
12
 
13
13
  Tools: `list_apps`, `open_app`, `screen`, `tap`, `type`, `scroll`, `back`, `screenshot`, `set_device`, `push_notification`, `notifications`, `open_notification`, `device_log`, `end_session`, `owner_describe`, `owner_command`.
14
14
 
package/dist/server.js CHANGED
@@ -15,9 +15,25 @@ import { z } from "zod";
15
15
  const SIM = (process.env.PN_SIM ?? "http://localhost:4545").replace(/\/$/, "");
16
16
  const CONTROL = (process.env.PN_CONTROL ?? "https://api.shipvo.dev").replace(/\/$/, "");
17
17
  const OWNER_TOKEN = process.env.PN_OWNER_TOKEN ?? "";
18
- // Apps this server can open. Hosted: the account's apps. Local: one app served by the simulator.
19
- const APPS = process.env.PN_APPS ? JSON.parse(process.env.PN_APPS)
20
- : { tide: { name: "Tide & Tackle", description: "Fishing-supplies shop in Douala: catalogue, cart, promo codes, delivery slots, cash or mobile money." } };
18
+ // Apps this server can open. Hosted: the account's apps. Local: the app the simulator serves (it is asked on
19
+ // first use), or PN_APPS (JSON) to override. Falls back to the sample app when the simulator cannot be reached.
20
+ const TIDE = { tide: { name: "Tide & Tackle", description: "Fishing-supplies shop in Douala: catalogue, cart, promo codes, delivery slots, cash or mobile money." } };
21
+ async function localApps() {
22
+ if (process.env.PN_APPS)
23
+ return JSON.parse(process.env.PN_APPS);
24
+ try {
25
+ const r = await fetch(SIM + "/apps", { method: "POST", headers: { "content-type": "application/json" }, body: "{}" });
26
+ if (r.ok) {
27
+ const out = {};
28
+ for (const a of (await r.json()).apps ?? [])
29
+ out[a.id] = { name: a.name ?? a.id, description: "The app this simulator serves." };
30
+ if (Object.keys(out).length)
31
+ return out;
32
+ }
33
+ }
34
+ catch { }
35
+ return TIDE;
36
+ }
21
37
  async function sim(path, body) {
22
38
  const r = await fetch(SIM + path, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
23
39
  if (!r.ok)
@@ -27,7 +43,7 @@ async function sim(path, body) {
27
43
  const sessions = new Map();
28
44
  const text = (t) => ({ content: [{ type: "text", text: t }] });
29
45
  const result = (sid, r) => { const s = sessions.get(sid); if (s)
30
- s.steps++; return text(r.ok ? (r.screen ?? "") : `Error: ${r.error}${/no session/.test(String(r.error)) ? " (sessions expire with the phone; call open_app again)" : ""}\n\nScreen now:\n${r.screen ?? ""}`); };
46
+ s.steps++; return text(r.ok ? (r.screen ?? "") : `Error${r.code ? ` [${r.code}]` : ""}: ${r.error}${/no session/.test(String(r.error)) ? " (sessions expire with the phone; call open_app again)" : ""}\n\nScreen now:\n${r.screen ?? ""}`); };
31
47
  // Sessions live in the simulator; this map only tracks step counts. A client may reconnect between calls
32
48
  // (ChatGPT does), so an unknown id is forwarded and the simulator answers if it knows it.
33
49
  const need = (_sid) => { };
@@ -39,8 +55,9 @@ System sheets (permission dialogs, the camera, the fingerprint prompt, payment p
39
55
  Finish a task by checking the final screen says what you expected (for example "Order placed"). Call end_session when done to get a report.` });
40
56
  // ctx.app: the app(s) this key opens, comma-separated (the beta app plus any the key owner has registered).
41
57
  const keyApps = ctx.app ? ctx.app.split(",").map((s) => s.trim()).filter(Boolean) : null;
42
- const allowed = (id) => (keyApps ? keyApps.includes(id) : !!APPS[id]);
58
+ const allowed = async (id) => (keyApps ? keyApps.includes(id) : !!(await localApps())[id]);
43
59
  server.tool("list_apps", "Apps this server can open, with what each one does. Optional query filters by name, description or category (e.g. 'shop', 'douala').", { query: z.string().optional() }, async ({ query }) => {
60
+ const APPS = await localApps();
44
61
  const ids = keyApps ?? Object.keys(APPS);
45
62
  const q = (query ?? "").toLowerCase();
46
63
  let dir = {};
@@ -65,8 +82,8 @@ Finish a task by checking the final screen says what you expected (for example "
65
82
  dark: z.boolean().optional().describe("Dark mode"),
66
83
  }, async ({ app, session, seed_cart, dark }) => {
67
84
  const id = app.split("@")[0];
68
- if (!allowed(id))
69
- throw new Error(keyApps ? `this key opens: ${keyApps.join(", ")}` : `unknown app "${id}". Apps: ${Object.keys(APPS).join(", ")}`);
85
+ if (!(await allowed(id)))
86
+ throw new Error(keyApps ? `this key opens: ${keyApps.join(", ")}` : `unknown app "${id}". Apps: ${Object.keys(await localApps()).join(", ")}`);
70
87
  if (ctx.scope === "read")
71
88
  throw new Error("this key is read-only: it cannot open sessions");
72
89
  const sid = session ?? `${id}-${Date.now().toString(36)}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipvo/mcp",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "description": "MCP server that lets AI agents open, see and operate Shipvo apps without a phone: a phone-sized simulator of the real app, plus the app's typed commands and owner API.",
@@ -14,7 +14,8 @@
14
14
  },
15
15
  "devDependencies": {
16
16
  "typescript": "^5.6.3",
17
- "@types/node": "^22.8.0"
17
+ "@types/node": "^22.8.0",
18
+ "vitest": "^2.1.4"
18
19
  },
19
20
  "keywords": [
20
21
  "mcp",
@@ -45,6 +46,7 @@
45
46
  },
46
47
  "mcpName": "io.github.shipvo/shipvo",
47
48
  "scripts": {
48
- "build": "tsc -p tsconfig.json"
49
+ "build": "tsc -p tsconfig.json",
50
+ "test": "vitest run"
49
51
  }
50
52
  }