@valbuild/server 0.115.0 → 0.117.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 +68 -0
- package/dist/declarations/src/ValRouter.d.ts +0 -26
- package/dist/declarations/src/getValidationErrorFileRef.d.ts +1 -1
- package/dist/declarations/src/index.d.ts +4 -0
- package/dist/declarations/src/patchStore.d.ts +6 -6
- package/dist/declarations/src/tools/createValTools.d.ts +17 -0
- package/dist/declarations/src/tools/index.d.ts +3 -0
- package/dist/declarations/src/tools/types.d.ts +172 -0
- package/dist/declarations/src/valServerConfig.d.ts +81 -0
- package/dist/valbuild-server.cjs.dev.js +1556 -210
- package/dist/valbuild-server.cjs.prod.js +1556 -210
- package/dist/valbuild-server.esm.js +1553 -213
- package/package.json +5 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# @valbuild/server
|
|
2
|
+
|
|
3
|
+
## 0.117.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#582](https://github.com/valbuild/val/pull/582) [`fca3efa`](https://github.com/valbuild/val/commit/fca3efa389e2817401f55ea3dd184af7c611b807) Thanks [@freekh](https://github.com/freekh)! - Accept OAuth access tokens on the MCP endpoint, so editors can authorize as themselves
|
|
8
|
+
|
|
9
|
+
`initValMcp` takes an optional `oauth` config. Give it the authorization server's
|
|
10
|
+
URL and this endpoint's own URL, and every MCP call must then present an access
|
|
11
|
+
token that Val's authorization server issued:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const { valMcpAuthorize, valMcpTools, valMcpMetadata } = initValMcp(
|
|
15
|
+
valModules,
|
|
16
|
+
config,
|
|
17
|
+
{
|
|
18
|
+
oauth: {
|
|
19
|
+
issuer: "https://admin.val.build",
|
|
20
|
+
resource: "https://your-app.com/api/mcp",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The token is verified in your app — signature against the issuer's published
|
|
27
|
+
keys, plus issuer, audience and expiry — so the caller's identity is checked
|
|
28
|
+
rather than claimed. **Patches created over MCP now carry that profile as their
|
|
29
|
+
author**, which is what makes an edit made from a phone show up in the review
|
|
30
|
+
screen as somebody's rather than nobody's. Scopes are enforced too: a token
|
|
31
|
+
without `val:write` cannot reach a tool that writes.
|
|
32
|
+
|
|
33
|
+
Mount the discovery document so clients can find where to authorize:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
// app/.well-known/oauth-protected-resource/route.ts
|
|
37
|
+
import { valMcpMetadata } from "../../../val/mcp";
|
|
38
|
+
export const { GET, OPTIONS } = valMcpMetadata!;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`valMcpMetadata` is `null` when no `oauth` config is given.
|
|
42
|
+
|
|
43
|
+
**Nothing changes if you leave `oauth` out.** Local development still works with
|
|
44
|
+
no authorization server, and an app already using a personal access token keeps
|
|
45
|
+
working as before.
|
|
46
|
+
|
|
47
|
+
One breaking change if you built your own host on `createValTools`:
|
|
48
|
+
`ValToolContext.auth` is now a tagged union, so `{ pat }` becomes
|
|
49
|
+
`{ type: "pat", pat }`. The new variant is
|
|
50
|
+
`{ type: "verified-profile", profileId, scopes }`, for a host that verified a
|
|
51
|
+
token itself.
|
|
52
|
+
|
|
53
|
+
### Patch Changes
|
|
54
|
+
|
|
55
|
+
- [#579](https://github.com/valbuild/val/pull/579) [`b2812ae`](https://github.com/valbuild/val/commit/b2812ae4ee03e005ecead3365f49c625e536f94d) Thanks [@freekh](https://github.com/freekh)! - Every release now ships a changelog. Each package's `CHANGELOG.md` records what
|
|
56
|
+
changed under the version that shipped it — with a link to the pull request, the
|
|
57
|
+
commit and the author — and the same entry becomes the body of the GitHub
|
|
58
|
+
Release for the tag. The file is included in the npm tarball, so it is also
|
|
59
|
+
readable from an installed copy.
|
|
60
|
+
|
|
61
|
+
Up to now those changelogs were generated empty, and the GitHub Releases with
|
|
62
|
+
them, so there was no record of what any given version contained. Releases from
|
|
63
|
+
this one on have one; earlier versions stay blank.
|
|
64
|
+
|
|
65
|
+
- Updated dependencies [[`d94a40f`](https://github.com/valbuild/val/commit/d94a40f8bd11027636d183e293aced820b6f341f), [`b2812ae`](https://github.com/valbuild/val/commit/b2812ae4ee03e005ecead3365f49c625e536f94d)]:
|
|
66
|
+
- @valbuild/core@0.117.0
|
|
67
|
+
- @valbuild/shared@0.117.0
|
|
68
|
+
- @valbuild/ui@0.117.0
|
|
@@ -114,32 +114,6 @@ type ValServerOverrides = Partial<{
|
|
|
114
114
|
disableCache?: boolean;
|
|
115
115
|
}>;
|
|
116
116
|
export declare function createValServer(valModules: ValModules, route: string, opts: ValApiOptions, config: ValConfig, callbacks: ValServerCallbacks, formatter?: (code: string, filePath: string) => string | Promise<string>): Promise<ValServer>;
|
|
117
|
-
/**
|
|
118
|
-
* Hosts we send credentials to, and what each one puts at risk. They differ:
|
|
119
|
-
* only `valBuildUrl` hands back the app token that becomes the session cookie,
|
|
120
|
-
* so a single shared sentence would overstate one and understate the other.
|
|
121
|
-
*/
|
|
122
|
-
type CredentialBearingUrl = "valBuildUrl" | "valContentUrl";
|
|
123
|
-
/**
|
|
124
|
-
* Returns a warning if `url` would send credentials somewhere they can be read
|
|
125
|
-
* off the wire, or null if it is fine.
|
|
126
|
-
*
|
|
127
|
-
* Both URLs default to https, but each is overridable - `opts.valBuildUrl` /
|
|
128
|
-
* `VAL_BUILD_URL`, `opts.valContentUrl` / `VAL_CONTENT_URL` - and neither
|
|
129
|
-
* override has ever been scheme-checked. Point one at a plain http host and the
|
|
130
|
-
* api key goes out in clear text, and whatever comes back is whatever the
|
|
131
|
-
* network says: for `valBuildUrl` that includes the app token this server
|
|
132
|
-
* re-signs into the session cookie.
|
|
133
|
-
*
|
|
134
|
-
* Loopback over http is exempt: that is a val.build running on the developer's
|
|
135
|
-
* own machine, and there is no network to be on the wrong side of.
|
|
136
|
-
*
|
|
137
|
-
* This warns rather than throws. Both overrides are set by the operator, not by
|
|
138
|
-
* an attacker, so this is a misconfiguration to surface - not untrusted input to
|
|
139
|
-
* reject - and refusing to boot would break anyone deliberately pointing at an
|
|
140
|
-
* internal http host today.
|
|
141
|
-
*/
|
|
142
|
-
export declare function insecureUrlWarning(name: CredentialBearingUrl, url: string): string | null;
|
|
143
117
|
export declare function safeReadGit(cwd: string): Promise<{
|
|
144
118
|
commit?: string;
|
|
145
119
|
branch?: string;
|
|
@@ -5,4 +5,4 @@ import { ValidationError } from "@valbuild/core";
|
|
|
5
5
|
* There is no schema in hand here — a `ValidationError` carries only the value
|
|
6
6
|
* it flagged — so the shape is all there is to go on.
|
|
7
7
|
*/
|
|
8
|
-
export declare function getValidationErrorFileRef(validationError: ValidationError):
|
|
8
|
+
export declare function getValidationErrorFileRef(validationError: ValidationError): string | null;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export { createService, Service } from "./Service.js";
|
|
2
2
|
export { createValApiRouter, createValServer, safeReadGit } from "./ValRouter.js";
|
|
3
|
+
export { createValTools } from "./tools/index.js";
|
|
4
|
+
export { VAL_SCOPE_READ, VAL_SCOPE_WRITE, authorIdFromVerifiedSubject, } from "./tools/index.js";
|
|
5
|
+
export type { ValScope, ValToolAuth, ValToolContext, ValToolDefinition, ValToolDefinitionJson, ValToolErrorCode, ValToolResult, ValTools, ValToolsOptions, } from "./tools/index.js";
|
|
6
|
+
export { initHandlerOptions, createValOps } from "./valServerConfig.js";
|
|
3
7
|
export { ValModuleLoader } from "./ValModuleLoader.js";
|
|
4
8
|
export { getCompilerOptions } from "./getCompilerOptions.js";
|
|
5
9
|
export { ValSourceFileHandler } from "./ValSourceFileHandler.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PatchId } from "@valbuild/core";
|
|
1
|
+
import { ModuleFilePath, PatchId } from "@valbuild/core";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import type { AuthorId, BaseSha } from "./ValOps.js";
|
|
4
4
|
import { PatchLogEntry, PatchLogProblem } from "./patchLog.js";
|
|
@@ -32,14 +32,14 @@ export declare const FSPatch: z.ZodObject<{
|
|
|
32
32
|
patch: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
33
33
|
op: z.ZodLiteral<"add">;
|
|
34
34
|
path: z.ZodArray<z.ZodString>;
|
|
35
|
-
value: z.ZodType<
|
|
35
|
+
value: z.ZodType<import("@valbuild/core/patch").JSONValue, unknown, z.core.$ZodTypeInternals<import("@valbuild/core/patch").JSONValue, unknown>>;
|
|
36
36
|
}, z.core.$strict>, z.ZodObject<{
|
|
37
37
|
op: z.ZodLiteral<"remove">;
|
|
38
38
|
path: z.ZodTuple<[z.ZodString], z.ZodString>;
|
|
39
39
|
}, z.core.$strict>, z.ZodObject<{
|
|
40
40
|
op: z.ZodLiteral<"replace">;
|
|
41
41
|
path: z.ZodArray<z.ZodString>;
|
|
42
|
-
value: z.ZodType<
|
|
42
|
+
value: z.ZodType<import("@valbuild/core/patch").JSONValue, unknown, z.core.$ZodTypeInternals<import("@valbuild/core/patch").JSONValue, unknown>>;
|
|
43
43
|
}, z.core.$strict>, z.ZodObject<{
|
|
44
44
|
op: z.ZodLiteral<"move">;
|
|
45
45
|
from: z.ZodTuple<[z.ZodString], z.ZodString>;
|
|
@@ -51,15 +51,15 @@ export declare const FSPatch: z.ZodObject<{
|
|
|
51
51
|
}, z.core.$strict>, z.ZodObject<{
|
|
52
52
|
op: z.ZodLiteral<"test">;
|
|
53
53
|
path: z.ZodArray<z.ZodString>;
|
|
54
|
-
value: z.ZodType<
|
|
54
|
+
value: z.ZodType<import("@valbuild/core/patch").JSONValue, unknown, z.core.$ZodTypeInternals<import("@valbuild/core/patch").JSONValue, unknown>>;
|
|
55
55
|
}, z.core.$strict>, z.ZodObject<{
|
|
56
56
|
op: z.ZodLiteral<"file">;
|
|
57
57
|
path: z.ZodArray<z.ZodString>;
|
|
58
58
|
filePath: z.ZodString;
|
|
59
|
-
value: z.ZodType<
|
|
59
|
+
value: z.ZodType<import("@valbuild/core/patch").JSONValue, unknown, z.core.$ZodTypeInternals<import("@valbuild/core/patch").JSONValue, unknown>>;
|
|
60
60
|
remote: z.ZodBoolean;
|
|
61
61
|
nestedFilePath: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
62
|
-
metadata: z.ZodOptional<z.ZodType<
|
|
62
|
+
metadata: z.ZodOptional<z.ZodType<import("@valbuild/core/patch").JSONValue, unknown, z.core.$ZodTypeInternals<import("@valbuild/core/patch").JSONValue, unknown>>>;
|
|
63
63
|
}, z.core.$strict>], "op">>;
|
|
64
64
|
patchId: z.ZodString;
|
|
65
65
|
baseSha: z.ZodString & z.ZodType<BaseSha, string, z.core.$ZodTypeInternals<BaseSha, string>>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ValModules } from "@valbuild/core";
|
|
2
|
+
import type { ValServerConfig } from "../ValServer.js";
|
|
3
|
+
import { type ValTools } from "./types.js";
|
|
4
|
+
export type ValToolsOptions = ValServerConfig;
|
|
5
|
+
/**
|
|
6
|
+
* Val's server-side tool registry.
|
|
7
|
+
*
|
|
8
|
+
* This is the piece Val did not have: the Studio's chat tools are defined *and
|
|
9
|
+
* executed in the browser*, against its client stores, so nothing here could be
|
|
10
|
+
* re-exposed. These tools run against {@link ValOps} instead, which is what lets
|
|
11
|
+
* an MCP server — or a stdio transport, or anything else — drive Val content
|
|
12
|
+
* without a browser.
|
|
13
|
+
*
|
|
14
|
+
* Transport-agnostic on purpose: nothing under `tools/` imports an MCP SDK. A
|
|
15
|
+
* host adapts {@link ValToolResult} at its own edge.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createValTools(valModules: ValModules, options: ValToolsOptions): ValTools;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { createValTools, type ValToolsOptions } from "./createValTools.js";
|
|
2
|
+
export { VAL_SCOPE_READ, VAL_SCOPE_WRITE, authorIdFromVerifiedSubject, } from "./types.js";
|
|
3
|
+
export type { ValScope, ValToolAuth, ValToolContext, ValToolDefinition, ValToolDefinitionJson, ValToolErrorCode, ValToolResult, ValTools, } from "./types.js";
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { AuthorId } from "../ValOps.js";
|
|
2
|
+
import type { Json } from "@valbuild/core";
|
|
3
|
+
import type { z } from "zod";
|
|
4
|
+
/**
|
|
5
|
+
* The public surface of Val's server-side tool registry.
|
|
6
|
+
*
|
|
7
|
+
* Types only, deliberately: this file is the contract that the MCP hosts, the
|
|
8
|
+
* CLI's stdio transport and the tools themselves are all written against, and
|
|
9
|
+
* keeping it free of implementation means those can be built in any order
|
|
10
|
+
* without one of them owning the shape.
|
|
11
|
+
*
|
|
12
|
+
* The design this implements is `docs/plans/mcp.md`, Part A. Two constraints
|
|
13
|
+
* from it are load-bearing and easy to break by accident:
|
|
14
|
+
*
|
|
15
|
+
* 1. **Nothing here may import an MCP SDK.** That is what lets hosts other
|
|
16
|
+
* than the template consume these tools, and it is not hypothetical
|
|
17
|
+
* hygiene — the TypeScript SDK reorganised itself at v2.0.0, and a registry
|
|
18
|
+
* coupled to it would have moved with it.
|
|
19
|
+
* 2. **The result type is deliberately not MCP's `CallToolResult`.** Each host
|
|
20
|
+
* adapts {@link ValToolResult} at its own edge, which is also where an
|
|
21
|
+
* error becomes an in-band `isError` result the model can recover from
|
|
22
|
+
* rather than a transport failure.
|
|
23
|
+
*/
|
|
24
|
+
/** Why a tool call failed, in a form a host can map onto its own errors. */
|
|
25
|
+
export type ValToolErrorCode =
|
|
26
|
+
/** No tool of that name is registered. */
|
|
27
|
+
"unknown-tool"
|
|
28
|
+
/** The arguments did not satisfy the tool's `inputSchema`. */
|
|
29
|
+
| "invalid-args"
|
|
30
|
+
/** The path, module or key the arguments name does not exist. */
|
|
31
|
+
| "not-found"
|
|
32
|
+
/** The caller is not allowed to do this — wrong credential, or none. */
|
|
33
|
+
| "forbidden"
|
|
34
|
+
/**
|
|
35
|
+
* The write was rejected because applying it would leave content invalid.
|
|
36
|
+
* Nothing was stored. See `docs/plans/mcp.md` Part C on speculative
|
|
37
|
+
* validation.
|
|
38
|
+
*/
|
|
39
|
+
| "validation-failed"
|
|
40
|
+
/**
|
|
41
|
+
* Another writer moved the patch chain first. Callers may re-derive the
|
|
42
|
+
* parent ref and retry once; the registry does that itself before
|
|
43
|
+
* surfacing this.
|
|
44
|
+
*/
|
|
45
|
+
| "conflict"
|
|
46
|
+
/** The tool exists but is not available in this mode (e.g. fs vs http). */
|
|
47
|
+
| "unsupported"
|
|
48
|
+
/** Anything else, including an upstream failure. */
|
|
49
|
+
| "internal";
|
|
50
|
+
export type ValToolDefinition = {
|
|
51
|
+
name: string;
|
|
52
|
+
title?: string;
|
|
53
|
+
description: string;
|
|
54
|
+
/** zod v4 — a Standard Schema, which is what the MCP SDKs consume. */
|
|
55
|
+
inputSchema: z.ZodType;
|
|
56
|
+
/**
|
|
57
|
+
* MCP tool annotations. Hints, not enforcement: a host may show them to a
|
|
58
|
+
* user or use them to decide what to auto-approve, so they have to be
|
|
59
|
+
* honest about what the tool does.
|
|
60
|
+
*/
|
|
61
|
+
annotations?: {
|
|
62
|
+
readOnlyHint?: boolean;
|
|
63
|
+
destructiveHint?: boolean;
|
|
64
|
+
idempotentHint?: boolean;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* The same definition with `inputSchema` as JSON Schema, for hosts that want the
|
|
69
|
+
* wire shape rather than a Standard Schema.
|
|
70
|
+
*
|
|
71
|
+
* Typed as whatever zod's own converter produces, so deriving it needs no cast
|
|
72
|
+
* and no second hand-written description of the same input.
|
|
73
|
+
*/
|
|
74
|
+
export type ValToolDefinitionJson = Omit<ValToolDefinition, "inputSchema"> & {
|
|
75
|
+
inputSchema: ReturnType<typeof z.toJSONSchema<z.ZodType>>;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* How the caller was established, and it is a union because there are two
|
|
79
|
+
* genuinely different answers — with different consequences downstream.
|
|
80
|
+
*
|
|
81
|
+
* The distinction that matters is **who checked**. A PAT is forwarded to the
|
|
82
|
+
* backend unchecked, because the app cannot resolve one; an access token is
|
|
83
|
+
* verified by the app itself, against a public key it does not hold and
|
|
84
|
+
* therefore cannot forge. The first is a credential being relayed. The second
|
|
85
|
+
* is a signature that has already been checked.
|
|
86
|
+
*/
|
|
87
|
+
export type ValToolAuth = {
|
|
88
|
+
type: "pat";
|
|
89
|
+
/**
|
|
90
|
+
* The caller's PAT. Never log this, never put it in a URL, and never let
|
|
91
|
+
* it reach a tool result.
|
|
92
|
+
*
|
|
93
|
+
* Relayed to the backend as-is: this app is not the authority on what the
|
|
94
|
+
* token may do, and the backend that is decides. Nothing is derived from
|
|
95
|
+
* it here — see `docs/plans/mcp.md` D.2.
|
|
96
|
+
*/
|
|
97
|
+
pat: string;
|
|
98
|
+
} | {
|
|
99
|
+
type: "verified-profile";
|
|
100
|
+
/**
|
|
101
|
+
* The profile the host **verified** — the `sub` of an access token whose
|
|
102
|
+
* signature, issuer, audience and expiry were all checked against the
|
|
103
|
+
* authorization server's published key.
|
|
104
|
+
*
|
|
105
|
+
* This field is the reason this type became a union, and an earlier
|
|
106
|
+
* version of this file argued no identity field should exist at all. That
|
|
107
|
+
* argument was about a specific case and stated too broadly: an id the
|
|
108
|
+
* host *asserts* on the strength of a credential it cannot check is an
|
|
109
|
+
* unverified claim dressed as a checked one, and that is still refused —
|
|
110
|
+
* it is why the `pat` variant carries no profile. An id the host
|
|
111
|
+
* *verified* cryptographically is a different thing, and it is the same
|
|
112
|
+
* standing the Studio has when it re-signs a session it established
|
|
113
|
+
* itself.
|
|
114
|
+
*/
|
|
115
|
+
profileId: AuthorId;
|
|
116
|
+
/**
|
|
117
|
+
* The token's granted scopes, as the authorization server issued them.
|
|
118
|
+
*
|
|
119
|
+
* Enforced here as well as by the backend, deliberately. Two checks on
|
|
120
|
+
* one grant is not redundancy for its own sake: this one can refuse a
|
|
121
|
+
* write before it is attempted, so a token that may only read never
|
|
122
|
+
* reaches the code that builds a patch.
|
|
123
|
+
*/
|
|
124
|
+
scopes: string[];
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Who is calling, established once per request by the host.
|
|
128
|
+
*
|
|
129
|
+
* `null` means local fs mode, where there is no credential to hold and patches
|
|
130
|
+
* are written with no author, exactly as the Studio does locally (D.1). In
|
|
131
|
+
* proxy mode `null` is refused rather than falling back to the app's own API
|
|
132
|
+
* key: that key can do more than any single user, and quietly substituting it
|
|
133
|
+
* would turn a missing credential into full access.
|
|
134
|
+
*/
|
|
135
|
+
export type ValToolContext = {
|
|
136
|
+
auth: ValToolAuth | null;
|
|
137
|
+
/** Groups a run of related edits, when the host has such a notion. */
|
|
138
|
+
sessionId: string | null;
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Brand a verified subject as an {@link AuthorId}.
|
|
142
|
+
*
|
|
143
|
+
* `AuthorId` is a branded string so that an id cannot be conjured from any
|
|
144
|
+
* string that happens to be lying around — which is exactly the mistake this
|
|
145
|
+
* type is guarding against. That makes one assertion unavoidable at the boundary
|
|
146
|
+
* where a real id enters the system, so it lives here, once, with a name that
|
|
147
|
+
* says what makes it legitimate: the caller has *verified* this subject, not
|
|
148
|
+
* received it.
|
|
149
|
+
*
|
|
150
|
+
* Do not reach for this to satisfy a type. If you are holding a string you did
|
|
151
|
+
* not verify, the honest value is `null`.
|
|
152
|
+
*/
|
|
153
|
+
export declare function authorIdFromVerifiedSubject(subject: string): AuthorId;
|
|
154
|
+
/** Read access. Every call needs it, the writes included. */
|
|
155
|
+
export declare const VAL_SCOPE_READ = "val:read";
|
|
156
|
+
/** Write access. Needed *in addition* by any tool not marked `readOnlyHint`. */
|
|
157
|
+
export declare const VAL_SCOPE_WRITE = "val:write";
|
|
158
|
+
export type ValScope = typeof VAL_SCOPE_READ | typeof VAL_SCOPE_WRITE;
|
|
159
|
+
export type ValToolResult = {
|
|
160
|
+
status: "ok";
|
|
161
|
+
data: Json;
|
|
162
|
+
} | {
|
|
163
|
+
status: "error";
|
|
164
|
+
code: ValToolErrorCode;
|
|
165
|
+
message: string;
|
|
166
|
+
};
|
|
167
|
+
export type ValTools = {
|
|
168
|
+
list(): ValToolDefinition[];
|
|
169
|
+
listJsonSchema(): ValToolDefinitionJson[];
|
|
170
|
+
call(name: string, args: unknown, ctx: ValToolContext): Promise<ValToolResult>;
|
|
171
|
+
dispose(): Promise<void>;
|
|
172
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { ValConfig, ValModules } from "@valbuild/core";
|
|
2
|
+
import type { ValServerConfig } from "./ValServer.js";
|
|
3
|
+
import type { ValApiOptions } from "./ValRouter.js";
|
|
4
|
+
import { ValOpsFS } from "./ValOpsFS.js";
|
|
5
|
+
import { ValOpsHttp } from "./ValOpsHttp.js";
|
|
6
|
+
/**
|
|
7
|
+
* Resolving how Val is configured, and building the data layer from it.
|
|
8
|
+
*
|
|
9
|
+
* Both live here rather than inside `createValApiRouter` because the MCP tool
|
|
10
|
+
* registry needs exactly the same answers: which mode we are in, which
|
|
11
|
+
* credential to use, and which `ValOps` implementation that implies. Two copies
|
|
12
|
+
* of this would drift, and the failure would be quiet — a registry that decides
|
|
13
|
+
* it is in fs mode while the Studio decides it is in proxy mode reads different
|
|
14
|
+
* content from the same project.
|
|
15
|
+
*
|
|
16
|
+
* The type-only import from `./ValRouter` is deliberate: it keeps `ValApiOptions`
|
|
17
|
+
* where its documentation lives without creating a runtime cycle.
|
|
18
|
+
*
|
|
19
|
+
* The credential-bearing URL check at the bottom of this file lives here for the
|
|
20
|
+
* same reason: it is a check on the URLs `initHandlerOptions` resolves, and its
|
|
21
|
+
* only caller is that function. Leaving it in `./ValRouter` would have meant
|
|
22
|
+
* importing it back from there, which is the runtime cycle the paragraph above
|
|
23
|
+
* exists to avoid.
|
|
24
|
+
*/
|
|
25
|
+
export declare const DEFAULT_VAL_BUILD_URL = "https://admin.val.build";
|
|
26
|
+
/**
|
|
27
|
+
* Resolve options plus environment into a concrete {@link ValServerConfig}.
|
|
28
|
+
*
|
|
29
|
+
* Moved verbatim out of `createValApiRouter`; the precedence rules are load
|
|
30
|
+
* bearing, so this is the one place they are written down. Note that "proxy"
|
|
31
|
+
* mode is inferred when `VAL_API_KEY` or `VAL_SECRET` is present and no mode was
|
|
32
|
+
* given, which is why a project can be pushed into proxy mode by setting an env
|
|
33
|
+
* var alone.
|
|
34
|
+
*/
|
|
35
|
+
export declare function initHandlerOptions(route: string, opts: ValApiOptions, config: ValConfig): Promise<ValServerConfig>;
|
|
36
|
+
/**
|
|
37
|
+
* Build the data layer a {@link ValServerConfig} calls for.
|
|
38
|
+
*
|
|
39
|
+
* `auth` decides *whose* credential the http backend sees. Left out, it is the
|
|
40
|
+
* app's own API key — which is what the Studio wants, because there the app has
|
|
41
|
+
* already verified a session cookie and is acting on the user's behalf under its
|
|
42
|
+
* own authority.
|
|
43
|
+
*
|
|
44
|
+
* A caller acting for a user it has *not* authenticated itself must pass that
|
|
45
|
+
* user's personal access token instead, so the backend is the one that decides
|
|
46
|
+
* what the caller may do. That is the whole of `docs/plans/mcp.md` D.2: the app
|
|
47
|
+
* stops being an authority and goes back to being a pipe. Passing the app's API
|
|
48
|
+
* key on such a request is the D.6 confused deputy, and it is worth being blunt
|
|
49
|
+
* about why it is tempting — it works, and it works for every project the key
|
|
50
|
+
* can reach, including the ones the caller cannot.
|
|
51
|
+
*/
|
|
52
|
+
export declare function createValOps(valModules: ValModules, options: ValServerConfig, auth?: {
|
|
53
|
+
pat: string;
|
|
54
|
+
}): ValOpsFS | ValOpsHttp;
|
|
55
|
+
/**
|
|
56
|
+
* Hosts we send credentials to, and what each one puts at risk. They differ:
|
|
57
|
+
* only `valBuildUrl` hands back the app token that becomes the session cookie,
|
|
58
|
+
* so a single shared sentence would overstate one and understate the other.
|
|
59
|
+
*/
|
|
60
|
+
type CredentialBearingUrl = "valBuildUrl" | "valContentUrl";
|
|
61
|
+
/**
|
|
62
|
+
* Returns a warning if `url` would send credentials somewhere they can be read
|
|
63
|
+
* off the wire, or null if it is fine.
|
|
64
|
+
*
|
|
65
|
+
* Both URLs default to https, but each is overridable - `opts.valBuildUrl` /
|
|
66
|
+
* `VAL_BUILD_URL`, `opts.valContentUrl` / `VAL_CONTENT_URL` - and neither
|
|
67
|
+
* override has ever been scheme-checked. Point one at a plain http host and the
|
|
68
|
+
* api key goes out in clear text, and whatever comes back is whatever the
|
|
69
|
+
* network says: for `valBuildUrl` that includes the app token this server
|
|
70
|
+
* re-signs into the session cookie.
|
|
71
|
+
*
|
|
72
|
+
* Loopback over http is exempt: that is a val.build running on the developer's
|
|
73
|
+
* own machine, and there is no network to be on the wrong side of.
|
|
74
|
+
*
|
|
75
|
+
* This warns rather than throws. Both overrides are set by the operator, not by
|
|
76
|
+
* an attacker, so this is a misconfiguration to surface - not untrusted input to
|
|
77
|
+
* reject - and refusing to boot would break anyone deliberately pointing at an
|
|
78
|
+
* internal http host today.
|
|
79
|
+
*/
|
|
80
|
+
export declare function insecureUrlWarning(name: CredentialBearingUrl, url: string): string | null;
|
|
81
|
+
export {};
|