appilot-mcp 0.2.1 → 0.4.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 (47) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/LICENSE +15 -0
  4. package/README.md +133 -27
  5. package/dist/appilot-configurator.mcpb +0 -0
  6. package/dist/cli.d.ts +34 -0
  7. package/dist/cli.js +173 -0
  8. package/dist/client.d.ts +45 -1
  9. package/dist/client.js +74 -1
  10. package/dist/config.d.ts +21 -0
  11. package/dist/config.js +6 -0
  12. package/dist/contract/healthContract.js +31 -4
  13. package/dist/index.bundle.js +2111 -826
  14. package/dist/index.d.ts +7 -1
  15. package/dist/index.js +22 -4
  16. package/dist/manifest.d.ts +14 -2
  17. package/dist/manifest.js +31 -9
  18. package/dist/public-marketplace/.claude-plugin/marketplace.json +20 -0
  19. package/dist/public-marketplace/README.md +23 -0
  20. package/dist/public-marketplace/plugins/app-configurator/.claude-plugin/plugin.json +43 -0
  21. package/dist/public-marketplace/plugins/app-configurator/README.md +328 -0
  22. package/dist/public-marketplace/plugins/app-configurator/dist/index.bundle.js +57370 -0
  23. package/dist/public-marketplace/plugins/app-configurator/skills/app-configurator/SKILL.md +267 -0
  24. package/dist/public-marketplace/plugins/app-configurator/skills/app-configurator/agents/openai.yaml +13 -0
  25. package/dist/redaction.d.ts +51 -0
  26. package/dist/redaction.js +59 -0
  27. package/dist/remote/consent.d.ts +10 -2
  28. package/dist/remote/consent.js +16 -6
  29. package/dist/remote/consentMessages.d.ts +6 -1
  30. package/dist/remote/consentMessages.js +15 -6
  31. package/dist/remote/httpServer.d.ts +10 -0
  32. package/dist/remote/httpServer.js +126 -40
  33. package/dist/remote/oauth.d.ts +10 -1
  34. package/dist/remote/oauth.js +29 -11
  35. package/dist/scaffold.d.ts +68 -6
  36. package/dist/scaffold.js +424 -97
  37. package/dist/server.js +175 -18
  38. package/dist/userClient.d.ts +213 -0
  39. package/dist/userClient.js +400 -0
  40. package/dist/userServer.d.ts +47 -0
  41. package/dist/userServer.js +248 -0
  42. package/dist/version.d.ts +1 -1
  43. package/dist/version.js +1 -1
  44. package/examples/app.appilot.json +212 -0
  45. package/mcpb/manifest.json +117 -21
  46. package/package.json +5 -3
  47. package/skills/app-configurator/SKILL.md +61 -19
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "app-configurator",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "description": "Set up, configure, audit, repair, back up, restore, and verify an Appilot app: provision the app, its domains and widget key, scaffold the host integration, and keep the content model correct. Uses the Appilot MCP server and the app-configurator skill.",
5
5
  "author": {
6
6
  "name": "Appilot",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "app-configurator",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "description": "Set up, configure, audit, repair, back up, restore, and verify an Appilot app: provision the app, its domains and widget key, scaffold the host integration, and keep the content model correct. Uses the Appilot MCP server and the app-configurator skill.",
5
5
  "author": {
6
6
  "name": "Appilot",
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 BetterKnow GmbH
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md CHANGED
@@ -1,11 +1,21 @@
1
1
  # appilot-mcp
2
2
 
3
- MCP server that lets an AI agent **read, validate, fix, and soak** an Appilot
4
- app's content-model configuration (Views, Controls, Forms, Action Plans,
5
- Knowledge, Zones, Tools) against the **config health contract**.
3
+ Two MCP servers ship from this package, and they do opposite jobs.
6
4
 
7
- It is the tool surface behind the `app-configurator` skill. The skill is the
8
- instructions; this server is what actually touches your instance.
5
+ **Appilot Studio** lets an AI agent **read, validate, fix, and soak** an Appilot
6
+ app's content-model configuration (Views, Controls, Forms, Action Plans,
7
+ Knowledge, Zones, Tools) against the **config health contract**. It is the tool
8
+ surface behind the `app-configurator` skill: the skill is the instructions, and
9
+ this server is what actually touches your instance. It is what a bare
10
+ `appilot-mcp` starts.
11
+
12
+ **Appilot**, the runtime connector, lets the person who USES a configured app
13
+ operate it from the assistant they already pay for. It reads the page they are
14
+ sharing, answers from the app's curated knowledge, and runs the app's own guided
15
+ procedures in their browser under their own session. It writes no configuration.
16
+ Start it with `appilot-mcp --runtime`, or connect to `/mcp/runtime` on the
17
+ deployed service. See
18
+ [the connector spec](../../../docs/architecture/appilot-runtime-connector.md).
9
19
 
10
20
  ## Why
11
21
 
@@ -21,10 +31,10 @@ Endpoint-agnostic by design. The same binary talks to a cloud tenant or an
21
31
  on-premise / sovereign instance purely by pointing `APPILOT_BASE_URL` at it. The
22
32
  static gate runs locally from `appilot-shared`, so it works with no network.
23
33
 
24
- ## Two transports, one tool surface
34
+ ## Two transports
25
35
 
26
- The same tools serve two shapes, and which one you need is decided by the client,
27
- not by preference.
36
+ Each server serves two shapes, and which one you need is decided by the client,
37
+ not by preference. A tool is registered once per server and reachable over both.
28
38
 
29
39
  | Transport | Who launches it | Credential | Clients |
30
40
  |-----------|-----------------|------------|---------|
@@ -44,6 +54,8 @@ OAuth. That is what HTTP mode is for. See
44
54
  | `APPILOT_PAT` | for writes / server echo | A scoped service token (`appilot_pat_…`), minted in the Backoffice (Service tokens). `config:read` to read/validate, `config:write` to apply. |
45
55
  | `APPILOT_APP_ID` | optional | Default app id for tools that omit one |
46
56
  | `APPILOT_SOAK_STORAGE_STATE` | optional | Path to a Playwright `storageState` JSON for an authenticated site session (soak) |
57
+ | `APPILOT_SESSION_TOKEN` | for `--runtime` | An Appilot user session. The connector authenticates as the person, so this is the only credential it reads. |
58
+ | `APPILOT_MCP_SERVER` | optional | `runtime` starts the Appilot connector instead of Studio. Same as `--runtime`. |
47
59
 
48
60
  HTTP mode reads a different profile. It holds no Appilot credential of its own,
49
61
  because each caller brings theirs.
@@ -55,23 +67,92 @@ because each caller brings theirs.
55
67
  | `APPILOT_MCP_OAUTH_SECRET` | yes | At least 32 characters (`openssl rand -base64 32`). Everything the authorization server issues is sealed with it, so rotating it disconnects every client. |
56
68
  | `PORT` | no | Defaults to 8080. Cloud Run supplies it. |
57
69
  | `APPILOT_MCP_ALLOWED_HOSTS` | no | Extra `Host` values to accept, comma separated, for a proxy or a platform-assigned hostname. The public origin is always accepted. |
70
+ | `APPILOT_MCP_DOCS_URL` | no | Where the protected-resource metadata points a client for documentation. Defaults to the public docs site. |
71
+ | `APPILOT_MCP_BACKOFFICE_URL` | no | The Backoffice origin consent screens link to, and the only origin account approval is accepted from. Must be HTTPS (localhost excepted). Defaults to `https://backoffice.appilot.space` when the instance is the Appilot cloud. |
72
+ | `APPILOT_MCP_HANDOFF_SECRET` | no | Enables account approval instead of pasting a service token. At least 32 characters, and it must match the backend's. Separate from the OAuth secret, and it requires `APPILOT_MCP_BACKOFFICE_URL`. |
58
73
 
59
- ## Tools
74
+ ## Studio tools
75
+
76
+ Twenty-five, in the order the server registers them. `test/toolSurface.test.ts`
77
+ holds this table, `mcpb/manifest.json` and the registered tools to one list.
60
78
 
61
79
  | Tool | Purpose |
62
80
  |------|---------|
63
- | `capabilities` | Version + migration level + schema versions of the connected instance (negotiate before configuring) |
81
+ | `capabilities` | Version, migration level and the closed vocabularies this instance accepts (negotiate before configuring) |
64
82
  | `read_config` | Normalized snapshot of an app's content model |
65
83
  | `validate_config` | Severity-ranked findings against the health contract (local + server echo) |
66
84
  | `entity_template` | A valid skeleton per entity kind, carrying the closed enums this instance accepts |
67
85
  | `create_entity` / `update_entity` / `delete_entity` | Authoring across all eight kinds (server re-validates the trust boundary) |
68
86
  | `validate_action_plan` | Check draft plan sections before writing them |
69
- | `inspect_page` | Read a page and rank locator candidates by whether they survive the next render |
70
- | `scaffold_agent_first` | The four artifacts one capability needs to be operable by the agent |
71
87
  | `export_config` / `import_config` | Whole-app ConfigBundle round trip, dry-run first, merge or replace |
88
+ | `whoami` | Which organization, which app and which scopes the credential reaches |
89
+ | `create_app` | Provision the app, its domains and a widget key, with the snippets to paste in |
90
+ | `list_apps` | What this organization has provisioned, with each domain's verification status |
91
+ | `list_widget_keys` | The keys already minted, by label and prefix. No raw key, no secret |
92
+ | `verify_domain` | Where a domain's DNS verification stands, and the TXT record it needs |
93
+ | `integration_snippet` | The script tag and boot call for an app that already exists. Pure, no scope |
94
+ | `plan_manifest` / `apply_manifest` | A whole tenant from one versioned file, plan before apply |
95
+ | `scaffold_integration` | The host application's source: identity relay, widget boot, a client action |
96
+ | `verify_integration` | Load the running page and report what is actually true |
72
97
  | `soak_selectors` | Headless-browser check that each control selector resolves on the live page (needs Playwright) |
98
+ | `inspect_page` | Read a page and rank locator candidates by whether they survive the next render |
99
+ | `scaffold_agent_first` | The artifacts one capability needs to be operable by the agent |
73
100
  | `report_feedback` / `list_feedback` | Report a gap or a defect in Appilot, and see what this organization has already raised |
74
101
 
102
+ ## Appilot connector tools
103
+
104
+ Eight, and deliberately no more: a model reads the whole list on every call.
105
+ They are named in the vocabulary of the person using the app rather than in
106
+ ours. `test/toolSurface.test.ts` holds this table to the registered surface.
107
+
108
+ | Tool | Purpose |
109
+ |------|---------|
110
+ | `where_am_i` | Which app and view, whether the app is configured, whether a tab is shared, which plans apply here |
111
+ | `read_page` | The shared page as an outline, as the visible text of a region, or as form state with its validation errors |
112
+ | `find_on_page` | Elements by role and accessible name, with refs `highlight` can point at |
113
+ | `highlight` | Point at one control, zone or element on the person's screen |
114
+ | `search_knowledge` | The app's curated knowledge, with citations |
115
+ | `list_plans` | The curated procedures that apply to this view, with the `plan_id` and fields `run_plan` takes |
116
+ | `run_plan` | Run one curated plan in the page at a pace (`teach`, `walk`, `do`) |
117
+ | `plan_status` | How a running plan is going, with a waiting mode that holds for the page to move |
118
+
119
+ Every result says whether its work was visible in the page and what the answer
120
+ is grounded in, and the tool descriptions oblige the assistant to act on both:
121
+ confirm a plan in one sentence rather than re-listing steps the person is
122
+ watching, and state the mode without inflating it.
123
+
124
+ `read_page` and `find_on_page` answer with one `page_content` string: the DOM
125
+ result serialized inside the backend's untrusted-content delimiters. The
126
+ connector passes it through untouched, because the delimiters are the
127
+ prompt-injection defence and they only work while they are still around the
128
+ content. What the connector does decide is how much a read asks for, and over a
129
+ hosted connection that is capped.
130
+
131
+ The connector needs a live Appilot session, and over stdio that is
132
+ `APPILOT_SESSION_TOKEN`. It never reads `APPILOT_PAT`: a service token names an
133
+ organization, a session names a person, and the runtime routes exist so the
134
+ connector can do no more than that person can.
135
+
136
+ ## The command line
137
+
138
+ The package is an MCP server first, and a small CLI for the things a pipeline
139
+ needs without an MCP client in front of them.
140
+
141
+ ```bash
142
+ npx appilot-mcp --help
143
+ npx appilot-mcp --version
144
+ npx appilot-mcp install-skill --claude # or --codex, --cursor, --gemini
145
+ npx appilot-mcp plan app.appilot.json # preview, writes nothing
146
+ npx appilot-mcp apply app.appilot.json # apply the plan it just printed
147
+ ```
148
+
149
+ `plan` and `apply` read the same environment the stdio server does, and `apply`
150
+ runs the plan itself and passes its own token, so the preview cannot be skipped.
151
+ `install-skill` copies the bundled `app-configurator` skill into the client's
152
+ skills directory, which is what makes the skill reachable from a bare `npx`
153
+ install: inside the npm cache no client looks for it.
154
+
155
+
75
156
  ## Distribution artifacts
76
157
 
77
158
  One package carries a tool-neutral implementation plus thin discovery adapters:
@@ -83,6 +164,8 @@ One package carries a tool-neutral implementation plus thin discovery adapters:
83
164
  | `.codex-plugin/plugin.json` | Codex plugin |
84
165
  | `.claude-plugin/plugin.json` | Claude Code plugin |
85
166
  | `mcpb/manifest.json` | Claude Desktop bundle manifest, packed by `pnpm build:mcpb` |
167
+ | `examples/app.appilot.json` | A complete app manifest to copy, held valid by `test/exampleManifest.test.ts` |
168
+ | `.claude-plugin/public-marketplace/` | The public marketplace repository, prepared and not published |
86
169
  | `Dockerfile` | The remote HTTP service |
87
170
  | `dist/index.bundle.js` | Self-contained Node.js MCP server, both transports |
88
171
 
@@ -113,10 +196,20 @@ task after installation so the new tool surface is loaded.
113
196
 
114
197
  ### Claude Code
115
198
 
116
- Add the repository marketplace and install `app-configurator@appilot`. Claude's
117
- plugin settings prompt for the backend URL, service token, and optional app ID.
118
- The token is declared sensitive so Claude stores it in secure credential
119
- storage.
199
+ ```
200
+ /plugin marketplace add /absolute/path/to/app-pilot
201
+ /plugin install app-configurator@appilot
202
+ ```
203
+
204
+ Claude's plugin settings then prompt for the backend URL, the service token and
205
+ an optional app id. The token is declared sensitive, so Claude stores it in
206
+ secure credential storage.
207
+
208
+ There is no published marketplace repository yet: `appilot/appilot-plugins` does
209
+ not exist on GitHub, so a `/plugin marketplace add appilot/appilot-plugins`
210
+ anywhere is wrong until someone publishes it. The tree that repository would
211
+ carry is prepared in `.claude-plugin/public-marketplace/`, along with the human
212
+ steps, and `pnpm build:marketplace` assembles it.
120
213
 
121
214
  ### Claude Desktop
122
215
 
@@ -152,17 +245,21 @@ instance needs no public listener.
152
245
 
153
246
  ### Cursor
154
247
 
155
- Copy `skills/app-configurator` to `~/.cursor/skills/app-configurator`, then add
156
- the server to `~/.cursor/mcp.json`.
248
+ ```bash
249
+ npx -y appilot-mcp install-skill --cursor
250
+ ```
157
251
 
158
- ### Antigravity
252
+ Then add the server to `~/.cursor/mcp.json`.
159
253
 
160
- Copy `skills/app-configurator` to the applicable global skills directory:
254
+ ### Antigravity
161
255
 
162
- - Antigravity IDE: `~/.gemini/config/skills/app-configurator`
163
- - Antigravity CLI: `~/.gemini/antigravity-cli/skills/app-configurator`
256
+ ```bash
257
+ npx -y appilot-mcp install-skill --gemini
258
+ ```
164
259
 
165
- Register the stdio server in `~/.gemini/config/mcp_config.json`.
260
+ That writes `~/.gemini/config/skills/app-configurator`, which is where the IDE
261
+ looks. The CLI reads `~/.gemini/antigravity-cli/skills/`, so pass `--dir` for
262
+ that one. Register the stdio server in `~/.gemini/config/mcp_config.json`.
166
263
 
167
264
  ### Any stdio MCP client
168
265
 
@@ -184,8 +281,15 @@ Point the client at the built bundle:
184
281
  }
185
282
  ```
186
283
 
187
- For registry-backed distribution, replace `node` and the bundle path with
188
- `pnpm dlx appilot-mcp` after the package is published.
284
+ From npm, with no checkout at all, replace `node` and the bundle path with
285
+ `npx` and `-y appilot-mcp`, then install the skill the package ships:
286
+
287
+ ```bash
288
+ npx -y appilot-mcp install-skill --claude # or --codex, --cursor, --gemini
289
+ ```
290
+
291
+ Without that step the skill stays in the npm cache, where no client looks, and
292
+ the tools arrive with no procedure around them.
189
293
 
190
294
  Do not commit service tokens. Use the MCP client's secure secret storage or
191
295
  environment forwarding when available.
@@ -201,8 +305,10 @@ What it serves:
201
305
 
202
306
  | Path | Purpose |
203
307
  |------|---------|
204
- | `POST /mcp` | The MCP endpoint, Streamable HTTP, bearer required |
205
- | `/.well-known/oauth-protected-resource/mcp` | Points a client at the authorization server |
308
+ | `POST /mcp` | Appilot Studio over Streamable HTTP, bearer required, a configuration scope required |
309
+ | `POST /mcp/runtime` | The Appilot connector, same transport, `runtime:use` required |
310
+ | `/.well-known/oauth-protected-resource/mcp` | Points a Studio client at the authorization server |
311
+ | `/.well-known/oauth-protected-resource/mcp/runtime` | The same for the connector |
206
312
  | `/.well-known/oauth-authorization-server` | Metadata, PKCE with S256, dynamic registration |
207
313
  | `/authorize`, `/token`, `/register` | The OAuth 2.1 flow |
208
314
  | `/consent` | Manual token verification and review |
Binary file
package/dist/cli.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The command line around the MCP server.
3
+ *
4
+ * `npx appilot-mcp` starts the stdio server, which is what an MCP client wants
5
+ * and what nothing else does. Everything here exists because a person or a
6
+ * pipeline typed something else and got a server waiting silently on stdin:
7
+ * `--help` and `--version` did that, and so did every attempt to apply a
8
+ * manifest from CI, which had to script an MCP client to reach a plan and an
9
+ * apply that are two ordinary function calls.
10
+ *
11
+ * `install-skill` is the other half of the same problem. The npm tarball
12
+ * carries `skills/app-configurator`, and it lands in the npm cache, where no
13
+ * client looks. Copying it to the client's own skills directory is the step
14
+ * that was documented only as a marketplace command pointing at a repository
15
+ * that does not exist.
16
+ */
17
+ export declare const HELP = "appilot-mcp 0.4.0\n\nThe Appilot MCP server: read, validate, fix and provision an Appilot app.\n\nUSAGE\n appilot-mcp Start Appilot Studio on stdio (what a client launches)\n appilot-mcp --runtime Start the Appilot connector instead: operate a configured app\n appilot-mcp --http Start the remote HTTP service, which serves both\n appilot-mcp plan <file> Preview an appilot.app-manifest. Writes nothing\n appilot-mcp apply <file> Plan, then apply the same manifest\n appilot-mcp install-skill Copy the app-configurator skill to an agent client\n appilot-mcp --help | --version\n\nOPTIONS\n apply --mode merge|replace How the config half is imported. Default merge\n --allow-unhealthy Import a bundle the health gate would refuse\n install-skill --claude | --codex | --cursor | --gemini | --dir <path>\n\nENVIRONMENT\n APPILOT_BASE_URL Your instance, e.g. https://api.appilot.space or http://localhost:6001\n APPILOT_PAT A scoped service token from the Backoffice, under Service tokens\n APPILOT_APP_ID Optional default app id\n APPILOT_SESSION_TOKEN An Appilot user session, which is what --runtime authenticates with\n\nDocs: https://docs.appilot.space/docs/developers/configure-with-ai/overview";
18
+ /** Where each client looks for a skill. One directory per client, all under $HOME. */
19
+ export declare const SKILL_TARGETS: Record<string, string>;
20
+ /**
21
+ * The bundled skill directory, resolved from the running file.
22
+ *
23
+ * `dist/index.bundle.js` and `skills/` are siblings under the package root in
24
+ * the tarball and in the workspace, so one hop up is the answer in both. The
25
+ * fallback covers a deeper build output rather than guessing silently.
26
+ */
27
+ export declare function bundledSkillDir(from?: string): string | null;
28
+ /**
29
+ * Handle everything that is not "start a server".
30
+ *
31
+ * Returns null when the argv asks for a server, so the entry point stays the
32
+ * transport switch it was.
33
+ */
34
+ export declare function runCommand(argv: string[]): Promise<number | null>;
package/dist/cli.js ADDED
@@ -0,0 +1,173 @@
1
+ /**
2
+ * The command line around the MCP server.
3
+ *
4
+ * `npx appilot-mcp` starts the stdio server, which is what an MCP client wants
5
+ * and what nothing else does. Everything here exists because a person or a
6
+ * pipeline typed something else and got a server waiting silently on stdin:
7
+ * `--help` and `--version` did that, and so did every attempt to apply a
8
+ * manifest from CI, which had to script an MCP client to reach a plan and an
9
+ * apply that are two ordinary function calls.
10
+ *
11
+ * `install-skill` is the other half of the same problem. The npm tarball
12
+ * carries `skills/app-configurator`, and it lands in the npm cache, where no
13
+ * client looks. Copying it to the client's own skills directory is the step
14
+ * that was documented only as a marketplace command pointing at a repository
15
+ * that does not exist.
16
+ */
17
+ import { cpSync, existsSync, readFileSync, mkdirSync } from 'node:fs';
18
+ import { homedir } from 'node:os';
19
+ import { join } from 'node:path';
20
+ import { fileURLToPath } from 'node:url';
21
+ import { SERVER_VERSION } from './version.js';
22
+ import { loadConnection } from './config.js';
23
+ import { AppilotClient } from './client.js';
24
+ import { applyManifest, parseManifest, planManifest } from './manifest.js';
25
+ export const HELP = `appilot-mcp ${SERVER_VERSION}
26
+
27
+ The Appilot MCP server: read, validate, fix and provision an Appilot app.
28
+
29
+ USAGE
30
+ appilot-mcp Start Appilot Studio on stdio (what a client launches)
31
+ appilot-mcp --runtime Start the Appilot connector instead: operate a configured app
32
+ appilot-mcp --http Start the remote HTTP service, which serves both
33
+ appilot-mcp plan <file> Preview an appilot.app-manifest. Writes nothing
34
+ appilot-mcp apply <file> Plan, then apply the same manifest
35
+ appilot-mcp install-skill Copy the app-configurator skill to an agent client
36
+ appilot-mcp --help | --version
37
+
38
+ OPTIONS
39
+ apply --mode merge|replace How the config half is imported. Default merge
40
+ --allow-unhealthy Import a bundle the health gate would refuse
41
+ install-skill --claude | --codex | --cursor | --gemini | --dir <path>
42
+
43
+ ENVIRONMENT
44
+ APPILOT_BASE_URL Your instance, e.g. https://api.appilot.space or http://localhost:6001
45
+ APPILOT_PAT A scoped service token from the Backoffice, under Service tokens
46
+ APPILOT_APP_ID Optional default app id
47
+ APPILOT_SESSION_TOKEN An Appilot user session, which is what --runtime authenticates with
48
+
49
+ Docs: https://docs.appilot.space/docs/developers/configure-with-ai/overview`;
50
+ /** Where each client looks for a skill. One directory per client, all under $HOME. */
51
+ export const SKILL_TARGETS = {
52
+ claude: join('.claude', 'skills'),
53
+ codex: join('.codex', 'skills'),
54
+ cursor: join('.cursor', 'skills'),
55
+ gemini: join('.gemini', 'config', 'skills'),
56
+ };
57
+ /**
58
+ * The bundled skill directory, resolved from the running file.
59
+ *
60
+ * `dist/index.bundle.js` and `skills/` are siblings under the package root in
61
+ * the tarball and in the workspace, so one hop up is the answer in both. The
62
+ * fallback covers a deeper build output rather than guessing silently.
63
+ */
64
+ export function bundledSkillDir(from = fileURLToPath(import.meta.url)) {
65
+ const candidates = [
66
+ join(from, '..', '..', 'skills', 'app-configurator'),
67
+ join(from, '..', '..', '..', 'skills', 'app-configurator'),
68
+ ];
69
+ return candidates.find(candidate => existsSync(join(candidate, 'SKILL.md'))) ?? null;
70
+ }
71
+ function installSkill(args) {
72
+ const source = bundledSkillDir();
73
+ if (!source) {
74
+ process.stderr.write('[appilot-mcp] the bundled skill was not found next to this install. Reinstall the package (npm i -g appilot-mcp), or copy skills/app-configurator from the repository by hand.\n');
75
+ return 1;
76
+ }
77
+ const dirFlag = args.indexOf('--dir');
78
+ let destParent;
79
+ if (dirFlag !== -1) {
80
+ const explicit = args[dirFlag + 1];
81
+ if (!explicit) {
82
+ process.stderr.write('[appilot-mcp] --dir needs a path.\n');
83
+ return 2;
84
+ }
85
+ destParent = explicit;
86
+ }
87
+ else {
88
+ const client = Object.keys(SKILL_TARGETS).find(name => args.includes(`--${name}`));
89
+ if (!client) {
90
+ process.stderr.write(`[appilot-mcp] name the client: ${Object.keys(SKILL_TARGETS).map(c => `--${c}`).join(', ')}, or --dir <path>.\n`);
91
+ return 2;
92
+ }
93
+ destParent = join(homedir(), SKILL_TARGETS[client]);
94
+ }
95
+ const dest = join(destParent, 'app-configurator');
96
+ mkdirSync(destParent, { recursive: true });
97
+ cpSync(source, dest, { recursive: true });
98
+ process.stdout.write(`Installed the app-configurator skill to ${dest}\nStart a new session so the client loads it.\n`);
99
+ return 0;
100
+ }
101
+ function readManifestFile(path) {
102
+ if (!path)
103
+ throw new Error('Name the manifest file: appilot-mcp plan app.appilot.json');
104
+ if (!existsSync(path))
105
+ throw new Error(`No such file: ${path}`);
106
+ return JSON.parse(readFileSync(path, 'utf8'));
107
+ }
108
+ async function runPlan(args) {
109
+ const conn = loadConnection();
110
+ const client = new AppilotClient(conn);
111
+ const manifest = parseManifest(readManifestFile(args[0]));
112
+ const plan = await planManifest(client, manifest, id => {
113
+ const resolved = id ?? conn.defaultAppId;
114
+ return resolved != null && Number.isFinite(resolved) ? resolved : null;
115
+ });
116
+ process.stdout.write(JSON.stringify(plan, null, 2) + '\n');
117
+ return 0;
118
+ }
119
+ /**
120
+ * Apply runs its own plan and passes the token it produced.
121
+ *
122
+ * The token binds an apply to a preview of the same bytes, and a CLI that made
123
+ * the caller carry it between two commands would only teach them to store it.
124
+ * Planning here keeps the property (the manifest cannot change between the two)
125
+ * and prints the preview, so the run is still auditable in a CI log.
126
+ */
127
+ async function runApply(args) {
128
+ const conn = loadConnection();
129
+ const client = new AppilotClient(conn);
130
+ const manifest = parseManifest(readManifestFile(args[0]));
131
+ const modeFlag = args.indexOf('--mode');
132
+ const mode = modeFlag !== -1 ? args[modeFlag + 1] : undefined;
133
+ if (mode !== undefined && mode !== 'merge' && mode !== 'replace') {
134
+ throw new Error(`--mode is merge or replace, not ${mode}.`);
135
+ }
136
+ const plan = await planManifest(client, manifest, id => {
137
+ const resolved = id ?? conn.defaultAppId;
138
+ return resolved != null && Number.isFinite(resolved) ? resolved : null;
139
+ });
140
+ process.stderr.write(`[appilot-mcp] planned ${plan.planToken.slice(0, 12)}…\n`);
141
+ const result = await applyManifest(client, manifest, {
142
+ planToken: plan.planToken,
143
+ mode,
144
+ expectedCurrentHash: plan.config?.expectedCurrentHash,
145
+ allowUnhealthy: args.includes('--allow-unhealthy'),
146
+ });
147
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
148
+ return 0;
149
+ }
150
+ /**
151
+ * Handle everything that is not "start a server".
152
+ *
153
+ * Returns null when the argv asks for a server, so the entry point stays the
154
+ * transport switch it was.
155
+ */
156
+ export async function runCommand(argv) {
157
+ const [command, ...rest] = argv;
158
+ if (command === '--help' || command === '-h' || command === 'help') {
159
+ process.stdout.write(HELP + '\n');
160
+ return 0;
161
+ }
162
+ if (command === '--version' || command === '-v' || command === 'version') {
163
+ process.stdout.write(SERVER_VERSION + '\n');
164
+ return 0;
165
+ }
166
+ if (command === 'install-skill')
167
+ return installSkill(rest);
168
+ if (command === 'plan')
169
+ return runPlan(rest);
170
+ if (command === 'apply')
171
+ return runApply(rest);
172
+ return null;
173
+ }
package/dist/client.d.ts CHANGED
@@ -52,10 +52,19 @@ export interface ProvisionAppResponse {
52
52
  }>;
53
53
  widgetKey?: {
54
54
  id: number | null;
55
- action: 'created' | 'reused' | 'updated';
55
+ /**
56
+ * `blocked` is a real outcome, not an error: a LIVE key needs a verified
57
+ * domain, and the app and its domains are still provisioned without it.
58
+ * The union has to carry it or every caller reads a blocked key as a
59
+ * successful one.
60
+ */
61
+ action: 'created' | 'reused' | 'updated' | 'blocked';
56
62
  keyPrefix?: string;
57
63
  rawKey?: string;
58
64
  rawSecret?: string;
65
+ allowedDomains?: string[];
66
+ /** Present on `blocked`: what is missing and what to do about it. */
67
+ reason?: string;
59
68
  };
60
69
  integration: {
61
70
  apiUrl: string | null;
@@ -91,6 +100,14 @@ export interface ProvisionedAppSummary {
91
100
  verification_status: string;
92
101
  }>;
93
102
  }
103
+ /** A widget key as the org sees it back: no raw value, no secret, ever. */
104
+ export interface ProvisionedWidgetKey {
105
+ id: number;
106
+ name: string;
107
+ keyPrefix: string;
108
+ allowedDomains: string[];
109
+ isActive: boolean;
110
+ }
94
111
  export declare class AppilotApiError extends Error {
95
112
  readonly status: number;
96
113
  readonly body?: unknown;
@@ -169,6 +186,13 @@ export declare class AppilotClient {
169
186
  errors: string[];
170
187
  }>;
171
188
  listProvisioned(): Promise<ProvisionedAppSummary[]>;
189
+ /**
190
+ * The org's widget keys, prefixes only.
191
+ *
192
+ * A caller that cannot see the keys it already minted asks for another every
193
+ * run, and each one is a live credential.
194
+ */
195
+ listWidgetKeys(): Promise<ProvisionedWidgetKey[]>;
172
196
  provisionApp(body: ProvisionAppRequest): Promise<ProvisionAppResponse>;
173
197
  /**
174
198
  * Domain check: does this hostname resolve to a tenant? Public endpoint, so
@@ -180,6 +204,17 @@ export declare class AppilotClient {
180
204
  * the tenant on every single run.
181
205
  */
182
206
  checkDomain(domain: string): Promise<Record<string, unknown>>;
207
+ /**
208
+ * The TXT record a domain needs, and where its verification stands.
209
+ *
210
+ * These two live on the apps router rather than under `/provision`, and that
211
+ * router authenticates an organization SESSION. A service token is refused
212
+ * there today, which is why `verify_domain` falls back to the provisioning
213
+ * dry run for the status and says plainly what it could not do.
214
+ */
215
+ domainVerification(domainId: number): Promise<Record<string, unknown>>;
216
+ /** Ask Appilot to look for the TXT record now. */
217
+ triggerDomainVerification(domainId: number): Promise<Record<string, unknown>>;
183
218
  /** Who this credential is: org, app narrowing, scopes. Never a secret. */
184
219
  whoami(): Promise<Record<string, unknown>>;
185
220
  exportConfig(appId: number): Promise<import('appilot-shared/config-bundle').ConfigBundle>;
@@ -205,6 +240,15 @@ export declare class AppilotClient {
205
240
  * languages it had never claimed to support.
206
241
  */
207
242
  buildSnapshot(appId: number, expectedLocales?: string[]): Promise<ConfigSnapshot>;
243
+ /**
244
+ * Why an empty read should not be believed, or null when the credential is
245
+ * fine and the app genuinely has nothing.
246
+ *
247
+ * Only a 401 or a 403 counts. An instance too old to answer `whoami` at all
248
+ * says nothing about the token, and reporting a gap on that would be a
249
+ * confident false alarm on every on-premise deployment behind cloud.
250
+ */
251
+ private credentialRefusal;
208
252
  /**
209
253
  * Which locales this app's configuration is expected to cover: the union of
210
254
  * its domains' configured languages. Falls back to the trilingual baseline