@valbuild/mcp 0.129.0 → 0.130.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/CHANGELOG.md CHANGED
@@ -1,5 +1,73 @@
1
1
  # @valbuild/mcp
2
2
 
3
+ ## 0.130.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#684](https://github.com/valbuild/val/pull/684) [`cab4098`](https://github.com/valbuild/val/commit/cab4098969585977b8d7574e86d66fcb01cb1d75) Thanks [@freekh](https://github.com/freekh)! - A third ValOps mode, for a host that already holds its own source
8
+
9
+ EXPERIMENTAL. `fs` mode assumes a working tree it can watch and write; `http`
10
+ mode assumes Val's content service owns the patch chain and that a commit is a
11
+ git commit. A host that builds and publishes its own output is neither: it holds
12
+ the source already, it has nowhere to watch, and its "commit" is a new build.
13
+
14
+ Forcing such a host into `fs` mode cost three things, all now fixed: `/stat`
15
+ long-polled against watchers that could never fire, burning CPU for the whole
16
+ hold to learn nothing;
17
+ `/api/val/enable` 500'd; and every read of a `.val.ts` went through a shimmed
18
+ filesystem when the host could simply hand the source over.
19
+
20
+ `ValOpsMemory` takes the source as `sourceFiles`, refuses the local binary
21
+ members by name — this configuration uses Val's remote files — and answers the
22
+ history members with the same closed `not-supported-in-fs-mode` error `ValOpsFS`
23
+ gives, so the History UI degrades the way it already knows how rather than
24
+ inventing a commit list.
25
+
26
+ `getStat` still long-polls -- the hold is what paces the client, and an earlier
27
+ version that answered immediately turned a 20-second poll into a request every
28
+ 6ms -- but it parks on a SIGNAL rather than a timer. This mode owns its store,
29
+ so it is told when something changes: no timers while parked, and a patch
30
+ written by another tab is seen at once rather than up to 250ms later.
31
+
32
+ Two seams come with it. `commitPrepared` lets a host take what a save produced
33
+ instead of a git commit, and `publishOverride` lets a publish be something other
34
+ than a push. Both are opt-in; an app that sets neither behaves exactly as before.
35
+
36
+ The in-memory patch store is explicitly **not durable**. It is behind
37
+ `ValPatchStore`, so a durable implementation is a swap rather than a rewrite,
38
+ but as shipped a restart loses unpublished patches.
39
+
40
+ **Memory mode authenticates.** `ValOps` gained `requiresAuth` alongside
41
+ `patchesAreLocal`, because one flag was answering two questions: whether a store
42
+ auto-saves or publishes (behaviour, reported as `mode` and keyed off by the UI),
43
+ and whether an unauthenticated request may write (security). With two
44
+ implementations the answers coincided — `fs` is a developer's own machine where
45
+ no credential exists, `http` is remote — so `getAuth` was written against
46
+ `patchesAreLocal` and returned anonymous _success_ for a missing cookie, an
47
+ invalid JWT, an unparseable payload, or no configured secret.
48
+
49
+ Memory mode splits them: its store is local, and it runs deployed. It therefore
50
+ requires a verified session, like `http` mode. A host that authorises requests
51
+ before Val sees them can opt out with `unsafelyAllowUnauthenticated`, which is
52
+ spelled that way on purpose and warns at startup. `fs` mode is unchanged.
53
+
54
+ Val's own MCP endpoint refuses memory mode outright. It has the same absence fs
55
+ mode has — no credential, no backend, every permission check on the far side of
56
+ one — and unlike fs mode it is meant to run deployed, so the existing
57
+ "development only" and loopback guards refuse nothing. A host in this mode owns
58
+ its own trust boundary and can offer the tools through it.
59
+
60
+ Internally, the routes' `instanceof ValOpsFS` checks meant "is this a local
61
+ store" — correct with two implementations and silently wrong with three. They are
62
+ now `ValOps.patchesAreLocal` at all 17 policy sites.
63
+
64
+ ### Patch Changes
65
+
66
+ - Updated dependencies [[`cab4098`](https://github.com/valbuild/val/commit/cab4098969585977b8d7574e86d66fcb01cb1d75), [`8425378`](https://github.com/valbuild/val/commit/8425378c315ea46b5d822f1130b633e0449ff1b0), [`cab4098`](https://github.com/valbuild/val/commit/cab4098969585977b8d7574e86d66fcb01cb1d75), [`473a185`](https://github.com/valbuild/val/commit/473a185f70351b44388f3bc1852649e2c1dbe001), [`64f0de3`](https://github.com/valbuild/val/commit/64f0de339b8621cb5a6c422dfe55cae5b2bbe2a0), [`cab4098`](https://github.com/valbuild/val/commit/cab4098969585977b8d7574e86d66fcb01cb1d75)]:
67
+ - @valbuild/server@0.130.0
68
+ - @valbuild/core@0.130.0
69
+ - @valbuild/shared@0.130.0
70
+
3
71
  ## 0.129.0
4
72
 
5
73
  ### Patch Changes
@@ -3365,7 +3365,7 @@ var packageJson = {
3365
3365
  "./package.json": "./package.json"
3366
3366
  },
3367
3367
  types: "dist/valbuild-mcp.cjs.d.ts",
3368
- version: "0.129.0",
3368
+ version: "0.130.0",
3369
3369
  scripts: {
3370
3370
  typecheck: "tsc --noEmit",
3371
3371
  test: "jest",
@@ -4343,7 +4343,7 @@ function initValMcp(valModules, config, opts) {
4343
4343
  }
4344
4344
 
4345
4345
  /**
4346
- * The two ways this route is dangerous, both refused here.
4346
+ * The three ways this route is dangerous, all refused here.
4347
4347
  *
4348
4348
  * 1. **Local filesystem mode outside development.** In fs mode there is no
4349
4349
  * credential and no backend: the tools read and write the running process's
@@ -4361,10 +4361,24 @@ function initValMcp(valModules, config, opts) {
4361
4361
  * needs a credential in fs mode. So a cross-origin `Origin` is refused, and
4362
4362
  * in fs mode the request must actually be addressed to a loopback host.
4363
4363
  *
4364
+ * 3. **Memory mode, always.** A host that holds its own source has the same
4365
+ * absence fs mode has -- no credential, no backend, and every permission
4366
+ * check on the far side of one -- and unlike fs mode it is *meant* to run
4367
+ * deployed, so "only in development" refuses nothing. Neither does the
4368
+ * loopback check: there is no localhost to require. So this is refused
4369
+ * outright rather than narrowed. A host in this mode owns its own trust
4370
+ * boundary and can offer the tools through it; what it cannot do is let
4371
+ * Val's own endpoint write content for anyone who can reach the port.
4372
+ *
4364
4373
  * MCP clients are not browsers and send no `Origin`, so the check costs them
4365
4374
  * nothing.
4366
4375
  */
4367
4376
  function refuseUnsafeRequest(request, mode) {
4377
+ if (mode === "memory") {
4378
+ return jsonResponse(403, {
4379
+ error: "Val: the MCP endpoint is disabled. This project is running in memory mode, where the host holds the source and MCP calls would be unauthenticated writes to it. Serve the tools through the host's own authenticated surface instead."
4380
+ });
4381
+ }
4368
4382
  if (mode === "fs" && process.env.NODE_ENV !== "development") {
4369
4383
  return jsonResponse(403, {
4370
4384
  error: "Val: the MCP endpoint is disabled. This project is running in local filesystem mode, where MCP calls are unauthenticated and write directly to the working tree, so it is only served in development. Configure Val for proxy mode to use MCP on a deployed host."
@@ -3365,7 +3365,7 @@ var packageJson = {
3365
3365
  "./package.json": "./package.json"
3366
3366
  },
3367
3367
  types: "dist/valbuild-mcp.cjs.d.ts",
3368
- version: "0.129.0",
3368
+ version: "0.130.0",
3369
3369
  scripts: {
3370
3370
  typecheck: "tsc --noEmit",
3371
3371
  test: "jest",
@@ -4343,7 +4343,7 @@ function initValMcp(valModules, config, opts) {
4343
4343
  }
4344
4344
 
4345
4345
  /**
4346
- * The two ways this route is dangerous, both refused here.
4346
+ * The three ways this route is dangerous, all refused here.
4347
4347
  *
4348
4348
  * 1. **Local filesystem mode outside development.** In fs mode there is no
4349
4349
  * credential and no backend: the tools read and write the running process's
@@ -4361,10 +4361,24 @@ function initValMcp(valModules, config, opts) {
4361
4361
  * needs a credential in fs mode. So a cross-origin `Origin` is refused, and
4362
4362
  * in fs mode the request must actually be addressed to a loopback host.
4363
4363
  *
4364
+ * 3. **Memory mode, always.** A host that holds its own source has the same
4365
+ * absence fs mode has -- no credential, no backend, and every permission
4366
+ * check on the far side of one -- and unlike fs mode it is *meant* to run
4367
+ * deployed, so "only in development" refuses nothing. Neither does the
4368
+ * loopback check: there is no localhost to require. So this is refused
4369
+ * outright rather than narrowed. A host in this mode owns its own trust
4370
+ * boundary and can offer the tools through it; what it cannot do is let
4371
+ * Val's own endpoint write content for anyone who can reach the port.
4372
+ *
4364
4373
  * MCP clients are not browsers and send no `Origin`, so the check costs them
4365
4374
  * nothing.
4366
4375
  */
4367
4376
  function refuseUnsafeRequest(request, mode) {
4377
+ if (mode === "memory") {
4378
+ return jsonResponse(403, {
4379
+ error: "Val: the MCP endpoint is disabled. This project is running in memory mode, where the host holds the source and MCP calls would be unauthenticated writes to it. Serve the tools through the host's own authenticated surface instead."
4380
+ });
4381
+ }
4368
4382
  if (mode === "fs" && "production" !== "development") {
4369
4383
  return jsonResponse(403, {
4370
4384
  error: "Val: the MCP endpoint is disabled. This project is running in local filesystem mode, where MCP calls are unauthenticated and write directly to the working tree, so it is only served in development. Configure Val for proxy mode to use MCP on a deployed host."
@@ -3356,7 +3356,7 @@ var packageJson = {
3356
3356
  "./package.json": "./package.json"
3357
3357
  },
3358
3358
  types: "dist/valbuild-mcp.cjs.d.ts",
3359
- version: "0.129.0",
3359
+ version: "0.130.0",
3360
3360
  scripts: {
3361
3361
  typecheck: "tsc --noEmit",
3362
3362
  test: "jest",
@@ -4334,7 +4334,7 @@ function initValMcp(valModules, config, opts) {
4334
4334
  }
4335
4335
 
4336
4336
  /**
4337
- * The two ways this route is dangerous, both refused here.
4337
+ * The three ways this route is dangerous, all refused here.
4338
4338
  *
4339
4339
  * 1. **Local filesystem mode outside development.** In fs mode there is no
4340
4340
  * credential and no backend: the tools read and write the running process's
@@ -4352,10 +4352,24 @@ function initValMcp(valModules, config, opts) {
4352
4352
  * needs a credential in fs mode. So a cross-origin `Origin` is refused, and
4353
4353
  * in fs mode the request must actually be addressed to a loopback host.
4354
4354
  *
4355
+ * 3. **Memory mode, always.** A host that holds its own source has the same
4356
+ * absence fs mode has -- no credential, no backend, and every permission
4357
+ * check on the far side of one -- and unlike fs mode it is *meant* to run
4358
+ * deployed, so "only in development" refuses nothing. Neither does the
4359
+ * loopback check: there is no localhost to require. So this is refused
4360
+ * outright rather than narrowed. A host in this mode owns its own trust
4361
+ * boundary and can offer the tools through it; what it cannot do is let
4362
+ * Val's own endpoint write content for anyone who can reach the port.
4363
+ *
4355
4364
  * MCP clients are not browsers and send no `Origin`, so the check costs them
4356
4365
  * nothing.
4357
4366
  */
4358
4367
  function refuseUnsafeRequest(request, mode) {
4368
+ if (mode === "memory") {
4369
+ return jsonResponse(403, {
4370
+ error: "Val: the MCP endpoint is disabled. This project is running in memory mode, where the host holds the source and MCP calls would be unauthenticated writes to it. Serve the tools through the host's own authenticated surface instead."
4371
+ });
4372
+ }
4359
4373
  if (mode === "fs" && process.env.NODE_ENV !== "development") {
4360
4374
  return jsonResponse(403, {
4361
4375
  error: "Val: the MCP endpoint is disabled. This project is running in local filesystem mode, where MCP calls are unauthenticated and write directly to the working tree, so it is only served in development. Configure Val for proxy mode to use MCP on a deployed host."
package/package.json CHANGED
@@ -20,7 +20,7 @@
20
20
  "./package.json": "./package.json"
21
21
  },
22
22
  "types": "dist/valbuild-mcp.cjs.d.ts",
23
- "version": "0.129.0",
23
+ "version": "0.130.0",
24
24
  "preconstruct": {
25
25
  "entrypoints": [
26
26
  "./index.ts",
@@ -31,9 +31,9 @@
31
31
  "dependencies": {
32
32
  "minimatch": "^10.2.6",
33
33
  "zod": "^4.4.3",
34
- "@valbuild/server": "0.129.0",
35
- "@valbuild/core": "0.129.0",
36
- "@valbuild/shared": "0.129.0"
34
+ "@valbuild/shared": "0.130.0",
35
+ "@valbuild/server": "0.130.0",
36
+ "@valbuild/core": "0.130.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@prettier/sync": "^0.6.1",