@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,160 @@
1
+ import type { ModuleFilePath, PatchId } from "@valbuild/core";
2
+ import { type Patch, type ParentRef } from "@valbuild/core/patch";
3
+ import type { PatchAnalysis, ValOps } from "@valbuild/server";
4
+ import type { ValToolDeps, ValToolState } from "./defineTool.js";
5
+ import type { ValToolError } from "./types.js";
6
+ /**
7
+ * A patch id, minted before the write is attempted.
8
+ *
9
+ * Same shape the Studio mints (a v4 UUID), and minting one that never gets used
10
+ * costs nothing — ids are not registered anywhere until a patch carries them.
11
+ */
12
+ export declare function mintPatchId(): PatchId;
13
+ /**
14
+ * What the new patch should hang off.
15
+ *
16
+ * The last known patch if there is one, otherwise the current head. Note the
17
+ * asymmetry between the two backends: `ValOpsFS` ignores `parentRef` entirely
18
+ * because its append-only ordering log defines order, while `ValOpsHttp` sends
19
+ * it up as `parentPatchId` for optimistic concurrency. So a wrong value here is
20
+ * invisible locally and a conflict in production — which is why this is derived
21
+ * fresh rather than remembered.
22
+ */
23
+ export declare function deriveParentRef(ops: ValOps, patches: {
24
+ patches: readonly {
25
+ patchId: PatchId;
26
+ }[];
27
+ }): Promise<ParentRef>;
28
+ /**
29
+ * Would this patch leave the content valid?
30
+ *
31
+ * Applied to a **clone** of the sources, never the real ones: `applyPatch`
32
+ * mutates the document it is given, and ValOps carries a standing note that
33
+ * add operations misbehave without a clone. Validating in place would corrupt
34
+ * the sources every later call in this process reads.
35
+ *
36
+ * Server-side this is strictly better than the Studio's speculative check.
37
+ * `getSchemas()` returns real `Schema` instances, so the user's own `validate`
38
+ * closures run — and those are not carried by the serialized schema the browser
39
+ * has, which means the browser cannot run them at all.
40
+ */
41
+ export declare function validateSpeculatively(ops: ValOps, state: ValToolState, moduleFilePath: ModuleFilePath, patch: Patch,
42
+ /** Bytes already staged for the patch this validates — see {@link PendingFiles}. */
43
+ pendingFiles?: PendingFiles): Promise<{
44
+ status: "valid";
45
+ }
46
+ /** Applicable, but the result would not be publishable. */
47
+ | {
48
+ status: "invalid";
49
+ errors: string;
50
+ }
51
+ /** The patch does not fit the content at all, so there is nothing to judge. */
52
+ | {
53
+ status: "unapplicable";
54
+ result: ValToolError;
55
+ }>;
56
+ /**
57
+ * What to do when the change would leave the content invalid.
58
+ *
59
+ * `"reject"` for a tool that is editing existing content: an agent should not be
60
+ * able to break a site, and a rejected patch stores nothing.
61
+ *
62
+ * `"report"` for a tool whose whole purpose is to create something incomplete.
63
+ * `empty_at_path` scaffolds an entry the caller is then expected to fill in, so
64
+ * on most real schemas — anything with a non-empty string — the value it creates
65
+ * is invalid by construction. Rejecting that would make the tool useless on
66
+ * exactly the schemas it exists for, so instead the patch is saved and the
67
+ * remaining errors come back as a to-do list. This mirrors the Studio, where
68
+ * creating an empty entry is normal and the errors show until it is filled in.
69
+ */
70
+ export type OnInvalid = "reject" | "report";
71
+ /**
72
+ * Put the bytes a `file` op refers to where the patch can find them.
73
+ *
74
+ * Called with the ids the patch is about to be written under, because that is
75
+ * the whole reason this is a hook rather than something the caller does first:
76
+ * an upload is keyed by the patch id, and the patch id is minted here — once
77
+ * per attempt, so a conflict retry uploads under the id that actually lands
78
+ * rather than orphaning the bytes under the one that did not.
79
+ *
80
+ * Uploads run BEFORE `createPatch`, which is the order the Studio uses and the
81
+ * only one that works: a `file` op carries a hash, not data, so a patch stored
82
+ * before its bytes points at nothing, and a reader that arrives in between sees
83
+ * a broken image rather than a missing one.
84
+ */
85
+ export type UploadPatchFiles = (input: {
86
+ patchId: PatchId;
87
+ parentRef: ParentRef;
88
+ }) => Promise<{
89
+ status: "ok";
90
+ /**
91
+ * Which files this upload put where, keyed by the path the source refers
92
+ * to — the same shape `PatchAnalysis.fileLastUpdatedByPatchId` has.
93
+ *
94
+ * Handed back rather than assumed, because speculative validation reads
95
+ * exactly this to decide where a file's bytes are. Without it the check
96
+ * looks for the new image on disk, does not find it, and rejects the very
97
+ * write that was about to put it there.
98
+ */
99
+ files: PendingFiles;
100
+ } | {
101
+ status: "error";
102
+ result: ValToolError;
103
+ }>;
104
+ /**
105
+ * Files uploaded for a patch that does not exist yet.
106
+ *
107
+ * Deliberately the analysis's own type rather than a lookalike: the whole point
108
+ * is that these merge over `fileLastUpdatedByPatchId`, and a second shape that
109
+ * happens to fit today would drift the first time a field is added there.
110
+ */
111
+ export type PendingFiles = PatchAnalysis["fileLastUpdatedByPatchId"];
112
+ export type SavePatchOptions = {
113
+ onInvalid?: OnInvalid;
114
+ uploadFiles?: UploadPatchFiles;
115
+ };
116
+ /**
117
+ * What a successful write reports back.
118
+ *
119
+ * Named, rather than left as the `Json` that `ValToolResult` widens it to, so
120
+ * that a tool building on `savePatch` — the image tool adds its own fields to
121
+ * this — can spread it without re-narrowing `Json` and without an assertion.
122
+ */
123
+ export type SavePatchData = {
124
+ patchId: PatchId;
125
+ moduleFilePath: ModuleFilePath;
126
+ createdAt: string;
127
+ /**
128
+ * Always present, so a caller does not have to tell "absent" from "nothing
129
+ * left to do" to know whether the content is publishable.
130
+ */
131
+ unresolvedValidationErrors: string | null;
132
+ };
133
+ export type SavePatchResult = {
134
+ status: "ok";
135
+ data: SavePatchData;
136
+ } | ValToolError;
137
+ /**
138
+ * Upload, validate, then save — and retry once if someone else got there first.
139
+ *
140
+ * The retry exists because the parent ref is derived from a read that happened
141
+ * before the write. A conflict means the chain moved underneath us, and
142
+ * re-deriving is usually enough. Once only: a loop here would be an agent
143
+ * fighting a human editor in the Studio, and losing slowly is worse than
144
+ * failing clearly.
145
+ *
146
+ * The order of the first two is the part that is easy to get backwards. A
147
+ * patch's `file` op carries a hash, not bytes, so validation asks the store
148
+ * where those bytes are — and if they are not there yet, the check that was
149
+ * supposed to say "this image is fine" says "this image is missing" and rejects
150
+ * the write that would have uploaded it. So the bytes go up first, and the
151
+ * upload says where it put them.
152
+ *
153
+ * The cost is that a REJECTED write leaves an upload no patch refers to. That
154
+ * is a state the patch store already expects and already collects — see
155
+ * `dropStaleUploads` in `patchStore.ts`, which exists because a browser can
156
+ * abandon an upload the same way — so it is bounded rather than untidy, and it
157
+ * is the cheaper of the two failures by a distance: the other one is a write
158
+ * that cannot succeed at all.
159
+ */
160
+ export declare function savePatch(deps: ValToolDeps, moduleFilePath: ModuleFilePath, patch: Patch, options?: SavePatchOptions): Promise<SavePatchResult>;
@@ -0,0 +1,93 @@
1
+ import { type ValToolAuth } from "./tools/index.js";
2
+ /**
3
+ * Verifying an OAuth access token, which is the whole of what makes this app a
4
+ * resource server rather than a relay.
5
+ *
6
+ * The token is issued by Val's authorization server and presented by an MCP
7
+ * client. This app holds no signing key for it and cannot mint one — it fetches
8
+ * the issuer's *public* keys and checks a signature. That asymmetry is the
9
+ * point: a verified `sub` is a fact about the token rather than a claim by
10
+ * whoever sent it, which is what lets the tools attribute a patch to that
11
+ * profile at all.
12
+ *
13
+ * ## Why this is not `jose`
14
+ *
15
+ * `jose` was the first choice and was rejected on a fact rather than a
16
+ * preference: version 6 is ESM-only (`"type": "module"`, no CJS export). This
17
+ * package is built by preconstruct and `require`d by Next.js server code, so an
18
+ * ESM-only dependency here is a runtime failure in consumers' apps, not a build
19
+ * inconvenience. Adding it would also put a dependency in every install of
20
+ * `@valbuild/next` for one function.
21
+ *
22
+ * The actual cryptography is still not hand-rolled — `node:crypto` does the
23
+ * ECDSA and the JWK import. What is written here is the JWS envelope and the
24
+ * claim checks, and the rules that keep that safe are worth stating because
25
+ * this repository has already shipped the counterexample (`decodeJwt`: `exp`
26
+ * never checked, a non-constant-time compare, verification skippable):
27
+ *
28
+ * - **`alg` is pinned**, not read from the token. The header is only consulted
29
+ * for `kid`. A verifier that honours the token's own `alg` can be handed
30
+ * `HS256` and will treat the *published* public key as a shared secret.
31
+ * - **Nothing is read from the payload before the signature verifies.** Claims
32
+ * from an unverified token are attacker input.
33
+ * - **Keys come only from the configured issuer's JWKS**, never from the token.
34
+ * The key set is cached, but a token naming a `kid` the cache does not hold
35
+ * provokes one rate-limited refetch rather than a refusal — see
36
+ * {@link UNKNOWN_KID_REFETCH_INTERVAL_MS}. Without that, this server's own
37
+ * cache turns any key rotation into an outage lasting the rest of the TTL.
38
+ * - **ECDSA JWS signatures are raw `r||s`** (RFC 7518), not DER, which is what
39
+ * `dsaEncoding: "ieee-p1363"` below is for. Omit it and every valid signature
40
+ * is rejected — or worse, a future change makes it accept the wrong thing.
41
+ */
42
+ export type ValOAuthConfig = {
43
+ /**
44
+ * The authorization server, and therefore the expected `iss`.
45
+ *
46
+ * Also where the JWKS is fetched from, so it is the one value that decides
47
+ * which keys can produce a token this app accepts. Configuration, never
48
+ * request input — a request that could name its own issuer could name its own
49
+ * key.
50
+ */
51
+ issuer: string;
52
+ /**
53
+ * This endpoint's absolute URL, and therefore the expected `aud` (RFC 8707).
54
+ *
55
+ * Audience binding is what stops a token minted for one Val site being
56
+ * replayed against another: without it, any deployment the user has access to
57
+ * would accept a token issued for any other.
58
+ */
59
+ resource: string;
60
+ /**
61
+ * Clock skew allowance, in seconds.
62
+ *
63
+ * Servers disagree about the time by more than you would like, and a token
64
+ * refused for being a second early is indistinguishable, to the person using
65
+ * it, from a broken login.
66
+ */
67
+ clockToleranceSeconds?: number;
68
+ /** Test seam. Defaults to the global `fetch`. */
69
+ fetchImpl?: typeof fetch;
70
+ };
71
+ export type ValAccessTokenResult = {
72
+ status: "ok";
73
+ auth: Extract<ValToolAuth, {
74
+ type: "verified-profile";
75
+ }>;
76
+ } | {
77
+ status: "refused";
78
+ /** For the `WWW-Authenticate` challenge: RFC 6750 section 3.1. */
79
+ error: "invalid_request" | "invalid_token" | "insufficient_scope";
80
+ description: string;
81
+ };
82
+ /** Test seam: a fresh process would have an empty cache anyway. */
83
+ export declare function clearValAccessTokenCache(): void;
84
+ /**
85
+ * Read `Authorization: Bearer …`.
86
+ *
87
+ * Exported because the refusal needs to know whether a token was presented at
88
+ * all: RFC 6750 distinguishes "no credential" — a bare `401`, which is an
89
+ * invitation to authenticate — from "a bad credential", and a client that gets
90
+ * the second when it deserved the first will not start the authorization flow.
91
+ */
92
+ export declare function readBearerToken(request: Request): string | null;
93
+ export declare function verifyValAccessToken(request: Request, config: ValOAuthConfig): Promise<ValAccessTokenResult>;
@@ -0,0 +1,47 @@
1
+ import type { ValOAuthConfig } from "./valAccessToken.js";
2
+ /**
3
+ * The one document an MCP client needs before it can authorize: RFC 9728
4
+ * Protected Resource Metadata, served by the *resource* server.
5
+ *
6
+ * This is how a client discovers where to authorize. It asks the resource — this
7
+ * app — and the resource names its authorization server. Which is why this
8
+ * belongs here and the RFC 8414 *authorization server* metadata does not: that
9
+ * document lives at the issuer, describes the issuer's own endpoints, and is
10
+ * served by the issuer. An app serving a copy would be asserting the issuer's
11
+ * configuration on its behalf, and would be wrong the moment the issuer changed
12
+ * anything.
13
+ *
14
+ * The flow, so the split reads as a whole:
15
+ *
16
+ * 1. client → `{app}/api/mcp` with no token → `401` naming this document
17
+ * 2. client → `{app}/.well-known/oauth-protected-resource` → the issuer
18
+ * 3. client → `{issuer}/.well-known/oauth-authorization-server` → endpoints
19
+ * 4. client → issuer's `/authorize`, then `/token`
20
+ * 5. client → `{app}/api/mcp` with the token
21
+ */
22
+ export type ValMcpMetadataHandlers = {
23
+ /** The metadata document. */
24
+ GET: (request: Request) => Response;
25
+ /**
26
+ * The CORS preflight.
27
+ *
28
+ * Required rather than defensive: the metadata document is fetched
29
+ * cross-origin by browser-based clients, and without a preflight answer the
30
+ * fetch fails before the document is read — which presents as "this connector
31
+ * cannot authorize" with nothing in any log to explain it.
32
+ */
33
+ OPTIONS: (request: Request) => Response;
34
+ };
35
+ export declare function createValMcpMetadata(oauth: ValOAuthConfig, scopesSupported: string[]): ValMcpMetadataHandlers;
36
+ /**
37
+ * The `WWW-Authenticate` value for a refusal (RFC 6750 section 3, RFC 9728
38
+ * section 5.1).
39
+ *
40
+ * `resource_metadata` is the load-bearing parameter: it is how a client that has
41
+ * never seen this server learns where to authorize. A `401` without it is a dead
42
+ * end — the client knows it needs a token and has no way to find out from where.
43
+ */
44
+ export declare function wwwAuthenticate(oauth: ValOAuthConfig, scopesSupported: string[], refusal?: {
45
+ error: string;
46
+ description: string;
47
+ }): string;
@@ -0,0 +1,2 @@
1
+ export * from "./declarations/src/index.js";
2
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsYnVpbGQtbWNwLmNqcy5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi9kZWNsYXJhdGlvbnMvc3JjL2luZGV4LmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEifQ==