@nullplatform/mcp 0.1.12 → 0.1.14

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 (52) hide show
  1. package/dist/i18n.js +6 -0
  2. package/dist/np/context.js +6 -2
  3. package/dist/np/journey.js +15 -0
  4. package/dist/surfaces/developer.js +10 -1
  5. package/dist/tool-names.js +2 -0
  6. package/dist/tools/approvals.js +1 -1
  7. package/dist/tools/builds.js +1 -1
  8. package/dist/tools/create-app.js +40 -28
  9. package/dist/tools/create-link.js +1 -1
  10. package/dist/tools/create-release.js +1 -1
  11. package/dist/tools/create-scope.js +1 -1
  12. package/dist/tools/create-service.js +12 -12
  13. package/dist/tools/delete-link.js +1 -1
  14. package/dist/tools/delete-param.js +1 -1
  15. package/dist/tools/delete-service.js +1 -1
  16. package/dist/tools/deploy.js +1 -1
  17. package/dist/tools/deployments.js +1 -1
  18. package/dist/tools/entity-get.js +36 -0
  19. package/dist/tools/entity-list.js +99 -0
  20. package/dist/tools/find-apps.js +5 -2
  21. package/dist/tools/index.js +4 -0
  22. package/dist/tools/logs.js +1 -1
  23. package/dist/tools/metrics.js +1 -1
  24. package/dist/tools/overview.js +1 -1
  25. package/dist/tools/params.js +1 -1
  26. package/dist/tools/playbook.js +1 -1
  27. package/dist/tools/releases.js +1 -1
  28. package/dist/tools/services.js +1 -1
  29. package/dist/tools/set-params.js +1 -1
  30. package/dist/tools/shared.js +43 -0
  31. package/dist/tools/status.js +1 -1
  32. package/dist/tools/traffic.js +1 -1
  33. package/dist/tools/update-link.js +1 -1
  34. package/dist/tools/update-service.js +1 -1
  35. package/package.json +4 -1
  36. package/widgets-dist/approvals.html +10 -2
  37. package/widgets-dist/builds.html +10 -2
  38. package/widgets-dist/create-app.html +18 -10
  39. package/widgets-dist/deployments.html +10 -2
  40. package/widgets-dist/find-apps.html +10 -2
  41. package/widgets-dist/logs.html +10 -2
  42. package/widgets-dist/manifest.json +16 -16
  43. package/widgets-dist/metrics.html +10 -2
  44. package/widgets-dist/np-panel.html +10 -2
  45. package/widgets-dist/overview.html +19 -11
  46. package/widgets-dist/params.html +22 -14
  47. package/widgets-dist/releases.html +10 -2
  48. package/widgets-dist/service-action.html +9 -1
  49. package/widgets-dist/service-create.html +10 -2
  50. package/widgets-dist/service-delete.html +9 -1
  51. package/widgets-dist/service-link.html +10 -2
  52. package/widgets-dist/services.html +15 -7
@@ -8,7 +8,7 @@ import { appArg, chooseScope, requireApp } from "./shared.js";
8
8
  export const metricsTool = defineTool({
9
9
  name: TOOL.applicationMetricList,
10
10
  title: "Performance metrics",
11
- description: "The golden signals of a scope — throughput (rpm), response time, error rate, CPU and memory — with sparkline trends over a time window. Use to answer 'how is it performing?' or to watch health during a rollout.",
11
+ description: 'The golden signals of ONE scope — throughput (rpm), response time, error rate, CPU, memory — with sparkline trends, rendered as a metrics PANEL. Use it for "how is it performing?", "is it healthy?", or to watch a scope during a rollout. Pick the `window` (1h/3h/24h/7d, default 3h); omit `scope` to use the only one. Does NOT return logs or deploy state — use application_log_list or application_get. Read-only.',
12
12
  annotations: { readOnlyHint: true, openWorldHint: true },
13
13
  widget: "metrics",
14
14
  errorKey: "metrics.errorLabel",
@@ -14,7 +14,7 @@ const MAX_APPS = 30;
14
14
  export const overviewTool = defineTool({
15
15
  name: TOOL.organizationGet,
16
16
  title: "Organization overview",
17
- description: "A cross-application health digest for the whole org: what's mid-rollout right now, and which scopes' last deployment failed or rolled back. Answers 'is anything broken?' / 'what's deploying?' without naming an app. Scans up to the first ~30 applications.",
17
+ description: 'A cross-application health digest for the WHOLE org, rendered as a panel: what\'s mid-rollout right now, and which scopes\' last deployment failed or rolled back. Use it for org-wide questions that name no app — "is anything broken?", "what\'s deploying?", "any failed rollouts?". Scans up to the first ~30 applications (truncation is reported, never silent). Does NOT show one app\'s full detail (use application_get), nor logs/metrics; to find the latest release or build ACROSS apps, gather headlessly with entity_list and compute it — don\'t call this. Read-only.',
18
18
  annotations: { readOnlyHint: true, openWorldHint: true },
19
19
  widget: "overview",
20
20
  errorKey: "overview.errorLabel",
@@ -24,7 +24,7 @@ function valueSummary(parameter, scopeNameById) {
24
24
  export const paramsTool = defineTool({
25
25
  name: TOOL.applicationParameterList,
26
26
  title: "Parameters",
27
- description: "List an application's configuration parameters and every value they hold across scopes and dimensions (env vars / files; secret values are masked). Pass `scope` to resolve the single effective value per parameter for that scope. Use application_parameter_create to add or change them.",
27
+ description: 'List ONE application\'s configuration parameters and EVERY value each holds across scopes and dimensions (env vars / files; secret values are masked, never shown in plaintext), rendered as a PANEL. Use it for "what env vars does this app have", "show the config", "what\'s DATABASE_URL set to". Pass `scope` to have the PLATFORM resolve the single effective value per parameter for that scope (it collapses scope value › most-specific dimension match › app default — never reimplement that precedence yourself). Does NOT change anything — use application_parameter_create to add/update, application_parameter_delete to remove. Read-only.',
28
28
  annotations: { readOnlyHint: true, openWorldHint: true },
29
29
  widget: "params",
30
30
  errorKey: "params.errorLabel",
@@ -14,7 +14,7 @@ const playbookBody = (markdown) => markdown.replace(/^---\n[\s\S]*?\n---\n?/, ""
14
14
  export const playbookGetTool = defineTool({
15
15
  name: TOOL.playbookGet,
16
16
  title: "Operating playbooks",
17
- description: "Read a nullplatform operating playbookthe methodology to follow BEFORE the matching non-trivial work (e.g. deploying-safely before a deploy, incident-response when something is broken, configuring-safely before changing parameters or secrets). The server instructions carry the full catalog; call this with no name to list them too.",
17
+ description: 'Read a nullplatform operating PLAYBOOKmodel-facing methodology for RISKY or MULTI-STEP work: deploying-safely (canary steps + metric gates for a production rollout), incident-response (triage when something is broken), configuring-safely (changing parameters or secrets carefully). Renders NO panel prose for YOU to consult, NOT a step you must run before every action. Reach for it only when you genuinely want the grounded procedure for a non-trivial operation; for a direct, simple command ("deploy", "set X", "roll back") just use that action\'s tool — do not detour here first. Call with no `name` to list the catalog (also in the server instructions).',
18
18
  annotations: { readOnlyHint: true },
19
19
  // No widget: a playbook is model-facing methodology prose, not an interactive entity — it
20
20
  // renders as text (the markdown below) for the model to read and act on. See CLAUDE.md.
@@ -12,7 +12,7 @@ import { appArg, offsetArg, pageOf, requireApp } from "./shared.js";
12
12
  export const releasesTool = defineTool({
13
13
  name: TOOL.applicationReleaseList,
14
14
  title: "Releases",
15
- description: "List an application's releases — semver, status (active/deprecated/unstable/failed), the build each pins, and age. Use to pick a release to deploy or promote, or to see what's shippable.",
15
+ description: "List ONE application's releases as a PANEL for the user — semver, status (active/deprecated/unstable/failed), the build each pins, and age. Use to pick a release to deploy or promote, or to see what's shippable. Do NOT call this once per app to compare across apps (it renders a panel each time): for an AGGREGATE like 'the latest release across all my apps' use entity_list (entity:\"release\"), gather headlessly, and compute the answer.",
16
16
  annotations: { readOnlyHint: true, openWorldHint: true },
17
17
  widget: "releases",
18
18
  errorKey: "releaseList.errorLabel",
@@ -13,7 +13,7 @@ import { appArg, requireApp } from "./shared.js";
13
13
  export const servicesTool = defineTool({
14
14
  name: TOOL.applicationServiceList,
15
15
  title: "Services & dependencies",
16
- description: "List the dependency services (databases, queues, caches…) attached to an application, plus the catalog of dependency types available to provision. Read-only: provisioning a new dependency is a guided flow, so this links you into the dashboard to create one.",
16
+ description: 'List the dependency services (databases, queues, caches…) attached to ONE application, plus the catalog of dependency types available to provision, rendered as a PANEL. Use it for "what databases/queues does this app use", "show its dependencies", "what can I provision". To provision a new dependency use application_service_create; to connect one to a scope use application_link_create. Read-only it does not provision or link anything itself.',
17
17
  annotations: { readOnlyHint: true, openWorldHint: true },
18
18
  widget: "services",
19
19
  errorKey: "services.errorLabel",
@@ -8,7 +8,7 @@ import { appArg, pickScope, requireApp } from "./shared.js";
8
8
  export const setParamsTool = defineTool({
9
9
  name: TOOL.applicationParameterCreate,
10
10
  title: "Set parameters",
11
- description: "Create or update application configuration parameters (environment variables or files; mark secrets). Target a value at the application (default — every scope), by dimension (applies to any matching scope), or pinned to one scope. Values apply on the NEXT deploy.",
11
+ description: 'Create or UPDATE application configuration parameters environment variables or files, secrets supported (stored masked, updatable; NOT write-once). A parameter is a value-SET: one definition holds many values keyed by target. Target a value at the APPLICATION (default — every scope), BY DIMENSION (e.g. environment=production — applies to any matching scope), or pinned to ONE scope (dimensions and a scope are mutually exclusive). Use it for "set DATABASE_URL", "add an env var", "change a secret", "set a prod-only value". Values apply on the NEXT deploy. Renders the parameters panel after applying. Convergent: reuses the existing definition and updates in place rather than duplicating. View current values with application_parameter_list; remove with application_parameter_delete.',
12
12
  annotations: { destructiveHint: false, idempotentHint: true, openWorldHint: true },
13
13
  widget: "params",
14
14
  errorKey: "setParams.errorLabel",
@@ -75,6 +75,49 @@ export async function requireApp(context, args) {
75
75
  }
76
76
  return { out: fail(notFoundMessage(resolution), { not_found: true }) };
77
77
  }
78
+ /**
79
+ * The ONE pre-selection rule for every create/fill form selector (namespace, template, service spec,
80
+ * scope type…). It turns the model's soft inference — an explicit id, or the free-text stack/type it
81
+ * reasoned out of the conversation — into a concrete option, or `undefined` when it's a genuine
82
+ * unhinted choice. It NEVER returns an arbitrary first entry: a wrong pre-selection is worse than an
83
+ * honest blank. Division of labour (see CLAUDE.md): the MODEL infers (it holds the context) and passes
84
+ * the hint as a tool arg; the TOOL resolves it here; the WIDGET only mirrors the result.
85
+ *
86
+ * Order, most to least certain: explicit id → exact name → every query token present in the option's
87
+ * haystack (fuzzy "sql database") → an option name contained in the hint ("Golang Hexagonal - API" ⊃
88
+ * the "golang-hexagonal" template) → with `pinOnly`, the sole candidate. `haystack` widens the
89
+ * searchable text per field (name + tags, name + category…); it defaults to the option's name.
90
+ */
91
+ export function resolveChoice(options, hint, settings = {}) {
92
+ if (hint.id != null) {
93
+ const byId = options.find((option) => option.id === hint.id);
94
+ if (byId)
95
+ return byId;
96
+ }
97
+ const query = hint.text?.trim().toLowerCase();
98
+ if (query) {
99
+ const haystackOf = settings.haystack ?? ((option) => option.name);
100
+ const exact = options.filter((option) => option.name.trim().toLowerCase() === query);
101
+ if (exact.length === 1)
102
+ return exact[0];
103
+ const tokens = query.split(/\s+/).filter(Boolean);
104
+ const tokenMatch = options.filter((option) => {
105
+ const haystack = haystackOf(option).toLowerCase();
106
+ return tokens.every((token) => haystack.includes(token));
107
+ });
108
+ if (tokenMatch.length === 1)
109
+ return tokenMatch[0];
110
+ const contained = options.filter((option) => {
111
+ const name = option.name.trim().toLowerCase();
112
+ return name.length >= 2 && query.includes(name);
113
+ });
114
+ if (contained.length === 1)
115
+ return contained[0];
116
+ }
117
+ if (settings.pinOnly && options.length === 1)
118
+ return options[0];
119
+ return undefined;
120
+ }
78
121
  /**
79
122
  * Match scopes by name OR by dimensions: "dev" (name substring), "production"
80
123
  * (dimension value substring), "environment=production" (exact key=value).
@@ -41,7 +41,7 @@ export async function buildAppStatus(context, app) {
41
41
  export const statusTool = defineTool({
42
42
  name: TOOL.applicationGet,
43
43
  title: "Application status",
44
- description: "THE place to start. Shows an application's full picture: scopes with what's live on each (release + traffic), latest build, latest release, and the one obvious next action. Call with no arguments inside a repo to use the linked app. Pass deployment:<id> to watch one rollout in detail.",
44
+ description: 'THE place to start for ONE application\'s live status — renders a status PANEL: each scope with what\'s live on it (release + traffic), the latest build, the latest release, and the single obvious next action. Use it for "what\'s the status of my app", "what\'s deployed where", "is it healthy" — a STATUS question. For a DIRECT action ("deploy", "send traffic", "show logs/metrics") reach for that action\'s own tool instead of starting here; only read status first when you genuinely need to see state before acting. Call with NO arguments inside a git repo to use the linked app; pass `app` (name or "#id") for a different one; pass `deployment:<id>` to watch one rollout\'s live detail. Does NOT return logs, metrics, parameters, or a build\'s assets — use application_log_list / application_metric_list / application_parameter_list / application_build_list for those. Do NOT loop this to WAIT for a build or deployment to finish: it repaints the whole panel every call (stacking duplicate panels) — poll headlessly with entity_get (entity:"build" or "deployment", id:<id>) instead, and show this panel once at the end.',
45
45
  annotations: { readOnlyHint: true, openWorldHint: true },
46
46
  widget: "np-panel",
47
47
  errorKey: "status.errorLabel",
@@ -9,7 +9,7 @@ import { appArg, delays, requireApp, sleep } from "./shared.js";
9
9
  export const trafficTool = defineTool({
10
10
  name: TOOL.applicationDeploymentUpdate,
11
11
  title: "Update traffic",
12
- description: 'Drive an in-flight rollout: move traffic to the new version (percent snaps to 0,1,5,10,25,50,75,90,95,99,100), finalize it (action:"finalize" retire the old version), or roll it back (action:"rollback" traffic returns to the old version). Finds the app\'s active deployment automatically.',
12
+ description: 'Drive an IN-FLIGHT rollout the step AFTER application_deployment_create. Call it DIRECTLY with `percent` (snaps to 0,1,5,10,25,50,75,90,95,99,100) to move traffic, action:"finalize" to retire the old version, or action:"rollback" to return traffic to the previous version (the safety hatch). You do NOT need to read status or look up a deployment id first — it finds the app\'s single active rollout automatically (pass `deployment_id` only when several are in flight at once). Use it for "send 25% traffic", "promote to 100%", "finalize", "roll back". Renders the live rollout panel. Does NOT create a deployment — use application_deployment_create first; this only steers one that already exists.',
13
13
  annotations: { destructiveHint: true, openWorldHint: true },
14
14
  widget: "np-panel",
15
15
  errorKey: "traffic.errorLabel",
@@ -15,7 +15,7 @@ import { appArg, requireApp } from "./shared.js";
15
15
  export const updateLinkTool = defineTool({
16
16
  name: TOOL.applicationLinkUpdate,
17
17
  title: "Run link action",
18
- description: "Run a custom action on a link (a service↔application connection) — the platform-team-defined operations its link specification exposes. Pass `link` to choose which one (by name) and `action` to choose which action; call WITHOUT `run:true` first to open the form. Not every link has custom actions.",
18
+ description: 'Run a CUSTOM ACTION on a link (a service↔application connection) — the platform-team-defined operations its link specification exposes. Use it for "run the <action> on the <link>". Pass `link` (by name) and `action`; call WITHOUT `run:true` first to open the form, the user confirms by submitting. Not every link has custom actions. Renders the action panel. Not for creating (application_link_create) or deleting (application_link_delete) a link.',
19
19
  annotations: { destructiveHint: false, openWorldHint: true },
20
20
  widget: "service-action",
21
21
  errorKey: "runAction.errorLabel",
@@ -16,7 +16,7 @@ import { appArg, requireApp } from "./shared.js";
16
16
  export const updateServiceTool = defineTool({
17
17
  name: TOOL.applicationServiceUpdate,
18
18
  title: "Run service action",
19
- description: "Run a custom action on a provisioned service — the platform-team-defined operations a service exposes (e.g. a Postgres database's DML/DDL query runners). Pass `service` to choose which one (by name) and `action` to choose which action; call WITHOUT `run:true` first to open the form, the user confirms by submitting it. A custom action is an explicit operation (not idempotent), so it never runs without that submit.",
19
+ description: 'Run a CUSTOM ACTION on a provisioned service — the platform-team-defined operations a service exposes (e.g. a Postgres database\'s DML/DDL query runners). Use it for "run a query on <db>", "run the <action> on <service>". Pass `service` (by name) and `action`; call WITHOUT `run:true` first to open the form, the user confirms by submitting. A custom action is an explicit operation (NOT idempotent), so it never runs without that submit. Renders the action panel. Not for provisioning (application_service_create) or deleting (application_service_delete) a service.',
20
20
  annotations: { destructiveHint: false, openWorldHint: true },
21
21
  widget: "service-action",
22
22
  errorKey: "runAction.errorLabel",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nullplatform/mcp",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "nullplatform from your code assistant — an MCP server that replaces the dashboard for the everyday developer journey",
5
5
  "license": "MIT",
6
6
  "author": "nullplatform",
@@ -41,6 +41,7 @@
41
41
  "pretest": "node scripts/build-widgets.mjs",
42
42
  "lint": "biome check .",
43
43
  "lint:fix": "biome check --write .",
44
+ "eval": "tsx scripts/eval/run.ts",
44
45
  "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p src/widgets-react/tsconfig.json --noEmit"
45
46
  },
46
47
  "dependencies": {
@@ -49,6 +50,7 @@
49
50
  "zod": "^3.23.8"
50
51
  },
51
52
  "devDependencies": {
53
+ "@anthropic-ai/sdk": "^0.105.0",
52
54
  "@biomejs/biome": "2.4.16",
53
55
  "@jsonforms/core": "^3.5.1",
54
56
  "@jsonforms/react": "^3.5.1",
@@ -56,6 +58,7 @@
56
58
  "@types/node": "^20.14.0",
57
59
  "@types/react": "^19.2.17",
58
60
  "@types/react-dom": "^19.2.3",
61
+ "dotenv": "^17.4.2",
59
62
  "esbuild": "^0.28.0",
60
63
  "happy-dom": "^20.10.3",
61
64
  "preact": "^10.29.2",