@valbuild/mcp 0.123.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.
Files changed (31) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/README.md +79 -0
  3. package/dist/asyncToGenerator-500f022f.esm.js +137 -0
  4. package/dist/asyncToGenerator-8e5c36c8.cjs.prod.js +140 -0
  5. package/dist/asyncToGenerator-c3823d62.cjs.dev.js +140 -0
  6. package/dist/declarations/src/images/imageTools.d.ts +14 -0
  7. package/dist/declarations/src/images/index.d.ts +2 -0
  8. package/dist/declarations/src/images/remoteUploadTarget.d.ts +70 -0
  9. package/dist/declarations/src/images/types.d.ts +60 -0
  10. package/dist/declarations/src/index.d.ts +24 -0
  11. package/dist/declarations/src/initValMcp.d.ts +95 -0
  12. package/dist/declarations/src/sharp/index.d.ts +46 -0
  13. package/dist/declarations/src/tools/createValTools.d.ts +47 -0
  14. package/dist/declarations/src/tools/defineTool.d.ts +79 -0
  15. package/dist/declarations/src/tools/index.d.ts +7 -0
  16. package/dist/declarations/src/tools/types.d.ts +172 -0
  17. package/dist/declarations/src/tools/writePath.d.ts +160 -0
  18. package/dist/declarations/src/valAccessToken.d.ts +93 -0
  19. package/dist/declarations/src/valMcpMetadata.d.ts +47 -0
  20. package/dist/valbuild-mcp.cjs.d.ts +2 -0
  21. package/dist/valbuild-mcp.cjs.dev.js +4399 -0
  22. package/dist/valbuild-mcp.cjs.js +7 -0
  23. package/dist/valbuild-mcp.cjs.prod.js +4399 -0
  24. package/dist/valbuild-mcp.esm.js +4381 -0
  25. package/package.json +66 -0
  26. package/sharp/dist/valbuild-mcp-sharp.cjs.d.ts +2 -0
  27. package/sharp/dist/valbuild-mcp-sharp.cjs.dev.js +157 -0
  28. package/sharp/dist/valbuild-mcp-sharp.cjs.js +7 -0
  29. package/sharp/dist/valbuild-mcp-sharp.cjs.prod.js +157 -0
  30. package/sharp/dist/valbuild-mcp-sharp.esm.js +153 -0
  31. package/sharp/package.json +4 -0
@@ -0,0 +1,70 @@
1
+ import { type SerializedFileSchema, type SerializedImageSchema } from "@valbuild/core";
2
+ import { type ValServerConfig } from "@valbuild/server";
3
+ import { type ValToolError } from "../tools/index.js";
4
+ /**
5
+ * Where a remote image goes, and what its ref has to say.
6
+ *
7
+ * Only one thing here needs the network: a project's `publicProjectId` and its
8
+ * list of buckets, which `getSettings` answers. Everything else is arithmetic
9
+ * over bytes the caller already handed us.
10
+ *
11
+ * And **no credential comes from the MCP caller**, in either mode. The rule is
12
+ * `resolveRemoteFileAuth` in `@valbuild/server`, shared with the Studio's own
13
+ * api routes: the app's api key where there is one, and otherwise — local
14
+ * development — the developer's own `val login` token off disk. That is the
15
+ * same precondition `val validate --fix` has, and the same one the Studio has
16
+ * when it uploads a remote image from a laptop.
17
+ *
18
+ * What this does NOT do is upload anything to the content host. A remote
19
+ * image's bytes go into the patch store like any other pending file, and the
20
+ * push to `remote.val.build` happens at publish, from
21
+ * `ValOpsFS.saveOrUploadFiles(mode: "upload-remote")`. See
22
+ * `docs/plans/mcp-remote-images.md` Part A.
23
+ */
24
+ export type RemoteUploadTarget = {
25
+ publicProjectId: string;
26
+ bucket: string;
27
+ coreVersion: string;
28
+ remoteHost: string;
29
+ };
30
+ /**
31
+ * Resolves the project's remote-file settings, once.
32
+ *
33
+ * Cached for the life of the process because the answer does not move — a
34
+ * project's public id never changes and its bucket list changes when someone
35
+ * adds a bucket, which is not something an agent's upload should pay a network
36
+ * round trip to notice. Held as the promise rather than the value so that two
37
+ * uploads arriving together make one request rather than two.
38
+ */
39
+ export type RemoteSettingsLoader = () => Promise<{
40
+ status: "success";
41
+ publicProjectId: string;
42
+ buckets: string[];
43
+ } | ValToolError>;
44
+ export declare function createRemoteSettingsLoader(options: ValServerConfig): RemoteSettingsLoader;
45
+ export declare function nextBucket(buckets: string[]): string;
46
+ /** Test seam: the rotation is process-global, so a suite has to be able to reset it. */
47
+ export declare function resetBucketRotation(): void;
48
+ export declare function resolveRemoteUploadTarget(loadSettings: RemoteSettingsLoader): Promise<{
49
+ status: "success";
50
+ target: RemoteUploadTarget;
51
+ } | ValToolError>;
52
+ /**
53
+ * The remote ref for these bytes, under this schema.
54
+ *
55
+ * The validation hash is the delicate part, and the reason `schema` is a
56
+ * parameter rather than something derived here: it is baked into the ref, and
57
+ * the validator recomputes it from the schema it finds at the path. Hand this
58
+ * the wrong schema and the upload succeeds and then never validates — see
59
+ * `docs/plans/mcp-remote-images.md` Part D, and `remoteImageSchemaFor`, which
60
+ * is where a gallery's synthesized schema comes from.
61
+ */
62
+ export declare function buildRemoteRef(input: {
63
+ target: RemoteUploadTarget;
64
+ bytes: Uint8Array;
65
+ /** `public/...`, with no leading slash — the shape `createRemoteRef` demands. */
66
+ filePath: `public/${string}`;
67
+ fileExt: string;
68
+ metadata: Record<string, unknown> | undefined;
69
+ schema: SerializedImageSchema | SerializedFileSchema;
70
+ }): string;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * What the image tool needs from an image library, and nothing else.
3
+ *
4
+ * The tool is not given `sharp`. It is given these two functions, because
5
+ * that is the whole of what uploading an image needs an image library for:
6
+ * read the dimensions out of the bytes, and re-encode them when the schema
7
+ * asks. Keeping the contract this narrow is what lets the host choose —
8
+ * `sharpImageProcessor` from `@valbuild/mcp/sharp` is the one we ship, and a
9
+ * project that already has an encoder can write its own in twenty lines.
10
+ *
11
+ * It also means this package has no native dependency. `sharp` ships a
12
+ * compiled binary per platform, and a CMS should not put one in every
13
+ * project's install just so that the projects which want image uploads can
14
+ * have them.
15
+ */
16
+ export type ValImageProcessorResult = {
17
+ width: number;
18
+ height: number;
19
+ /** e.g. `image/webp`. */
20
+ mimeType: string;
21
+ };
22
+ export type ValImageEncodeRequest = {
23
+ /** The mime type to convert to, e.g. `image/webp`. */
24
+ mimeType: string;
25
+ /** Between 0 and 1, as `s.image({ encode: { quality } })` states it. */
26
+ quality: number;
27
+ /**
28
+ * The size to scale down to, or `null` when the image already fits inside
29
+ * the schema's bounds. Never an upscale: {@link fitWithin} answers `null`
30
+ * rather than a bigger box.
31
+ */
32
+ resizeTo: {
33
+ width: number;
34
+ height: number;
35
+ } | null;
36
+ };
37
+ export type ValImageProcessor = {
38
+ /**
39
+ * Width, height and mime type of these bytes.
40
+ *
41
+ * `null` when they are not an image this processor can read — which is an
42
+ * answer, not a failure: the tool reports it to the caller as invalid input
43
+ * rather than as an internal error, because that is what it is.
44
+ *
45
+ * Must not throw. A processor that throws is treated as an internal error.
46
+ */
47
+ read(bytes: Uint8Array): Promise<ValImageProcessorResult | null>;
48
+ /**
49
+ * Re-encode, or decline to.
50
+ *
51
+ * `null` means "I could not, upload the original" — the same answer the
52
+ * browser gives when a canvas is unavailable. A failed optimisation must
53
+ * never become a failed upload, so this is a normal outcome and not an
54
+ * error.
55
+ */
56
+ encode(bytes: Uint8Array, request: ValImageEncodeRequest): Promise<{
57
+ bytes: Uint8Array;
58
+ mimeType: string;
59
+ } | null>;
60
+ };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Val's content tools, and the checks that decide whether a request may reach
3
+ * them, for hosts that speak the Model Context Protocol.
4
+ *
5
+ * Nothing in this package imports an MCP SDK. The app owns the transport —
6
+ * which SDK, which route, which framework — and this owns the parts that must
7
+ * not be re-decided per app: the tools themselves, whether a request is allowed
8
+ * to reach them at all, and whose credential it carries.
9
+ * `docs/plans/mcp.md` Part A has the reasoning; the short version is that the
10
+ * SDK reorganised itself once already, and the security checks should not move
11
+ * when it does again.
12
+ */
13
+ export { createValTools } from "./tools/index.js";
14
+ export type { ValToolsOptions } from "./tools/index.js";
15
+ export type { ValScope, ValToolAuth, ValToolContext, ValToolDefinition, ValToolDefinitionJson, ValToolError, ValToolErrorCode, ValToolResult, ValTools, } from "./tools/index.js";
16
+ export { VAL_SCOPE_READ, VAL_SCOPE_WRITE, authorIdFromVerifiedSubject, } from "./tools/index.js";
17
+ export { defineTool, err, ok, savePatch } from "./tools/index.js";
18
+ export type { OnInvalid, SavePatchData, SavePatchResult, UploadPatchFiles, ValToolDeps, ValToolImpl, ValToolState, } from "./tools/index.js";
19
+ export { createValImageTools } from "./images/index.js";
20
+ export type { ValImageProcessor, ValImageProcessorResult, ValImageEncodeRequest, } from "./images/index.js";
21
+ export { initValMcp } from "./initValMcp.js";
22
+ export type { ValMcp, ValMcpAuthorizationResult } from "./initValMcp.js";
23
+ export type { ValOAuthConfig } from "./valAccessToken.js";
24
+ export type { ValMcpMetadataHandlers } from "./valMcpMetadata.js";
@@ -0,0 +1,95 @@
1
+ import { ValConfig, ValModules } from "@valbuild/core";
2
+ import { type ValToolContext, type ValToolImpl, type ValTools } from "./tools/index.js";
3
+ import { type ValOAuthConfig } from "./valAccessToken.js";
4
+ import { type ValMcpMetadataHandlers } from "./valMcpMetadata.js";
5
+ /**
6
+ * Val's tools over MCP, and the two checks that have to happen before a request
7
+ * gets to them.
8
+ *
9
+ * Nothing here imports an MCP SDK. The app owns the transport — which SDK, which
10
+ * route, which framework — and this owns the parts that must not be re-decided
11
+ * per app: whether the request is allowed to reach the tools at all, and whose
12
+ * credential it carries. `docs/plans/mcp.md` Part A has the reasoning; the short
13
+ * version is that the SDK reorganised itself once already, and the security
14
+ * checks should not move when it does again.
15
+ */
16
+ export type ValMcpAuthorizationResult = {
17
+ status: "ok";
18
+ tools: ValTools;
19
+ ctx: ValToolContext;
20
+ } | {
21
+ status: "refused";
22
+ response: Response;
23
+ };
24
+ export type ValMcp = {
25
+ /**
26
+ * Check a request and, if it is allowed, hand back the tools and the context
27
+ * to call them with.
28
+ *
29
+ * Call this per request — both halves are per request. Refusing early is the
30
+ * point: a refused request must not reach the protocol layer, let alone a
31
+ * tool.
32
+ */
33
+ valMcpAuthorize: (request: Request | undefined) => Promise<ValMcpAuthorizationResult>;
34
+ /**
35
+ * The registry itself, for listing tools at startup.
36
+ *
37
+ * Listing needs no credential — it reads no content — so registering tools
38
+ * with an MCP server can happen once rather than per request.
39
+ */
40
+ valMcpTools: () => Promise<ValTools>;
41
+ /**
42
+ * The RFC 9728 protected-resource document, for mounting at
43
+ * `/.well-known/oauth-protected-resource`.
44
+ *
45
+ * `null` when no `oauth` config was given: a project that has not been told
46
+ * where to authorize must not publish a document claiming it knows.
47
+ */
48
+ valMcpMetadata: ValMcpMetadataHandlers | null;
49
+ };
50
+ export declare function initValMcp(valModules: ValModules, config: ValConfig, opts?: {
51
+ formatter?: (code: string, filePath: string) => string | Promise<string>;
52
+ /**
53
+ * Where to authorize, and what audience to expect.
54
+ *
55
+ * Required for proxy mode, and only omittable in local filesystem mode —
56
+ * where there is no authorization server to talk to and no backend to
57
+ * authenticate to, so local development stays a one-liner. Provide it and
58
+ * every call needs a verified access token, including in fs mode, where a
59
+ * token is refused rather than ignored: a host that thinks it is
60
+ * authenticating should never silently get unauthenticated local access.
61
+ */
62
+ oauth?: ValOAuthConfig;
63
+ /**
64
+ * Tools to serve alongside the built-in ones.
65
+ *
66
+ * This is how the image tool gets in. It is not in the default set because
67
+ * it needs an image library to read the dimensions out of a JPEG and to
68
+ * re-encode one, and `sharp` is a native dependency that no project should
69
+ * acquire by installing a CMS. So the host constructs it —
70
+ * `createValImageTools(sharpImageProcessor(sharp))` — and passes it here,
71
+ * which is also what makes it the host's decision rather than ours.
72
+ *
73
+ * A name that collides with a built-in tool is refused rather than
74
+ * silently winning or silently losing: an agent that called `get_source`
75
+ * and reached something else would be a genuinely confusing afternoon.
76
+ */
77
+ extraTools?: ValToolImpl[];
78
+ /**
79
+ * The Val API route the Studio is mounted on. Only the data layer reads it.
80
+ */
81
+ route?: string;
82
+ /**
83
+ * Package versions, for a host that has its own.
84
+ *
85
+ * `initHandlerOptions` refuses to build a proxy-mode config without both,
86
+ * so a Next app passes its own version through
87
+ * (`@valbuild/next/server`'s `initValMcp` does this for you). A host that
88
+ * is not Next gets this package's version, which is the honest answer: it
89
+ * is the package serving the request.
90
+ */
91
+ versions?: {
92
+ core?: string;
93
+ next?: string;
94
+ };
95
+ }): ValMcp;
@@ -0,0 +1,46 @@
1
+ import type { ValImageProcessor } from "../images/types.js";
2
+ /**
3
+ * `sharp`, as the image tool wants it.
4
+ *
5
+ * The library is passed IN rather than imported, and that is the whole point of
6
+ * this file existing separately. `sharp` ships a compiled binary per platform;
7
+ * importing it here would put one in every project that installs Val, for a
8
+ * feature most of them will not mount. Instead the project installs `sharp`
9
+ * itself and writes:
10
+ *
11
+ * ```ts
12
+ * import sharp from "sharp";
13
+ * import { createValImageTools } from "@valbuild/mcp";
14
+ * import { sharpImageProcessor } from "@valbuild/mcp/sharp";
15
+ *
16
+ * const tools = createValImageTools(sharpImageProcessor(sharp));
17
+ * ```
18
+ *
19
+ * The parameter is typed structurally — {@link SharpLike} — rather than as
20
+ * `import("sharp")`, for the same reason: a type-only import of a package that
21
+ * is not installed is still a compile error, and this package must typecheck in
22
+ * a project that has never heard of sharp. The real `sharp` satisfies it, and
23
+ * `sharpImageProcessor.test.ts` asserts as much against the actual library so
24
+ * the structural type cannot drift away from it unnoticed.
25
+ */
26
+ /** What `sharp(bytes)` gives back, narrowed to what this file calls. */
27
+ export type SharpPipelineLike = {
28
+ metadata(): Promise<{
29
+ width?: number | undefined;
30
+ height?: number | undefined;
31
+ format?: string | undefined;
32
+ }>;
33
+ resize(options: {
34
+ width?: number | undefined;
35
+ height?: number | undefined;
36
+ fit?: "inside" | undefined;
37
+ withoutEnlargement?: boolean | undefined;
38
+ }): SharpPipelineLike;
39
+ rotate(): SharpPipelineLike;
40
+ webp(options: {
41
+ quality?: number | undefined;
42
+ }): SharpPipelineLike;
43
+ toBuffer(): Promise<Buffer>;
44
+ };
45
+ export type SharpLike = (input: Uint8Array) => SharpPipelineLike;
46
+ export declare function sharpImageProcessor(sharp: SharpLike): ValImageProcessor;
@@ -0,0 +1,47 @@
1
+ import type { ValModules } from "@valbuild/core";
2
+ import { type ValOps, type ValServerConfig } from "@valbuild/server";
3
+ import type { ValToolImpl, ValToolState } from "./defineTool.js";
4
+ import { type ValToolError, type ValTools } from "./types.js";
5
+ export type ValToolsOptions = ValServerConfig;
6
+ /**
7
+ * Val's server-side tool registry.
8
+ *
9
+ * This is the piece Val did not have: the Studio's chat tools are defined *and
10
+ * executed in the browser*, against its client stores, so nothing here could be
11
+ * re-exposed. These tools run against {@link ValOps} instead, which is what lets
12
+ * an MCP server — or a stdio transport, or anything else — drive Val content
13
+ * without a browser.
14
+ *
15
+ * Transport-agnostic on purpose: nothing under `tools/` imports an MCP SDK. A
16
+ * host adapts {@link ValToolResult} at its own edge.
17
+ */
18
+ export declare function createValTools(valModules: ValModules, options: ValToolsOptions,
19
+ /**
20
+ * Tools the host built itself, served alongside the built-in ones.
21
+ *
22
+ * The image tool arrives this way: it needs an image library the host has to
23
+ * install, so it cannot be constructed here. See `createValImageTools`.
24
+ */
25
+ extraTools?: ValToolImpl[]): ValTools;
26
+ /**
27
+ * The content as the caller should see it, loaded once per call.
28
+ *
29
+ * Pending patches are applied, because an agent looking at a project mid-edit
30
+ * should see what the Studio would show rather than the last published state.
31
+ *
32
+ * Deliberately not cached across calls. In fs mode a save recomputes the base
33
+ * sha within the same process, so a cached view would go stale silently — and
34
+ * the cost of being wrong here is an agent writing a patch against content that
35
+ * has already moved.
36
+ *
37
+ * Exported for `toolsFixture`, not for consumers: `tools/index.ts` re-exports by
38
+ * name, so this stays inside the package. A test that assembled its own state
39
+ * would be asserting against a view no real call ever sees.
40
+ */
41
+ export declare function loadState(ops: ValOps): Promise<{
42
+ status: "ok";
43
+ state: ValToolState;
44
+ } | {
45
+ status: "error";
46
+ result: ValToolError;
47
+ }>;
@@ -0,0 +1,79 @@
1
+ import type { Json, ModuleFilePath, PatchId, SerializedSchema } from "@valbuild/core";
2
+ import type { z } from "zod";
3
+ import type { ValOps, OrderedPatches, PatchAnalysis, Schemas, Sources, ValServerConfig } from "@valbuild/server";
4
+ import type { ValToolContext, ValToolDefinition, ValToolError, 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
+ /**
22
+ * How this project is configured — the same object `createValOps` was built
23
+ * from.
24
+ *
25
+ * Here because remote images need it: which credential talks to the content
26
+ * host about them is a property of the project, not of the caller, and a tool
27
+ * that had to be handed it separately would be handed a second copy of an
28
+ * answer the registry already has.
29
+ */
30
+ config: ValServerConfig;
31
+ ctx: ValToolContext;
32
+ /**
33
+ * The content as the caller should see it: base sources with pending patches
34
+ * applied, plus the schemas and the patch analysis that produced them.
35
+ *
36
+ * Loaded once per call and shared, because a tool that re-derived it would
37
+ * both pay for it again and risk reading a different revision than the one it
38
+ * validated against.
39
+ */
40
+ state: ValToolState;
41
+ };
42
+ export type ValToolState = {
43
+ schemas: Schemas;
44
+ serializedSchemas: Record<ModuleFilePath, SerializedSchema>;
45
+ sources: Sources;
46
+ patches: OrderedPatches;
47
+ analysis: PatchAnalysis;
48
+ /**
49
+ * Pending patches that would not apply, by the module they belong to.
50
+ *
51
+ * A module listed here has `sources` that silently lack those changes, so its
52
+ * content is not what publishing would produce. Reads still return it — it is
53
+ * what the Studio would show too, and a project has to stay diagnosable — but
54
+ * a write to such a module is refused, because it would be based on a state
55
+ * that does not exist.
56
+ */
57
+ unappliedPatches: Record<ModuleFilePath, {
58
+ patchId: PatchId;
59
+ skipped: boolean;
60
+ error: {
61
+ message: string;
62
+ };
63
+ }[]>;
64
+ };
65
+ export type ValToolImpl = ValToolDefinition & {
66
+ handler: (args: unknown, deps: ValToolDeps) => Promise<ValToolResult>;
67
+ };
68
+ /**
69
+ * Declare a tool, binding its handler to its input schema.
70
+ *
71
+ * The handler receives already-parsed arguments: the registry validates against
72
+ * `inputSchema` before calling, so a handler never sees input its schema would
73
+ * have rejected.
74
+ */
75
+ export declare function defineTool<S extends z.ZodType>(definition: Omit<ValToolDefinition, "inputSchema"> & {
76
+ inputSchema: S;
77
+ }, handler: (args: z.infer<S>, deps: ValToolDeps) => Promise<ValToolResult>): ValToolImpl;
78
+ export declare function ok(data: Json): ValToolResult;
79
+ export declare function err(code: ValToolErrorCode, message: string): ValToolError;
@@ -0,0 +1,7 @@
1
+ export { createValTools, type ValToolsOptions } from "./createValTools.js";
2
+ export { defineTool, err, ok } from "./defineTool.js";
3
+ export type { ValToolDeps, ValToolImpl, ValToolState } from "./defineTool.js";
4
+ export { savePatch, mintPatchId, deriveParentRef } from "./writePath.js";
5
+ export type { OnInvalid, SavePatchData, SavePatchResult, UploadPatchFiles, } from "./writePath.js";
6
+ export { VAL_SCOPE_READ, VAL_SCOPE_WRITE, authorIdFromVerifiedSubject, } from "./types.js";
7
+ export type { ValScope, ValToolAuth, ValToolContext, ValToolDefinition, ValToolDefinitionJson, ValToolError, ValToolErrorCode, ValToolResult, ValTools, } from "./types.js";
@@ -0,0 +1,172 @@
1
+ import type { AuthorId } from "@valbuild/server";
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 there is one acceptable answer: the host
79
+ * **checked a signature**.
80
+ *
81
+ * A union of one, deliberately. It carried a second variant — a personal access
82
+ * token relayed to the backend unchecked, on the reasoning that the app cannot
83
+ * resolve one and the backend can. The reasoning held; the shape did not. A
84
+ * credential the host cannot check is one it also cannot refuse, so accepting
85
+ * one made "a deployed endpoint that authenticates nobody" a supported
86
+ * configuration, and it let a host serve these tools without ever being told
87
+ * where callers should authorize. The discriminant stays so that adding a
88
+ * second *verified* kind stays a one-line change at every call site.
89
+ */
90
+ export type ValToolAuth = {
91
+ type: "verified-profile";
92
+ /**
93
+ * The profile the host **verified** — the `sub` of an access token whose
94
+ * signature, issuer, audience and expiry were all checked against the
95
+ * authorization server's published key.
96
+ *
97
+ * This field is why an identity field exists at all, and an earlier version
98
+ * of this file argued none should. That argument was about a specific case
99
+ * and stated too broadly: an id the host *asserts* on the strength of a
100
+ * credential it cannot check is an unverified claim dressed as a checked one,
101
+ * and that is still refused — it is why a relayed token never carried a
102
+ * profile, and now cannot reach here at all. An id the host *verified*
103
+ * cryptographically is a different thing, and it is the same standing the
104
+ * Studio has when it re-signs a session it established itself.
105
+ */
106
+ profileId: AuthorId;
107
+ /**
108
+ * The token's granted scopes, as the authorization server issued them.
109
+ *
110
+ * Enforced here as well as by the backend, deliberately. Two checks on one
111
+ * grant is not redundancy for its own sake: this one can refuse a write
112
+ * before it is attempted, so a token that may only read never reaches the
113
+ * code that builds a patch.
114
+ */
115
+ scopes: string[];
116
+ };
117
+ /**
118
+ * Who is calling, established once per request by the host.
119
+ *
120
+ * `null` means local fs mode, where there is no credential to hold and patches
121
+ * are written with no author, exactly as the Studio does locally (D.1). In
122
+ * proxy mode `null` is refused rather than falling back to the app's own API
123
+ * key: that key can do more than any single user, and quietly substituting it
124
+ * would turn a missing credential into full access.
125
+ */
126
+ export type ValToolContext = {
127
+ auth: ValToolAuth | null;
128
+ /** Groups a run of related edits, when the host has such a notion. */
129
+ sessionId: string | null;
130
+ };
131
+ /**
132
+ * Brand a verified subject as an {@link AuthorId}.
133
+ *
134
+ * `AuthorId` is a branded string so that an id cannot be conjured from any
135
+ * string that happens to be lying around — which is exactly the mistake this
136
+ * type is guarding against. That makes one assertion unavoidable at the boundary
137
+ * where a real id enters the system, so it lives here, once, with a name that
138
+ * says what makes it legitimate: the caller has *verified* this subject, not
139
+ * received it.
140
+ *
141
+ * Do not reach for this to satisfy a type. If you are holding a string you did
142
+ * not verify, the honest value is `null`.
143
+ */
144
+ export declare function authorIdFromVerifiedSubject(subject: string): AuthorId;
145
+ /** Read access. Every call needs it, the writes included. */
146
+ export declare const VAL_SCOPE_READ = "val:read";
147
+ /** Write access. Needed *in addition* by any tool not marked `readOnlyHint`. */
148
+ export declare const VAL_SCOPE_WRITE = "val:write";
149
+ export type ValScope = typeof VAL_SCOPE_READ | typeof VAL_SCOPE_WRITE;
150
+ export type ValToolResult = {
151
+ status: "ok";
152
+ data: Json;
153
+ } | ValToolError;
154
+ /**
155
+ * The failure half of {@link ValToolResult}, on its own.
156
+ *
157
+ * Named because several internal steps can only fail — speculative validation
158
+ * rejecting a patch, an upload that did not land — and typing those as the
159
+ * whole union means the compiler cannot see that an early return is in fact an
160
+ * error return.
161
+ */
162
+ export type ValToolError = {
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
+ };