@wirl/mcp 0.2.0 → 0.2.1

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 +5 -3
  2. package/bin.js +85 -43
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -14,18 +14,20 @@ The token it receives is deploy-scoped by default: it can deploy, roll back, pul
14
14
 
15
15
  ## Running it locally
16
16
 
17
- For agents without HTTP MCP support, this package runs the same tools over stdio, using the credentials `wirl login` wrote. wirl serves the package itself, so the command names its address rather than an npm name:
17
+ For agents without HTTP MCP support, this package runs the same tools over stdio, using the credentials `wirl login` wrote. `npx` fetches it from npm the first time, so there is nothing to install beyond Node 20 or later:
18
18
 
19
19
  ```bash
20
- claude mcp add wirl -- npx -y https://app.wirl.dev/cli/wirl-mcp.tgz
20
+ claude mcp add wirl -- npx -y @wirl/mcp
21
21
  ```
22
22
 
23
23
  Cursor and anything else that speaks MCP take the same command in their own configuration:
24
24
 
25
25
  ```json
26
- { "mcpServers": { "wirl": { "command": "npx", "args": ["-y", "https://app.wirl.dev/cli/wirl-mcp.tgz"] } } }
26
+ { "mcpServers": { "wirl": { "command": "npx", "args": ["-y", "@wirl/mcp"] } } }
27
27
  ```
28
28
 
29
+ If your npm registry does not carry wirl, or to run exactly the build app.wirl.dev serves, name its address instead of the package: `claude mcp add wirl -- npx -y https://app.wirl.dev/cli/wirl-mcp.tgz`.
30
+
29
31
  The first time, the agent calls `wirl_login`, shows you a link and a code, and carries on once you approve in the browser. A local server acts with the CLI's credentials, which are full-scope; revoke them on the Connect an agent page at app.wirl.dev if an agent should lose access.
30
32
 
31
33
  ## Tools
package/bin.js CHANGED
@@ -2788,20 +2788,21 @@ arrives: pass \`response.body\` straight on. There are no WebSockets. A front en
2788
2788
  \`"static": "dist"\` serves that folder's files at their own paths (\`/\` is its \`index.html\`) and the module
2789
2789
  answers everything else; add \`"spa": true\` for a client-side router. Nothing outside that folder is served.
2790
2790
 
2791
- Preview before you deploy: \`wirl preview\` puts the folder up at its own address with
2792
- its own database (empty at first), and \`wirl fetch <path> --preview <name>\` (or the
2793
- \`wirl_preview_fetch\` tool) shows you what it serves.
2791
+ Preview before you deploy: \`wirl preview\` gives a folder its own address and database (at first only what
2792
+ \`migrations/\` makes). \`wirl fetch <path> --preview <name>\` or \`wirl_preview_fetch\` shows what it answers.
2794
2793
 
2795
2794
  npm packages work: install them in the project and the deploy bundles them in. Nothing is installed on the
2796
2795
  server: native code will not run. Node built-ins work (\`node:crypto\`, \`node:path\`, ...; \`Buffer\` and
2797
2796
  \`process\` are global), but what \`node:fs\` writes is gone by the next request: use \`env.DB\`.
2798
2797
 
2799
2798
  \`env\` holds \`DB\` and nothing else. \`env.DB\` is Cloudflare D1: prepared statements with \`.bind()\`, at most
2800
- 100 bound parameters a statement. \`exec()\` runs one statement per line. Declare a host under \`connections\`
2801
- in \`wirl.json\`, then \`fetch\` the vendor's real URL: wirl attaches the key. An undeclared host is refused; a
2802
- public API needing no key uses \`"kind": "none"\`. Declaring one deploys before anyone approves it;
2803
- the deploy says per connection whether it is usable now or waiting on an admin. A vendor has 60
2804
- seconds to answer, whole, unless it streams: ask a model for a stream.
2799
+ 100 bound parameters a statement. \`exec()\` runs one statement per line, as does each file in \`migrations/\`,
2800
+ which wirl applies in order to a preview and before going live; a failing one fails the deploy. Never edit
2801
+ an applied file. Dropping or renaming a table or column needs \`"migrations": { "destructive": [...] }\`, its
2802
+ own live deploy and a person's yes. Declare a host under \`connections\`, then \`fetch\` the vendor's real URL:
2803
+ wirl attaches the key. An undeclared host is refused; a public API needing no key uses \`"kind": "none"\`.
2804
+ Declaring one deploys before anyone approves it; the deploy says per connection whether it is usable now or
2805
+ waiting on an admin. A vendor has 60 seconds to answer, whole, unless it streams: ask a model for a stream.
2805
2806
 
2806
2807
  Six headers say who is asking: \`x-wirl-user-id\`, \`x-wirl-user-email\`, \`x-wirl-user-name\`,
2807
2808
  \`x-wirl-role\`, \`x-wirl-app-id\`, \`x-wirl-org\`. Read the name with
@@ -2811,10 +2812,9 @@ Schedules POST to \`/__wirl/schedule/<name>\`; a run gets 5 minutes unless that
2811
2812
  says otherwise, and at most 15. A \`public_paths\` path takes requests with no sign-in.
2812
2813
  Work that outlives one request (waiting for a person, trying tomorrow, a long loop) is a flow: see Flows.
2813
2814
 
2814
- A new app defaults to org-wide visibility when its org has a company email domain,
2815
- and to private otherwise, including one made with \`wirl orgs create\`.
2816
- Sharing by email adds one named person; \`wirl visibility private|org\` (or the tool)
2817
- sets the level. Public is only ever a person's click on the app's Access page.
2815
+ A new app defaults to org-wide visibility when its org has a company email domain, and to private
2816
+ otherwise, including one made with \`wirl orgs create\`; \`wirl visibility private|org\` sets the level.
2817
+ Sharing by email adds one named person. Public is only ever a person's click on the app's Access page.
2818
2818
 
2819
2819
  Porting an existing project: route it through the \`fetch\` handler, replace ORM calls with
2820
2820
  \`env.DB.prepare(...).bind(...)\`, turn timers into \`schedules\`, and move keys into \`connections\`. Keep the
@@ -2846,18 +2846,40 @@ early, repeated or late one gets 409, or 404 once the run ends; check \`x-wirl-u
2846
2846
  }
2847
2847
  });
2848
2848
 
2849
+ // packages/core/src/version.ts
2850
+ function wirlVersion() {
2851
+ return BUILT_VERSION ?? WORKSPACE_VERSION;
2852
+ }
2853
+ var WORKSPACE_VERSION, BUILT_VERSION;
2854
+ var init_version = __esm({
2855
+ "packages/core/src/version.ts"() {
2856
+ "use strict";
2857
+ WORKSPACE_VERSION = "0.0.0";
2858
+ BUILT_VERSION = true ? "0.2.1" : null;
2859
+ }
2860
+ });
2861
+
2849
2862
  // packages/core/src/install.ts
2863
+ function fallbackWhen(controlPlane = "this control plane") {
2864
+ return `If your npm registry does not carry wirl, or to run exactly the build ${controlPlane} serves,`;
2865
+ }
2866
+ function namedControlPlane(controlOrigin) {
2867
+ return `the wirl control plane (${controlOrigin.replace(/^https?:\/\//, "").replace(/\/+$/, "")})`;
2868
+ }
2850
2869
  function installFor(controlOrigin) {
2851
- if (NPM_PUBLISHED) return { cli: "wirl", mcp: "@wirl/mcp" };
2852
2870
  const origin = controlOrigin.replace(/\/+$/, "");
2853
- return { cli: `${origin}/cli/wirl.tgz`, mcp: `${origin}/cli/wirl-mcp.tgz` };
2871
+ const tarball = { cli: `${origin}/cli/wirl.tgz`, mcp: `${origin}/cli/wirl-mcp.tgz` };
2872
+ if (NPM_PUBLISHED) return { cli: "wirl", mcp: "@wirl/mcp", tarball };
2873
+ return { ...tarball, tarball };
2854
2874
  }
2855
- var NPM_PUBLISHED, WIRL_BIN;
2875
+ var NPM_PUBLISHED, WIRL_BIN, FALLBACK_WHEN, hasFallback;
2856
2876
  var init_install = __esm({
2857
2877
  "packages/core/src/install.ts"() {
2858
2878
  "use strict";
2859
- NPM_PUBLISHED = false;
2879
+ NPM_PUBLISHED = true;
2860
2880
  WIRL_BIN = NPM_PUBLISHED ? "npx wirl" : "wirl";
2881
+ FALLBACK_WHEN = fallbackWhen();
2882
+ hasFallback = (install) => install.cli !== install.tarball.cli;
2861
2883
  }
2862
2884
  });
2863
2885
 
@@ -3142,6 +3164,7 @@ var init_src = __esm({
3142
3164
  init_broker_stream();
3143
3165
  init_retention();
3144
3166
  init_contract();
3167
+ init_version();
3145
3168
  init_install();
3146
3169
  init_porting();
3147
3170
  init_supplied_bundle();
@@ -4980,7 +5003,7 @@ async function loadCredentials(env = process.env) {
4980
5003
  }
4981
5004
  } catch {
4982
5005
  }
4983
- throw new CliError("Not signed in.", "Run `wirl login` first, or set WIRL_TOKEN.", { code: "not_signed_in" });
5006
+ throw new CliError("Not signed in.", `Run \`${WIRL_BIN} login\` first, or set WIRL_TOKEN.`, { code: "not_signed_in" });
4984
5007
  }
4985
5008
  async function saveCredentials(creds, env = process.env) {
4986
5009
  const path = credentialsPath(env);
@@ -5041,11 +5064,11 @@ async function pollDeviceLogin(apiOrigin, login, options = {}) {
5041
5064
  continue;
5042
5065
  }
5043
5066
  case "expired_token":
5044
- throw new CliError("The sign-in code expired before it was approved.", "Run `wirl login` again.");
5067
+ throw new CliError("The sign-in code expired before it was approved.", `Run \`${WIRL_BIN} login\` again.`);
5045
5068
  default:
5046
5069
  throw new CliError(
5047
5070
  typeof body["error"] === "string" ? `Sign-in failed: ${body["error"]}.` : `Sign-in failed (${response.status}).`,
5048
- "Run `wirl login` again."
5071
+ `Run \`${WIRL_BIN} login\` again.`
5049
5072
  );
5050
5073
  }
5051
5074
  }
@@ -5096,6 +5119,10 @@ var SERVER_TEMPLATE = [
5096
5119
  " *",
5097
5120
  " * Everything real happens here. The page is HTML this module returns. The",
5098
5121
  " * data lives in env.DB, a SQLite database that belongs to this app alone.",
5122
+ " * Its tables are in migrations/0001_init.sql: wirl applies each new file in",
5123
+ " * migrations/ before a version goes live, to a preview's database when the",
5124
+ " * preview is put up, and to the local one when wirl dev starts. Put the",
5125
+ " * next change in migrations/0002_<what>.sql rather than editing that one.",
5099
5126
  " * The person making the request is in headers wirl sets after verifying",
5100
5127
  " * them, which the browser cannot forge. Any rule that matters, such as",
5101
5128
  ' * "you only see your own notes", is a line of code in this file: a check',
@@ -5107,18 +5134,8 @@ var SERVER_TEMPLATE = [
5107
5134
  " * live in .wirl/keys.json beside this file, which git ignores.",
5108
5135
  " */",
5109
5136
  "",
5110
- "// One line: exec runs one statement per line, as D1 does.",
5111
- "const SCHEMA =",
5112
- ' "create table if not exists notes (" +',
5113
- ' " id integer primary key," +',
5114
- ' " user_id text not null," +',
5115
- ' " body text not null," +',
5116
- ` " created_at text not null default (datetime('now'))" +`,
5117
- ' ")";',
5118
- "",
5119
5137
  "export default {",
5120
5138
  " async fetch(request, env) {",
5121
- " await env.DB.exec(SCHEMA);",
5122
5139
  " const url = new URL(request.url);",
5123
5140
  "",
5124
5141
  ` // One line per request. Read it with ${WIRL_BIN} logs.`,
@@ -5201,7 +5218,21 @@ var SERVER_TEMPLATE = [
5201
5218
  "}",
5202
5219
  ""
5203
5220
  ].join("\n");
5221
+ var INIT_MIGRATION = [
5222
+ "-- This app's tables. wirl applies each new file here, in order, before a version goes live.",
5223
+ "-- Make the next change in 0002_<what>.sql; never edit a file that has been deployed.",
5224
+ "create table notes (id integer primary key, user_id text not null, body text not null, created_at text not null default (datetime('now')))",
5225
+ ""
5226
+ ].join("\n");
5227
+ var INIT_MIGRATION_PATH = `${MIGRATIONS_DIR}/${ADOPTED_FILE}`;
5204
5228
  var SKILL_PATH = join5(".claude", "skills", "wirl", "SKILL.md");
5229
+ var SKILL_ORIGIN = "https://app.wirl.dev";
5230
+ var SKILL_INSTALL = installFor(SKILL_ORIGIN);
5231
+ var SKILL_RUN = NPM_PUBLISHED ? `Run the CLI as \`${WIRL_BIN} <command>\`, which needs no install beyond Node 22.13 or later (in a terminal npm asks "Ok to proceed?" the first time: answer y); this file writes a command as \`wirl <command>\` for short anywhere but the list below.
5232
+
5233
+ ` : "";
5234
+ var SKILL_FALLBACK = hasFallback(SKILL_INSTALL) ? `- ${fallbackWhen(namedControlPlane(SKILL_ORIGIN))} install the CLI from the control plane instead with \`npm install -g ${SKILL_INSTALL.tarball.cli}\`, run plain \`wirl\` where this file says \`${WIRL_BIN}\`, and name \`${SKILL_INSTALL.tarball.mcp}\` in place of \`${SKILL_INSTALL.mcp}\`.
5235
+ ` : "";
5205
5236
  var SKILL_MARKDOWN = `---
5206
5237
  name: wirl
5207
5238
  description: Deploy and share the internal app in this project with wirl (${WIRL_BIN}). Use when the user wants a tool, dashboard or prototype put somewhere colleagues can open it, shared with a named person, run locally the way it will run deployed, or connected to a third-party API; or wants work that outlives one request, such as waiting for a person to approve something or trying again tomorrow.
@@ -5218,20 +5249,23 @@ Before you build, fetch https://docs.wirl.dev/llms-full.txt: every page of the d
5218
5249
 
5219
5250
  ## Commands
5220
5251
 
5221
- - Connect first, if the agent supports MCP: \`claude mcp add --transport http wirl https://app.wirl.dev/mcp\` opens the browser once to sign in. No HTTP MCP support? \`claude mcp add wirl -- npx -y ${installFor("https://app.wirl.dev").mcp}\` does the same, running locally. Neither available: \`${WIRL_BIN} login\` prints a link and a code; the person approves in the browser. Do this once, when any command says you are not signed in.
5252
+ ${SKILL_RUN}- Connect first, if the agent supports MCP: \`claude mcp add --transport http wirl https://app.wirl.dev/mcp\` opens the browser once to sign in, and needs no install. No HTTP MCP support? \`claude mcp add wirl -- npx -y ${SKILL_INSTALL.mcp}\` does the same, running locally. Neither available: \`${WIRL_BIN} login\` prints a link and a code; the person approves in the browser. Do this once, when any command says you are not signed in.
5222
5253
  - \`${WIRL_BIN} deploy\` \u2014 deploys this directory. Prints the URL and who can open it.
5223
5254
  - \`${WIRL_BIN} share someone@company.com\` \u2014 that person can open it. Add \`--role editor\` so they can deploy too.
5224
5255
  - \`${WIRL_BIN} dev\` \u2014 runs the app here as it will run deployed, signed in as a stub user.
5225
5256
  - \`${WIRL_BIN} pull <app>\` \u2014 fetches a deployed app's source to keep building.
5226
5257
  - \`${WIRL_BIN} logs\` \u2014 what the deployed app printed, per request, newest first; \`--errors\` for failures only, \`--follow\` to keep watching, \`--cursor <n>\` for the page before a "More before this" line. Read this before changing code when something misbehaves.
5258
+ - \`${WIRL_BIN} migrations\` \u2014 what this app's database has applied, read only.
5259
+ - \`${WIRL_BIN} migrations adopt\` \u2014 once, for an app whose tables were made in code: writes \`migrations/0001_init.sql\` from the live database's shape.
5227
5260
  - \`${WIRL_BIN} --help\` \u2014 everything else: orgs, members, connections, move, transfer, rollback, flows and runs.
5228
-
5261
+ ${SKILL_FALLBACK}
5229
5262
  ## Rules
5230
5263
 
5231
5264
  1. **Never ask for or paste an API key.** Declare the connection in \`wirl.json\` and deploy. Needs no key at all (a public API)? Declare it with "kind": "none" and its hosts. If the key is missing, the deploy prints a link to the app's keys page; show the person that link. Locally, keys live in \`.wirl/keys.json\`, which git ignores. To try a vendor call locally without the key, map its connection to a server of your own in \`.wirl/mocks.json\`, like \`{ "anthropic": "http://127.0.0.1:4010" }\`: \`wirl dev\` then sends the calls to that connection's hosts there instead (\`wirl dev --help\` has the rest).
5232
5265
  2. **Call third-party APIs by their real URL** (\`fetch("https://api.hubspot.com/...")\`). wirl attaches the key on the way out, deployed and under \`wirl dev\` alike.
5233
5266
  3. If a command names several organisations and asks which, ask the person; pass \`--org\` or put \`"org"\` in \`wirl.json\`.
5234
5267
  4. To keep a file out of the deploy, list it in a \`.wirlignore\` at the project root (gitignore-style lines; no \`!\` negation).
5268
+ 5. **A file that can lose data waits for a person.** A deploy that would apply a destructive file (one wirl.json's \`"destructive"\` list must name) to the live database stops and names the page where a person approves each one. Show the person those pages, and deploy again once they say they approved. If \`${WIRL_BIN} deploy\` asks you to type a file's name, stop it and show the page instead, or deploy with \`--json\`, which never asks. Never type the name yourself or send the confirmation: that is the person's, and no flag skips it.
5235
5269
 
5236
5270
  ## wirl.json
5237
5271
 
@@ -5522,7 +5556,7 @@ async function callApi(creds, path, options = {}) {
5522
5556
  throw new CliError(body.message, void 0, refusal);
5523
5557
  }
5524
5558
  if (response.status === 401 || response.status === 403) {
5525
- throw new CliError("Not signed in, or this token cannot do that.", "Try `wirl login`.", refusal);
5559
+ throw new CliError("Not signed in, or this token cannot do that.", `Try \`${WIRL_BIN} login\`.`, refusal);
5526
5560
  }
5527
5561
  if (response.status === 404) {
5528
5562
  if (body.error !== void 0 && body.error !== "not_found" && body.message) throw new CliError(body.message, void 0, refusal);
@@ -5554,7 +5588,7 @@ async function callApiBytes(creds, path, options = {}) {
5554
5588
  body = {};
5555
5589
  }
5556
5590
  if (response.status === 401 || response.status === 403) {
5557
- throw new CliError("Not signed in, or this token cannot do that.", "Try `wirl login`.");
5591
+ throw new CliError("Not signed in, or this token cannot do that.", `Try \`${WIRL_BIN} login\`.`);
5558
5592
  }
5559
5593
  if (response.status === 404) throw new CliError("Not found, or you do not have access to it.");
5560
5594
  throw new CliError(body.message ?? body.error ?? `Request failed (${response.status}).`);
@@ -6709,7 +6743,7 @@ var ADOPT_SENTENCES2 = {
6709
6743
  */
6710
6744
  cannotTell: (what) => `wirl did not get the adopt route's answer (${what}), so it cannot tell whether the live database recorded migrations/${ADOPTED_FILE}, and nothing was written here. Run wirl migrations adopt again: while an adopted ${ADOPTED_FILE} is all the live database records, adopting again replaces that record and writes the file.`,
6711
6745
  /** After the call, so the live database records the file; a CLI that reads files otherwise than the control plane says this every time. */
6712
- notAsRecorded: (install) => `The live database now records migrations/${ADOPTED_FILE} as adopted, but the file wirl sent does not match its own record of it, so nothing was written here. Run wirl migrations adopt again: while an adopted ${ADOPTED_FILE} is all the live database records, adopting again replaces that record and writes the file. If it says this again, this copy of wirl reads migration files differently from the control plane: update it (npm install -g ${install}).`,
6746
+ notAsRecorded: (install) => `The live database now records migrations/${ADOPTED_FILE} as adopted, but the file wirl sent does not match its own record of it, so nothing was written here. Run wirl migrations adopt again: while an adopted ${ADOPTED_FILE} is all the live database records, adopting again replaces that record and writes the file. If it says this again, this copy of wirl reads migration files differently from the control plane: update it (npm install -g ${install.cli}${hasFallback(install) ? `, or, to match this control plane exactly, npm install -g ${install.tarball.cli}` : ""}).`,
6713
6747
  unwritten: (code) => `The live database now records migrations/${ADOPTED_FILE} as adopted, but wirl could not write that file here (${code}). Make migrations/ a folder you can write that holds no .sql file, then run wirl migrations adopt again: while an adopted ${ADOPTED_FILE} is all the live database records, adopting again replaces that record and writes the file.`,
6714
6748
  nothingWritten: "Nothing was written to migrations/.",
6715
6749
  /**
@@ -6887,7 +6921,7 @@ async function adoptIntoProject(creds, options) {
6887
6921
  const read = await readMigrations([{ name: ADOPTED_FILE, text: answer.file.text }], []);
6888
6922
  const [file, ...others] = read.ok ? read.files : [];
6889
6923
  if (file === void 0 || others.length > 0 || file.sha256 !== answer.file.sha256 || file.statements.length !== answer.file.statements) {
6890
- throw new CliError(ADOPT_SENTENCES2.notAsRecorded(installFor(creds.apiOrigin).cli));
6924
+ throw new CliError(ADOPT_SENTENCES2.notAsRecorded(installFor(creds.apiOrigin)));
6891
6925
  }
6892
6926
  const path = await writeAdopted(projectDir, answer.file.text);
6893
6927
  const { recordLocalAdoption: recordLocalAdoption2 } = await Promise.resolve().then(() => (init_migrations_local(), migrations_local_exports));
@@ -7862,6 +7896,11 @@ function refuseUnlessNames(names) {
7862
7896
  throw new CliError(`${JSON.stringify(value)} is not ${what} name. ${validateSlug(value).message ?? ""}`.trimEnd());
7863
7897
  }
7864
7898
  }
7899
+ function npmFallbackLines() {
7900
+ const origin = apiOriginFromEnv();
7901
+ const install = installFor(origin);
7902
+ return hasFallback(install) ? ["", `${fallbackWhen(namedControlPlane(origin))} install the wirl CLI from the control plane instead (npm install -g ${install.tarball.cli}) and run plain wirl where this says ${WIRL_BIN}.`] : [];
7903
+ }
7865
7904
  var REMOTE_DEPLOY = {
7866
7905
  name: "wirl_deploy",
7867
7906
  description: "Deploy a directory of files to a URL that colleagues can open. This server runs at app.wirl.dev and cannot read files on the machine you are on, so it returns the exact commands to run in a terminal (and makes the app first, when you name one); the upload itself happens from the machine holding the files, never through this chat. A new app is visible to everyone in the org when the org came from a company email domain; in any other org, including a personal one or one made with wirl orgs create, it starts private. Returns the URL it will get.",
@@ -7880,13 +7919,13 @@ var REMOTE_DEPLOY = {
7880
7919
  const directory = str(args, "directory", ".");
7881
7920
  const app = str(args, "app");
7882
7921
  if (app) await ensureRemoteApp(ctx.creds, org, app);
7883
- const install = installFor(apiOriginFromEnv());
7884
7922
  return [
7885
7923
  app ? `App ${app} exists in ${org}. Upload it from the machine that holds the files:` : "Upload it from the machine that holds the files:",
7886
7924
  "",
7887
- ` npm install -g ${install.cli} # once per machine`,
7888
- " wirl login # once per machine; opens the browser",
7889
- ` wirl deploy ${shellPath(directory)} --org ${shellQuote(org)}${app ? ` --app ${shellQuote(app)}` : ""}`,
7925
+ ...NPM_PUBLISHED ? [] : [` npm install -g ${installFor(apiOriginFromEnv()).cli} # once per machine`],
7926
+ ` ${WIRL_BIN} login # once per machine; opens the browser`,
7927
+ ` ${WIRL_BIN} deploy ${shellPath(directory)} --org ${shellQuote(org)}${app ? ` --app ${shellQuote(app)}` : ""}`,
7928
+ ...npmFallbackLines(),
7890
7929
  "",
7891
7930
  app ? `It will be at https://${org}--${app}.${APPS_DOMAIN}.` : `The command takes the app from "app" in the project's wirl.json, else from the folder's name, makes it if it is new, and prints its URL.`,
7892
7931
  "Once it is deployed, wirl_list_apps shows who can open it.",
@@ -7910,12 +7949,12 @@ var REMOTE_PULL = {
7910
7949
  refuseUnlessNames({ org: str(args, "org"), app: str(args, "app") });
7911
7950
  const org = await defaultOrg(ctx.creds, str(args, "org"));
7912
7951
  const app = str(args, "app");
7913
- const install = installFor(apiOriginFromEnv());
7914
7952
  return [
7915
7953
  "Run in a terminal:",
7916
7954
  "",
7917
- ...NPM_PUBLISHED ? [] : [` npm install -g ${install.cli} # once per machine`],
7918
- ` ${WIRL_BIN} pull ${shellQuote(app)} ${shellPath(str(args, "directory", app))} --org ${shellQuote(org)}`
7955
+ ...NPM_PUBLISHED ? [] : [` npm install -g ${installFor(apiOriginFromEnv()).cli} # once per machine`],
7956
+ ` ${WIRL_BIN} pull ${shellQuote(app)} ${shellPath(str(args, "directory", app))} --org ${shellQuote(org)}`,
7957
+ ...npmFallbackLines()
7919
7958
  ].join("\n");
7920
7959
  }
7921
7960
  };
@@ -7954,6 +7993,7 @@ var REMOTE_PREVIEW = {
7954
7993
  ...NPM_PUBLISHED ? [] : [` npm install -g ${installFor(apiOriginFromEnv()).cli} # once per machine`],
7955
7994
  ` ${WIRL_BIN} login # once per machine; opens the browser`,
7956
7995
  ` ${WIRL_BIN} preview ${shellPath(directory)} --org ${shellQuote(org)}${app ? ` --app ${shellQuote(app)}` : ""}${name ? ` --name ${shellQuote(name)}` : ""}`,
7996
+ ...npmFallbackLines(),
7957
7997
  "",
7958
7998
  ...app ? [] : [`The command takes the app from "app" in the project's wirl.json, else from the folder's name.`],
7959
7999
  "Once it is up, wirl_preview_fetch shows you what it actually renders.",
@@ -8015,6 +8055,7 @@ var REMOTE_MIGRATIONS_ADOPT = {
8015
8055
  ...NPM_PUBLISHED ? [] : [` npm install -g ${installFor(apiOriginFromEnv()).cli} # once per machine`],
8016
8056
  ` ${WIRL_BIN} login # once per machine; opens the browser`,
8017
8057
  ` ${WIRL_BIN} migrations adopt ${shellPath(directory)} --org ${shellQuote(org)}${app ? ` --app ${shellQuote(app)}` : ""}`,
8058
+ ...npmFallbackLines(),
8018
8059
  "",
8019
8060
  ...app ? [] : [`The command takes the app from "app" in the project's wirl.json, else from the folder's name.`],
8020
8061
  "It writes migrations/0001_init.sql beside wirl.json from the live database's shape, never a row, and the live database records it as applied.",
@@ -8034,6 +8075,7 @@ function toolsFor(mode) {
8034
8075
  var SERVER_INSTRUCTIONS = [
8035
8076
  "wirl deploys a small internal tool to a URL colleagues can open, with company sign-in, a database and vaulted API keys already wired up.",
8036
8077
  "Reach for it whenever what you just built should live somewhere shared instead of only on this machine.",
8078
+ ...NPM_PUBLISHED ? [`The text below writes a command as \`wirl <command>\` for short; run one as \`${WIRL_BIN} <command>\`, which needs no install beyond Node 22.13 or later.`] : [],
8037
8079
  "",
8038
8080
  BUILD_CONTRACT,
8039
8081
  "",
@@ -8060,7 +8102,7 @@ async function handleRpc(request, ctx) {
8060
8102
  return result(id, {
8061
8103
  protocolVersion: negotiate(request.params?.["protocolVersion"]),
8062
8104
  capabilities: { tools: {} },
8063
- serverInfo: { name: "wirl", version: "0.0.0" },
8105
+ serverInfo: { name: "wirl", version: ctx?.serverVersion ?? wirlVersion() },
8064
8106
  instructions: SERVER_INSTRUCTIONS
8065
8107
  });
8066
8108
  // Notifications carry no id and expect no reply.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wirl/mcp",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "MCP server for wirl: let your coding agent deploy what it just built, privately.",
5
5
  "keywords": [
6
6
  "mcp",