appilot-mcp 0.1.1 → 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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +30 -10
- package/dist/appilot-configurator.mcpb +0 -0
- package/dist/client.d.ts +77 -2
- package/dist/client.js +233 -31
- package/dist/config.d.ts +14 -0
- package/dist/config.js +19 -0
- package/dist/contract/bundleSnapshot.js +8 -1
- package/dist/contract/healthContract.d.ts +1 -1
- package/dist/contract/healthContract.js +69 -6
- package/dist/contract/types.d.ts +37 -1
- package/dist/index.bundle.js +3768 -16479
- package/dist/inspect.d.ts +88 -0
- package/dist/inspect.js +384 -0
- package/dist/redaction.d.ts +18 -3
- package/dist/redaction.js +27 -3
- package/dist/remote/consent.d.ts +22 -20
- package/dist/remote/consent.js +105 -82
- package/dist/remote/consentMessages.d.ts +62 -0
- package/dist/remote/consentMessages.js +196 -0
- package/dist/remote/handoff.d.ts +10 -0
- package/dist/remote/handoff.js +44 -0
- package/dist/remote/httpServer.js +28 -5
- package/dist/remote/oauth.d.ts +39 -6
- package/dist/remote/oauth.js +280 -35
- package/dist/scaffold.d.ts +47 -0
- package/dist/scaffold.js +108 -0
- package/dist/server.js +259 -29
- package/dist/soak.js +21 -1
- package/dist/templates.d.ts +62 -0
- package/dist/templates.js +255 -0
- package/dist/verify.js +18 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/mcpb/manifest.json +11 -5
- package/package.json +1 -1
- package/skills/app-configurator/SKILL.md +79 -10
|
@@ -52,6 +52,11 @@ export function snapshotFromBundle(entities, expectedLocales) {
|
|
|
52
52
|
};
|
|
53
53
|
});
|
|
54
54
|
const zones = entities.zones.map(z => ({ semantic_id: z.semantic_id }));
|
|
55
|
+
const tools = entities.tools.map(t => ({
|
|
56
|
+
tool_name: t.tool_name,
|
|
57
|
+
kind: String(t.runtime_spec?.kind ?? ''),
|
|
58
|
+
view_path: t.view?.path ?? null,
|
|
59
|
+
}));
|
|
55
60
|
const knowledge = entities.knowledge_content.map(k => ({
|
|
56
61
|
id: k.group,
|
|
57
62
|
scope: k.scope,
|
|
@@ -61,5 +66,7 @@ export function snapshotFromBundle(entities, expectedLocales) {
|
|
|
61
66
|
title: b.title,
|
|
62
67
|
})),
|
|
63
68
|
}));
|
|
64
|
-
|
|
69
|
+
// A bundle is complete by construction (the envelope validator refuses a
|
|
70
|
+
// partial one), so there is never a gap on this path.
|
|
71
|
+
return { expectedLocales, views, controls, forms, tools, zones, actionPlans, knowledge };
|
|
65
72
|
}
|
|
@@ -16,4 +16,4 @@ import type { ConfigSnapshot, HealthReport } from './types.js';
|
|
|
16
16
|
* Run the full config health contract over a snapshot. Findings are returned
|
|
17
17
|
* most-severe first, so the report reads like the manual audit.
|
|
18
18
|
*/
|
|
19
|
-
export declare function runHealthContract(
|
|
19
|
+
export declare function runHealthContract(input: ConfigSnapshot): HealthReport;
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { validateActionPlanMarkers } from 'appilot-shared/utils';
|
|
15
15
|
import { IDENTIFIER_KINDS } from 'appilot-shared/validation';
|
|
16
|
-
|
|
16
|
+
import { KNOWLEDGE_SCOPES } from 'appilot-shared/types';
|
|
17
|
+
const VALID_KB_SCOPES = new Set(KNOWLEDGE_SCOPES);
|
|
17
18
|
/** Extract every `{{kind:id}}` marker (with or without a `[label](...)` wrapper). */
|
|
18
19
|
function extractMarkers(step) {
|
|
19
20
|
const rx = /\{\{([a-z_]+):([^}]+)\}\}/g;
|
|
@@ -87,13 +88,30 @@ function lintActionability(snap) {
|
|
|
87
88
|
const entersValue = hasFormStep || hasSetValue || hasFormValues;
|
|
88
89
|
const looksLikeCreate = localizedValues({ ...plan.name, ...plan.description }).some(v => CREATE_INTENT.test(v))
|
|
89
90
|
|| CREATE_INTENT.test(plan.semantic_id);
|
|
90
|
-
|
|
91
|
+
// A plan that enters no value is not automatically inert. The defect
|
|
92
|
+
// this lint exists for is a plan that OPENS or FOCUSES something and
|
|
93
|
+
// stops, which is what "click the input, then nothing" looks like. A
|
|
94
|
+
// plan whose task is a decision, approve, reject, publish, archive,
|
|
95
|
+
// completes that task with the click itself, and there is no value to
|
|
96
|
+
// enter anywhere in it.
|
|
97
|
+
//
|
|
98
|
+
// So the test is what the clicks target. A click on a control that is
|
|
99
|
+
// not one of a form's own fields commits something. Only a plan with no
|
|
100
|
+
// commit at all, or one whose wording promises a create or an edit, is
|
|
101
|
+
// the defect.
|
|
102
|
+
const formFieldIds = new Set(snap.forms.flatMap(f => f.field_ids));
|
|
103
|
+
const commits = markers.some(m => m.kind === 'click' && !formFieldIds.has(m.id));
|
|
104
|
+
if (!entersValue && (looksLikeCreate || !commits)) {
|
|
91
105
|
findings.push({
|
|
92
106
|
severity: looksLikeCreate ? 'critical' : 'high',
|
|
93
107
|
category: 'actionability',
|
|
94
108
|
entity: `action_plan:${plan.semantic_id}`,
|
|
95
|
-
message:
|
|
96
|
-
|
|
109
|
+
message: looksLikeCreate
|
|
110
|
+
? 'Plan enters no value: it has no {{form:…}} step, no set_value step, and empty form_values, so it opens an element and never completes the create or edit its description promises.'
|
|
111
|
+
: 'Plan neither enters a value nor clicks anything outside a form\'s own fields, so it only opens or focuses an element and never completes the task.',
|
|
112
|
+
recommendation: looksLikeCreate
|
|
113
|
+
? 'Add a {{form:…}} step (or a set_value step) to enter the value, then a click step on the submit control to confirm.'
|
|
114
|
+
: 'Add the step that commits the task: a click on the control that confirms it, or a {{form:…}} step followed by a submit.',
|
|
97
115
|
});
|
|
98
116
|
}
|
|
99
117
|
// Submit-without-fill: clicks a form's submit but never fills that form
|
|
@@ -209,7 +227,7 @@ function lintKnowledge(snap) {
|
|
|
209
227
|
findings.push({
|
|
210
228
|
severity: 'high', category: 'kb-scope', entity,
|
|
211
229
|
message: `Invalid knowledge scope "${kb.scope}".`,
|
|
212
|
-
recommendation:
|
|
230
|
+
recommendation: `Use one of ${KNOWLEDGE_SCOPES.join(', ')}.`,
|
|
213
231
|
});
|
|
214
232
|
}
|
|
215
233
|
const languages = new Set(kb.bodies.map(b => b.language));
|
|
@@ -274,14 +292,58 @@ function lintIdentifiers(snap) {
|
|
|
274
292
|
lintIdentifier(z.semantic_id, `zone:${z.semantic_id}`, findings);
|
|
275
293
|
for (const p of snap.actionPlans)
|
|
276
294
|
lintIdentifier(p.semantic_id, `action_plan:${p.semantic_id}`, findings);
|
|
295
|
+
// A tool name is its own identifier kind: it reaches the model as a callable
|
|
296
|
+
// name, so it follows the tool_name rule rather than semantic_id. Medium, not
|
|
297
|
+
// high: an off-shape name still resolves at runtime, and a high finding would
|
|
298
|
+
// turn importing a working configuration that predates the rule into a 422.
|
|
299
|
+
for (const t of snap.tools) {
|
|
300
|
+
if (!IDENTIFIER_KINDS.tool_name.isValid(t.tool_name)) {
|
|
301
|
+
findings.push({
|
|
302
|
+
severity: 'medium', category: 'identifier', entity: `tool:${t.tool_name}`,
|
|
303
|
+
message: `Tool name "${t.tool_name}" is not a valid tool_name (${IDENTIFIER_KINDS.tool_name.rule}).`,
|
|
304
|
+
recommendation: `Rename it, for example ${IDENTIFIER_KINDS.tool_name.example}. The model calls the tool by this name.`,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
277
308
|
return findings;
|
|
278
309
|
}
|
|
310
|
+
// ---------------------------------------------------------------------------
|
|
311
|
+
// Lint 7 — snapshot completeness.
|
|
312
|
+
//
|
|
313
|
+
// Reported as findings rather than thrown, because a partial snapshot is still
|
|
314
|
+
// worth auditing. What must not happen is a partial snapshot reading as a clean
|
|
315
|
+
// bill of health: every lint below an unreadable entity silently passes.
|
|
316
|
+
// ---------------------------------------------------------------------------
|
|
317
|
+
function lintGaps(snap) {
|
|
318
|
+
return (snap.gaps ?? []).map(g => ({
|
|
319
|
+
severity: 'high',
|
|
320
|
+
category: 'coverage',
|
|
321
|
+
entity: `snapshot:${g.entity}`,
|
|
322
|
+
message: `${g.entity} could not be read, so no ${g.entity} lint ran: ${g.reason}`,
|
|
323
|
+
recommendation: 'Fix the access problem and re-run. Treat every finding in this report as incomplete until this line is gone.',
|
|
324
|
+
}));
|
|
325
|
+
}
|
|
279
326
|
const SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
280
327
|
/**
|
|
281
328
|
* Run the full config health contract over a snapshot. Findings are returned
|
|
282
329
|
* most-severe first, so the report reads like the manual audit.
|
|
283
330
|
*/
|
|
284
|
-
export function runHealthContract(
|
|
331
|
+
export function runHealthContract(input) {
|
|
332
|
+
// Normalise once. A snapshot reaches this function from three producers (the
|
|
333
|
+
// live-API client, the bundle adapter, the backend import path) and every lint
|
|
334
|
+
// below iterates these arrays without a guard; one producer omitting one of
|
|
335
|
+
// them would otherwise crash the audit instead of reporting on it.
|
|
336
|
+
const snap = {
|
|
337
|
+
...input,
|
|
338
|
+
expectedLocales: input.expectedLocales ?? [],
|
|
339
|
+
views: input.views ?? [],
|
|
340
|
+
controls: input.controls ?? [],
|
|
341
|
+
forms: input.forms ?? [],
|
|
342
|
+
tools: input.tools ?? [],
|
|
343
|
+
zones: input.zones ?? [],
|
|
344
|
+
actionPlans: input.actionPlans ?? [],
|
|
345
|
+
knowledge: input.knowledge ?? [],
|
|
346
|
+
};
|
|
285
347
|
const findings = [
|
|
286
348
|
...lintMarkers(snap),
|
|
287
349
|
...lintActionability(snap),
|
|
@@ -289,6 +351,7 @@ export function runHealthContract(snap) {
|
|
|
289
351
|
...lintI18n(snap),
|
|
290
352
|
...lintKnowledge(snap),
|
|
291
353
|
...lintIdentifiers(snap),
|
|
354
|
+
...lintGaps(snap),
|
|
292
355
|
].sort((a, b) => SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity]);
|
|
293
356
|
const counts = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
294
357
|
for (const f of findings)
|
package/dist/contract/types.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* same validator works online, offline (air-gapped), and in unit fixtures.
|
|
8
8
|
*/
|
|
9
9
|
export type Severity = 'critical' | 'high' | 'medium' | 'low';
|
|
10
|
-
export type FindingCategory = 'markers' | 'actionability' | 'selector-stability' | 'i18n-coverage' | 'kb-scope' | 'kb-procedure' | 'kb-hygiene' | 'identifier';
|
|
10
|
+
export type FindingCategory = 'markers' | 'actionability' | 'selector-stability' | 'i18n-coverage' | 'kb-scope' | 'kb-procedure' | 'kb-hygiene' | 'identifier' | 'coverage';
|
|
11
11
|
export interface Finding {
|
|
12
12
|
severity: Severity;
|
|
13
13
|
category: FindingCategory;
|
|
@@ -21,6 +21,9 @@ export interface Finding {
|
|
|
21
21
|
/** A per-locale text map, e.g. `{ de: 'Neuer Ordner', en: 'New folder' }`. */
|
|
22
22
|
export type LocalizedText = Record<string, string | undefined>;
|
|
23
23
|
export interface ControlSnapshot {
|
|
24
|
+
/** Row id, when the snapshot came from the live API. Absent from a bundle,
|
|
25
|
+
* which is keyed semantically. `update_control` needs it. */
|
|
26
|
+
id?: string;
|
|
24
27
|
semantic_id: string;
|
|
25
28
|
locator_type: string;
|
|
26
29
|
locator: string;
|
|
@@ -34,6 +37,8 @@ export interface FormSnapshot {
|
|
|
34
37
|
entry_control_id?: string | null;
|
|
35
38
|
}
|
|
36
39
|
export interface ActionPlanSnapshot {
|
|
40
|
+
/** Row id, when the snapshot came from the live API. See ControlSnapshot.id. */
|
|
41
|
+
id?: string;
|
|
37
42
|
semantic_id: string;
|
|
38
43
|
sections: Array<{
|
|
39
44
|
view_path?: string;
|
|
@@ -52,9 +57,25 @@ export interface ActionPlanSnapshot {
|
|
|
52
57
|
is_active?: boolean;
|
|
53
58
|
}
|
|
54
59
|
export interface ZoneSnapshot {
|
|
60
|
+
id?: string;
|
|
55
61
|
semantic_id: string;
|
|
56
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* A tool as the agent needs to reason about it: what it is called, what kind of
|
|
65
|
+
* runtime backs it, and whether it is bound to a view. Not the full definition;
|
|
66
|
+
* `export_config` carries that.
|
|
67
|
+
*/
|
|
68
|
+
export interface ToolSnapshot {
|
|
69
|
+
id?: string;
|
|
70
|
+
tool_name: string;
|
|
71
|
+
/** `http_proxy`, `client_action`, … Empty when the instance does not say. */
|
|
72
|
+
kind: string;
|
|
73
|
+
view_path?: string | null;
|
|
74
|
+
is_active?: boolean;
|
|
75
|
+
}
|
|
57
76
|
export interface KnowledgeSnapshot {
|
|
77
|
+
/** Row id on the live-API path (what `update_knowledge` takes), the
|
|
78
|
+
* translation group on the bundle path. */
|
|
58
79
|
id: string | number;
|
|
59
80
|
scope: string;
|
|
60
81
|
/** Body text per language row (knowledge_content is one row per language). */
|
|
@@ -69,15 +90,30 @@ export interface ViewSnapshot {
|
|
|
69
90
|
path: string;
|
|
70
91
|
name: LocalizedText;
|
|
71
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* An entity the snapshot could not read, and why.
|
|
95
|
+
*
|
|
96
|
+
* A snapshot with an empty `knowledge` array and a snapshot whose knowledge read
|
|
97
|
+
* was refused look identical, and the second one silently turns every knowledge
|
|
98
|
+
* lint into a pass. Recording the failure is what lets `read_config` and
|
|
99
|
+
* `validate_config` say "I could not see this" instead of "this is fine".
|
|
100
|
+
*/
|
|
101
|
+
export interface SnapshotGap {
|
|
102
|
+
entity: 'views' | 'controls' | 'forms' | 'tools' | 'zones' | 'actionPlans' | 'knowledge';
|
|
103
|
+
reason: string;
|
|
104
|
+
}
|
|
72
105
|
export interface ConfigSnapshot {
|
|
73
106
|
/** Locales the config is expected to cover, e.g. ['de','en','es']. */
|
|
74
107
|
expectedLocales: string[];
|
|
75
108
|
views: ViewSnapshot[];
|
|
76
109
|
controls: ControlSnapshot[];
|
|
77
110
|
forms: FormSnapshot[];
|
|
111
|
+
tools: ToolSnapshot[];
|
|
78
112
|
actionPlans: ActionPlanSnapshot[];
|
|
79
113
|
zones: ZoneSnapshot[];
|
|
80
114
|
knowledge: KnowledgeSnapshot[];
|
|
115
|
+
/** Entities that could not be read. Empty on a complete snapshot. */
|
|
116
|
+
gaps?: SnapshotGap[];
|
|
81
117
|
}
|
|
82
118
|
export interface HealthReport {
|
|
83
119
|
ok: boolean;
|