@valbuild/server 0.119.0 → 0.120.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/CHANGELOG.md +33 -0
- package/dist/declarations/src/tools/createValTools.d.ts +25 -1
- package/dist/declarations/src/tools/defineTool.d.ts +69 -0
- package/dist/valbuild-server.cjs.dev.js +12 -1
- package/dist/valbuild-server.cjs.prod.js +12 -1
- package/dist/valbuild-server.esm.js +12 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# @valbuild/server
|
|
2
2
|
|
|
3
|
+
## 0.120.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#590](https://github.com/valbuild/val/pull/590) [`6f318d4`](https://github.com/valbuild/val/commit/6f318d406295b772e721bf463283f47e2822e996) Thanks [@freekh](https://github.com/freekh)! - MCP: survive a signing-key rotation, and say what a refused local token means.
|
|
8
|
+
|
|
9
|
+
`@valbuild/next` caches the authorization server's JWKS for five minutes. Until
|
|
10
|
+
now a token signed with a key that arrived inside that window was refused
|
|
11
|
+
outright, so every warm instance rejected valid tokens until the cache expired —
|
|
12
|
+
a rotation on the issuer's side showed up as an outage on yours.
|
|
13
|
+
|
|
14
|
+
A token naming a key the cache does not hold now provokes one refetch of the key
|
|
15
|
+
set, at most once per issuer every 30 seconds. The rate limit matters because the
|
|
16
|
+
key id comes from the token: without it, unknown key ids would be a way to make
|
|
17
|
+
your app call its issuer once per request. It limits how often a fetch is
|
|
18
|
+
_started_, so requests that arrive while one is already running join it — which
|
|
19
|
+
is the normal shape of a rotation, where many requests meet the new key at once.
|
|
20
|
+
|
|
21
|
+
Separately, an MCP call that presents an access token to a project running in
|
|
22
|
+
local filesystem mode is still refused — there is nothing to authenticate against
|
|
23
|
+
— but the message now names the cause, which is that the project has an `oauth`
|
|
24
|
+
issuer configured (often `VAL_OAUTH_ISSUER` in a local `.env`) and should not
|
|
25
|
+
have one for local development.
|
|
26
|
+
|
|
27
|
+
## 0.120.0
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- Updated dependencies [[`c2d3c0e`](https://github.com/valbuild/val/commit/c2d3c0e6c2010c0a94c725d9dbaa618998773e8a)]:
|
|
32
|
+
- @valbuild/core@0.120.0
|
|
33
|
+
- @valbuild/shared@0.120.0
|
|
34
|
+
- @valbuild/ui@0.120.0
|
|
35
|
+
|
|
3
36
|
## 0.119.0
|
|
4
37
|
|
|
5
38
|
### Patch Changes
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { ValModules } from "@valbuild/core";
|
|
2
2
|
import type { ValServerConfig } from "../ValServer.js";
|
|
3
|
-
import {
|
|
3
|
+
import type { ValOps } from "../ValOps.js";
|
|
4
|
+
import type { ValToolState } from "./defineTool.js";
|
|
5
|
+
import { type ValToolResult, type ValTools } from "./types.js";
|
|
4
6
|
export type ValToolsOptions = ValServerConfig;
|
|
5
7
|
/**
|
|
6
8
|
* Val's server-side tool registry.
|
|
@@ -15,3 +17,25 @@ export type ValToolsOptions = ValServerConfig;
|
|
|
15
17
|
* host adapts {@link ValToolResult} at its own edge.
|
|
16
18
|
*/
|
|
17
19
|
export declare function createValTools(valModules: ValModules, options: ValToolsOptions): ValTools;
|
|
20
|
+
/**
|
|
21
|
+
* The content as the caller should see it, loaded once per call.
|
|
22
|
+
*
|
|
23
|
+
* Pending patches are applied, because an agent looking at a project mid-edit
|
|
24
|
+
* should see what the Studio would show rather than the last published state.
|
|
25
|
+
*
|
|
26
|
+
* Deliberately not cached across calls. In fs mode a save recomputes the base
|
|
27
|
+
* sha within the same process, so a cached view would go stale silently — and
|
|
28
|
+
* the cost of being wrong here is an agent writing a patch against content that
|
|
29
|
+
* has already moved.
|
|
30
|
+
*
|
|
31
|
+
* Exported for `toolsFixture`, not for consumers: `tools/index.ts` re-exports by
|
|
32
|
+
* name, so this stays inside the package. A test that assembled its own state
|
|
33
|
+
* would be asserting against a view no real call ever sees.
|
|
34
|
+
*/
|
|
35
|
+
export declare function loadState(ops: ValOps): Promise<{
|
|
36
|
+
status: "ok";
|
|
37
|
+
state: ValToolState;
|
|
38
|
+
} | {
|
|
39
|
+
status: "error";
|
|
40
|
+
result: ValToolResult;
|
|
41
|
+
}>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Json, ModuleFilePath, PatchId, SerializedSchema } from "@valbuild/core";
|
|
2
|
+
import type { z } from "zod";
|
|
3
|
+
import type { ValOps, OrderedPatches, PatchAnalysis, Schemas, Sources } from "../ValOps.js";
|
|
4
|
+
import type { ValToolContext, ValToolDefinition, ValToolErrorCode, ValToolResult } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* How a tool is written, and what it is handed.
|
|
7
|
+
*
|
|
8
|
+
* Tools are defined with {@link defineTool} so that the handler's `args` are
|
|
9
|
+
* inferred from the tool's own `inputSchema`. Without that the array of tools
|
|
10
|
+
* would have to be typed at its widest and every handler would start by
|
|
11
|
+
* re-narrowing `unknown`, which is exactly where a tool and its schema drift
|
|
12
|
+
* apart unnoticed.
|
|
13
|
+
*/
|
|
14
|
+
/** Everything a tool is allowed to reach. Deliberately narrow. */
|
|
15
|
+
export type ValToolDeps = {
|
|
16
|
+
/**
|
|
17
|
+
* The data layer for *this call*. In proxy mode it is authenticated as the
|
|
18
|
+
* caller, so a tool must never reach for an ambient instance instead.
|
|
19
|
+
*/
|
|
20
|
+
ops: ValOps;
|
|
21
|
+
ctx: ValToolContext;
|
|
22
|
+
/**
|
|
23
|
+
* The content as the caller should see it: base sources with pending patches
|
|
24
|
+
* applied, plus the schemas and the patch analysis that produced them.
|
|
25
|
+
*
|
|
26
|
+
* Loaded once per call and shared, because a tool that re-derived it would
|
|
27
|
+
* both pay for it again and risk reading a different revision than the one it
|
|
28
|
+
* validated against.
|
|
29
|
+
*/
|
|
30
|
+
state: ValToolState;
|
|
31
|
+
};
|
|
32
|
+
export type ValToolState = {
|
|
33
|
+
schemas: Schemas;
|
|
34
|
+
serializedSchemas: Record<ModuleFilePath, SerializedSchema>;
|
|
35
|
+
sources: Sources;
|
|
36
|
+
patches: OrderedPatches;
|
|
37
|
+
analysis: PatchAnalysis;
|
|
38
|
+
/**
|
|
39
|
+
* Pending patches that would not apply, by the module they belong to.
|
|
40
|
+
*
|
|
41
|
+
* A module listed here has `sources` that silently lack those changes, so its
|
|
42
|
+
* content is not what publishing would produce. Reads still return it — it is
|
|
43
|
+
* what the Studio would show too, and a project has to stay diagnosable — but
|
|
44
|
+
* a write to such a module is refused, because it would be based on a state
|
|
45
|
+
* that does not exist.
|
|
46
|
+
*/
|
|
47
|
+
unappliedPatches: Record<ModuleFilePath, {
|
|
48
|
+
patchId: PatchId;
|
|
49
|
+
skipped: boolean;
|
|
50
|
+
error: {
|
|
51
|
+
message: string;
|
|
52
|
+
};
|
|
53
|
+
}[]>;
|
|
54
|
+
};
|
|
55
|
+
export type ValToolImpl = ValToolDefinition & {
|
|
56
|
+
handler: (args: unknown, deps: ValToolDeps) => Promise<ValToolResult>;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Declare a tool, binding its handler to its input schema.
|
|
60
|
+
*
|
|
61
|
+
* The handler receives already-parsed arguments: the registry validates against
|
|
62
|
+
* `inputSchema` before calling, so a handler never sees input its schema would
|
|
63
|
+
* have rejected.
|
|
64
|
+
*/
|
|
65
|
+
export declare function defineTool<S extends z.ZodType>(definition: Omit<ValToolDefinition, "inputSchema"> & {
|
|
66
|
+
inputSchema: S;
|
|
67
|
+
}, handler: (args: z.infer<S>, deps: ValToolDeps) => Promise<ValToolResult>): ValToolImpl;
|
|
68
|
+
export declare function ok(data: Json): ValToolResult;
|
|
69
|
+
export declare function err(code: ValToolErrorCode, message: string): ValToolResult;
|
|
@@ -11935,12 +11935,19 @@ function createOpsResolver(valModules, options) {
|
|
|
11935
11935
|
// credential should not silently get local filesystem access instead —
|
|
11936
11936
|
// and the difference matters, because fs mode writes straight to disk
|
|
11937
11937
|
// with no backend permission check at all.
|
|
11938
|
+
//
|
|
11939
|
+
// The two credentials get different messages because they arrive here
|
|
11940
|
+
// for different reasons. A PAT is something the caller chose to send. A
|
|
11941
|
+
// verified access token is not: it only exists because this app
|
|
11942
|
+
// advertised an authorization server, so the developer seeing this did
|
|
11943
|
+
// not do anything wrong — a config file did, and naming it is the
|
|
11944
|
+
// difference between a two-minute fix and an afternoon.
|
|
11938
11945
|
return {
|
|
11939
11946
|
status: "error",
|
|
11940
11947
|
result: {
|
|
11941
11948
|
status: "error",
|
|
11942
11949
|
code: "unsupported",
|
|
11943
|
-
message: "This Val project is running in local filesystem mode, where there is nothing to authenticate against. Do not send a credential."
|
|
11950
|
+
message: ctx.auth.type === "verified-profile" ? "This Val project is running in local filesystem mode, so there is nothing to authenticate against, but it is configured with an `oauth` issuer and is therefore asking clients for an access token it cannot use. Remove the `oauth` config (or `VAL_OAUTH_ISSUER` from your local `.env`) for local development." : "This Val project is running in local filesystem mode, where there is nothing to authenticate against. Do not send a credential."
|
|
11944
11951
|
}
|
|
11945
11952
|
};
|
|
11946
11953
|
}
|
|
@@ -12036,6 +12043,10 @@ function createOpsResolver(valModules, options) {
|
|
|
12036
12043
|
* sha within the same process, so a cached view would go stale silently — and
|
|
12037
12044
|
* the cost of being wrong here is an agent writing a patch against content that
|
|
12038
12045
|
* has already moved.
|
|
12046
|
+
*
|
|
12047
|
+
* Exported for `toolsFixture`, not for consumers: `tools/index.ts` re-exports by
|
|
12048
|
+
* name, so this stays inside the package. A test that assembled its own state
|
|
12049
|
+
* would be asserting against a view no real call ever sees.
|
|
12039
12050
|
*/
|
|
12040
12051
|
async function loadState(ops) {
|
|
12041
12052
|
const patches = await ops.fetchPatches({
|
|
@@ -11935,12 +11935,19 @@ function createOpsResolver(valModules, options) {
|
|
|
11935
11935
|
// credential should not silently get local filesystem access instead —
|
|
11936
11936
|
// and the difference matters, because fs mode writes straight to disk
|
|
11937
11937
|
// with no backend permission check at all.
|
|
11938
|
+
//
|
|
11939
|
+
// The two credentials get different messages because they arrive here
|
|
11940
|
+
// for different reasons. A PAT is something the caller chose to send. A
|
|
11941
|
+
// verified access token is not: it only exists because this app
|
|
11942
|
+
// advertised an authorization server, so the developer seeing this did
|
|
11943
|
+
// not do anything wrong — a config file did, and naming it is the
|
|
11944
|
+
// difference between a two-minute fix and an afternoon.
|
|
11938
11945
|
return {
|
|
11939
11946
|
status: "error",
|
|
11940
11947
|
result: {
|
|
11941
11948
|
status: "error",
|
|
11942
11949
|
code: "unsupported",
|
|
11943
|
-
message: "This Val project is running in local filesystem mode, where there is nothing to authenticate against. Do not send a credential."
|
|
11950
|
+
message: ctx.auth.type === "verified-profile" ? "This Val project is running in local filesystem mode, so there is nothing to authenticate against, but it is configured with an `oauth` issuer and is therefore asking clients for an access token it cannot use. Remove the `oauth` config (or `VAL_OAUTH_ISSUER` from your local `.env`) for local development." : "This Val project is running in local filesystem mode, where there is nothing to authenticate against. Do not send a credential."
|
|
11944
11951
|
}
|
|
11945
11952
|
};
|
|
11946
11953
|
}
|
|
@@ -12036,6 +12043,10 @@ function createOpsResolver(valModules, options) {
|
|
|
12036
12043
|
* sha within the same process, so a cached view would go stale silently — and
|
|
12037
12044
|
* the cost of being wrong here is an agent writing a patch against content that
|
|
12038
12045
|
* has already moved.
|
|
12046
|
+
*
|
|
12047
|
+
* Exported for `toolsFixture`, not for consumers: `tools/index.ts` re-exports by
|
|
12048
|
+
* name, so this stays inside the package. A test that assembled its own state
|
|
12049
|
+
* would be asserting against a view no real call ever sees.
|
|
12039
12050
|
*/
|
|
12040
12051
|
async function loadState(ops) {
|
|
12041
12052
|
const patches = await ops.fetchPatches({
|
|
@@ -11901,12 +11901,19 @@ function createOpsResolver(valModules, options) {
|
|
|
11901
11901
|
// credential should not silently get local filesystem access instead —
|
|
11902
11902
|
// and the difference matters, because fs mode writes straight to disk
|
|
11903
11903
|
// with no backend permission check at all.
|
|
11904
|
+
//
|
|
11905
|
+
// The two credentials get different messages because they arrive here
|
|
11906
|
+
// for different reasons. A PAT is something the caller chose to send. A
|
|
11907
|
+
// verified access token is not: it only exists because this app
|
|
11908
|
+
// advertised an authorization server, so the developer seeing this did
|
|
11909
|
+
// not do anything wrong — a config file did, and naming it is the
|
|
11910
|
+
// difference between a two-minute fix and an afternoon.
|
|
11904
11911
|
return {
|
|
11905
11912
|
status: "error",
|
|
11906
11913
|
result: {
|
|
11907
11914
|
status: "error",
|
|
11908
11915
|
code: "unsupported",
|
|
11909
|
-
message: "This Val project is running in local filesystem mode, where there is nothing to authenticate against. Do not send a credential."
|
|
11916
|
+
message: ctx.auth.type === "verified-profile" ? "This Val project is running in local filesystem mode, so there is nothing to authenticate against, but it is configured with an `oauth` issuer and is therefore asking clients for an access token it cannot use. Remove the `oauth` config (or `VAL_OAUTH_ISSUER` from your local `.env`) for local development." : "This Val project is running in local filesystem mode, where there is nothing to authenticate against. Do not send a credential."
|
|
11910
11917
|
}
|
|
11911
11918
|
};
|
|
11912
11919
|
}
|
|
@@ -12002,6 +12009,10 @@ function createOpsResolver(valModules, options) {
|
|
|
12002
12009
|
* sha within the same process, so a cached view would go stale silently — and
|
|
12003
12010
|
* the cost of being wrong here is an agent writing a patch against content that
|
|
12004
12011
|
* has already moved.
|
|
12012
|
+
*
|
|
12013
|
+
* Exported for `toolsFixture`, not for consumers: `tools/index.ts` re-exports by
|
|
12014
|
+
* name, so this stays inside the package. A test that assembled its own state
|
|
12015
|
+
* would be asserting against a view no real call ever sees.
|
|
12005
12016
|
*/
|
|
12006
12017
|
async function loadState(ops) {
|
|
12007
12018
|
const patches = await ops.fetchPatches({
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"./package.json": "./package.json"
|
|
17
17
|
},
|
|
18
18
|
"types": "dist/valbuild-server.cjs.d.ts",
|
|
19
|
-
"version": "0.
|
|
19
|
+
"version": "0.120.1",
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@prettier/sync": "^0.6.1",
|
|
22
22
|
"@types/jest": "^30.0.0"
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
"typescript": "^6.0.3",
|
|
30
30
|
"zod": "^4.4.3",
|
|
31
31
|
"zod-validation-error": "^5.0.0",
|
|
32
|
-
"@valbuild/
|
|
33
|
-
"@valbuild/ui": "0.
|
|
34
|
-
"@valbuild/
|
|
32
|
+
"@valbuild/core": "0.120.0",
|
|
33
|
+
"@valbuild/ui": "0.120.0",
|
|
34
|
+
"@valbuild/shared": "0.120.0"
|
|
35
35
|
},
|
|
36
36
|
"engines": {
|
|
37
37
|
"node": "^20.19.0 || >=22"
|