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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/LICENSE +15 -0
- package/README.md +133 -27
- package/dist/appilot-configurator.mcpb +0 -0
- package/dist/cli.d.ts +34 -0
- package/dist/cli.js +173 -0
- package/dist/client.d.ts +45 -1
- package/dist/client.js +74 -1
- package/dist/config.d.ts +21 -0
- package/dist/config.js +6 -0
- package/dist/contract/healthContract.js +31 -4
- package/dist/index.bundle.js +2111 -826
- package/dist/index.d.ts +7 -1
- package/dist/index.js +22 -4
- package/dist/manifest.d.ts +14 -2
- package/dist/manifest.js +31 -9
- package/dist/public-marketplace/.claude-plugin/marketplace.json +20 -0
- package/dist/public-marketplace/README.md +23 -0
- package/dist/public-marketplace/plugins/app-configurator/.claude-plugin/plugin.json +43 -0
- package/dist/public-marketplace/plugins/app-configurator/README.md +328 -0
- package/dist/public-marketplace/plugins/app-configurator/dist/index.bundle.js +57370 -0
- package/dist/public-marketplace/plugins/app-configurator/skills/app-configurator/SKILL.md +267 -0
- package/dist/public-marketplace/plugins/app-configurator/skills/app-configurator/agents/openai.yaml +13 -0
- package/dist/redaction.d.ts +51 -0
- package/dist/redaction.js +59 -0
- package/dist/remote/consent.d.ts +10 -2
- package/dist/remote/consent.js +16 -6
- package/dist/remote/consentMessages.d.ts +6 -1
- package/dist/remote/consentMessages.js +15 -6
- package/dist/remote/httpServer.d.ts +10 -0
- package/dist/remote/httpServer.js +126 -40
- package/dist/remote/oauth.d.ts +10 -1
- package/dist/remote/oauth.js +29 -11
- package/dist/scaffold.d.ts +68 -6
- package/dist/scaffold.js +424 -97
- package/dist/server.js +175 -18
- package/dist/userClient.d.ts +213 -0
- package/dist/userClient.js +400 -0
- package/dist/userServer.d.ts +47 -0
- package/dist/userServer.js +248 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/examples/app.appilot.json +212 -0
- package/mcpb/manifest.json +117 -21
- package/package.json +5 -3
- package/skills/app-configurator/SKILL.md +61 -19
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "app-configurator",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
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
|
|
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
|
-
|
|
8
|
-
|
|
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
|
|
34
|
+
## Two transports
|
|
25
35
|
|
|
26
|
-
|
|
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
|
-
##
|
|
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
|
|
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
|
-
|
|
117
|
-
plugin
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
156
|
-
|
|
248
|
+
```bash
|
|
249
|
+
npx -y appilot-mcp install-skill --cursor
|
|
250
|
+
```
|
|
157
251
|
|
|
158
|
-
|
|
252
|
+
Then add the server to `~/.cursor/mcp.json`.
|
|
159
253
|
|
|
160
|
-
|
|
254
|
+
### Antigravity
|
|
161
255
|
|
|
162
|
-
|
|
163
|
-
-
|
|
256
|
+
```bash
|
|
257
|
+
npx -y appilot-mcp install-skill --gemini
|
|
258
|
+
```
|
|
164
259
|
|
|
165
|
-
|
|
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
|
-
|
|
188
|
-
`
|
|
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` |
|
|
205
|
-
|
|
|
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
|
-
|
|
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
|