@usegraft/mcp 0.0.0-canary-20260831153011

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anderson Joseph
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # @usegraft/mcp
2
+
3
+ > An MCP server over your content, schema, and typed functions, with errors an agent can act on.
4
+
5
+ Part of [Graft](https://github.com/AndersonDesign1/graft), a CMS built so an AI agent is the primary operator.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm i @usegraft/mcp
11
+ ```
12
+
13
+ ## Serve on stdio
14
+
15
+ For `.mcp.json` and local agents. `graft mcp` does this for you.
16
+
17
+ ```ts
18
+ import { createGraftMcp, serveStdio } from "@usegraft/mcp";
19
+
20
+ const server = createGraftMcp({ contentDir, collections, functions, db });
21
+ await serveStdio(server);
22
+ ```
23
+
24
+ ## Serve over HTTP
25
+
26
+ ```ts
27
+ import { createGraftMcpHandler } from "@usegraft/mcp";
28
+
29
+ export const POST = createGraftMcpHandler({
30
+ contentDir,
31
+ collections,
32
+ db,
33
+ actor: resolveActor,
34
+ connectionActor: resolveConnectionActor,
35
+ });
36
+ ```
37
+
38
+ `connectionActor` is not optional in practice. Without it every write-tool scope check is silently disabled, so pass it whenever a tool can write.
39
+
40
+ ## Tools
41
+
42
+ Content: `list_content`, `read_content`, `write_content`, `delete_content`, `search_content`. Introspection: `describe_schema`, `list_functions`, `describe_function`. Execution: `run_function`. Registry: `list_registry`, `describe_item`. Approvals: `list_approvals`, `decide_approval`.
43
+
44
+ ## Defaults that fail closed
45
+
46
+ - Anonymous callers are refused unless explicitly allowed.
47
+ - `write_content` requires the `content:write` scope, and being authenticated earns nothing on its own.
48
+ - `delete_content` is destructive and always human-gated: the first call files an approval and fails with its id.
49
+ - Authored MDX is refused if it contains `{…}` expressions, `import`, `export` or spread attributes, because rendering evaluates MDX as JavaScript on the server.
50
+
51
+ ---
52
+
53
+ MIT. [Repository](https://github.com/AndersonDesign1/graft) · [Changelog](https://github.com/AndersonDesign1/graft/blob/main/packages/mcp/CHANGELOG.md) · [Security policy](https://github.com/AndersonDesign1/graft/blob/main/SECURITY.md)
@@ -0,0 +1,268 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { Storage } from '@usegraft/assets';
3
+ import { AnyCollection, AnyGraftFunction, FunctionActor, RateLimit } from '@usegraft/core';
4
+ import { Database, BranchScope, AuditStore, ApprovalStore } from '@usegraft/db';
5
+ import { MdxTrust } from '@usegraft/mdx-safety';
6
+ import { ErrorCode } from '@usegraft/contracts';
7
+
8
+ /**
9
+ * How a Graft MCP server is configured.
10
+ *
11
+ * Split out of server.ts so tool modules can name it without importing the
12
+ * server that hosts them.
13
+ */
14
+
15
+ interface GraftMcpOptions {
16
+ /** Absolute path to the content root (documents live at <contentDir>/<collection>/<slug>.mdx). */
17
+ contentDir: string;
18
+ collections: Record<string, AnyCollection>;
19
+ /**
20
+ * The Postgres content index. Omit only when serving a static project via
21
+ * `staticIndexPath` — `db` wins if both are somehow set.
22
+ */
23
+ db?: Database;
24
+ /**
25
+ * Path to a compiled static index artifact (.graft/index.db) — the
26
+ * zero-service tier. Authoring works exactly as it does on Postgres (files
27
+ * are the truth, write_content recompiles), and the Postgres-tier tools
28
+ * (functions, branches, approvals, the gated delete) answer NEEDS_DATABASE
29
+ * with the upgrade rather than being silently absent.
30
+ */
31
+ staticIndexPath?: string;
32
+ /**
33
+ * Typed functions from graft.config — enables list_functions / describe_function
34
+ * / run_function and fills describe_schema.functions. Optional so content-only
35
+ * projects still work.
36
+ */
37
+ functions?: Record<string, AnyGraftFunction>;
38
+ /**
39
+ * How much of MDX authored bodies may be, from `mdxTrust` in graft.config.ts.
40
+ * Defaults to "restricted". Applies to the whole tree on every projection,
41
+ * not just to bodies arriving through write_content, because compile re-reads
42
+ * every authored file including the ones that came from git.
43
+ */
44
+ mdxTrust?: MdxTrust;
45
+ /** Content branch to project into. Defaults to "main". */
46
+ branchId?: string;
47
+ /**
48
+ * Resolved read scope for `branchId` (from resolveBranchHandle / resolveBranchScope)
49
+ * — what makes search_content overlay-aware: an overlay branch searches its full
50
+ * ancestor chain, so content inherited from parents is found, branch overrides
51
+ * win, and tombstones hide. When omitted, the server resolves the scope itself
52
+ * on first search (memoized per server instance, like sdk-core's per-client
53
+ * memo), so a bare branchId still searches the branch's effective content.
54
+ */
55
+ scope?: BranchScope;
56
+ /** Server identity reported to MCP clients. */
57
+ name?: string;
58
+ version?: string;
59
+ /**
60
+ * Resolve the caller for run_function (and future gated tools). Same seam as
61
+ * createFunctionsHandler / createGraftMcpHandler. Defaults to anonymous.
62
+ */
63
+ actor?: (request: Request) => FunctionActor | Promise<FunctionActor>;
64
+ /**
65
+ * The identity this connection authenticated as, already resolved.
66
+ *
67
+ * `actor` above resolves a *Request*, which tools do not have — they are
68
+ * JSON-RPC calls on an established connection. Tools that need to know who
69
+ * is calling (rather than merely forwarding a credential) read this instead.
70
+ * The HTTP handler sets it from the bearer it already verified; `graft mcp`
71
+ * sets it from the dev-token identity. Absent means anonymous.
72
+ */
73
+ connectionActor?: FunctionActor;
74
+ /**
75
+ * Directory `put_asset`'s `path` argument may read from, enabling that
76
+ * argument at all.
77
+ *
78
+ * Unset — which is every remote mount — means `put_asset` has no `path`
79
+ * argument: a remote agent sends bytes as base64 or nothing. It used to pass
80
+ * the raw string to readFileSync with no containment whatsoever, upload the
81
+ * result under a key of the caller's choosing, and return a fetchable URL, so
82
+ * `{ path: "/srv/app/.env" }` was a one-call read of DATABASE_URL, dev tokens
83
+ * and S3 credentials on any HTTP-mounted server.
84
+ *
85
+ * `graft mcp` sets it to the project directory: a local agent uploading a
86
+ * hero image from the repo is the case the argument exists for.
87
+ */
88
+ localUploadRoot?: string;
89
+ /**
90
+ * Forwarded to createFunctionsHandler for run_function.
91
+ *
92
+ * Deliberately narrower than the core option, which also accepts
93
+ * `"unattended"`. That policy exists for a deployment with no human to ask —
94
+ * a scheduled job, a CI step — and an MCP mount is the opposite of that: it
95
+ * exists because an agent is calling it, and the agent is the party the gate
96
+ * is there to stop. Allowing it here would let one option turn every
97
+ * destructive tool an agent can reach into an ungated one.
98
+ *
99
+ * A headless deployment that genuinely wants it still has it, on
100
+ * `createFunctionsHandler` and through `GRAFT_APPROVAL_POLICY` on
101
+ * `graft serve`'s /api/fn routes. It just does not reach the tool surface.
102
+ */
103
+ approvalPolicy?: "none" | "human";
104
+ rateLimit?: RateLimit;
105
+ gitSha?: string;
106
+ /**
107
+ * Bearer token applied to run_function when the tool call passes no
108
+ * `authorization` — so the credential lives with the server (env/config),
109
+ * not in the agent's context window or MCP transcript. An explicit
110
+ * `authorization` argument still wins. `graft mcp` sets this from
111
+ * GRAFT_DEV_TOKEN; the HTTP handler forwards the caller's own header.
112
+ */
113
+ defaultAuthorization?: string;
114
+ /**
115
+ * Ask the connected client's human to decide a pending approval in-band,
116
+ * instead of failing with an id and waiting for someone to run
117
+ * `graft approve`.
118
+ *
119
+ * **Off unless configured, and it belongs only on a mount whose human is
120
+ * actually present** — a local stdio server, a desktop client. A remote or
121
+ * public mount must never set it: there is nobody at the other end to ask,
122
+ * and an elicitation nobody answers is a destructive call left hanging.
123
+ *
124
+ * The reason this needs an explicit `decider` rather than reusing the
125
+ * connection's identity is the invariant underneath: `decideApproval`
126
+ * enforces `requested_by_id <> decided_by` in the UPDATE's own WHERE, so a
127
+ * requester deciding its own approval is refused by Postgres, not by a guard
128
+ * that could be forgotten. Elicitation changes *how the human is asked*, not
129
+ * *who is recorded as having answered*. Name the operator sitting at the
130
+ * machine; if that operator is also the requester, the decision is refused
131
+ * exactly as it would be from the CLI.
132
+ *
133
+ * Deciding is a plain UPDATE on `approvals`, which the hardened runtime role
134
+ * deliberately cannot perform (see `graft harden`). On such a deployment this
135
+ * fails, correctly and by construction.
136
+ */
137
+ approvalElicitation?: {
138
+ /** The operator a decision is attributed to. Never the requester. */
139
+ decider: {
140
+ kind: string;
141
+ id: string;
142
+ };
143
+ };
144
+ /**
145
+ * Audit / approval stores for run_function. Defaults match createFunctionsHandler
146
+ * (db-backed). Pass `audit: false` in unit tests that do not hit a real DB.
147
+ */
148
+ audit?: AuditStore | false;
149
+ approvals?: ApprovalStore;
150
+ /**
151
+ * Registry root for list_registry / describe_item. Defaults to @usegraft/registry's
152
+ * bundled primitives — the set `graft add` installs from. Tests point it at a fixture.
153
+ */
154
+ registryRoot?: string;
155
+ /**
156
+ * Asset store for put_asset. Defaults to S3_* env config (same rules as
157
+ * `graft asset put`), resolved lazily so content-only servers never need it.
158
+ * Tests inject a fake.
159
+ */
160
+ storage?: Storage | (() => Storage | Promise<Storage>);
161
+ }
162
+
163
+ /**
164
+ * What a public documentation server needs, which is much less than the full
165
+ * one. No `functions`, no `actor`, no asset store, no approval elicitation:
166
+ * omitting them from the type is what keeps them from being configured by
167
+ * accident on a mount that answers the internet.
168
+ */
169
+ type DocsMcpOptions = Pick<GraftMcpOptions, "contentDir" | "collections" | "db" | "staticIndexPath" | "branchId" | "scope" | "name" | "version">;
170
+ declare function createGraftMcp(options: GraftMcpOptions): McpServer;
171
+ /**
172
+ * A public, read-only documentation server.
173
+ *
174
+ * A separate factory rather than a flag on `createGraftMcp`, because the
175
+ * safety property worth having is that the closed mount gains no new way to be
176
+ * opened. There is no option here that widens the surface; reaching the wider
177
+ * one means calling the other function.
178
+ *
179
+ * Serve it at `/mcp` on the docs domain, which is where clients look.
180
+ */
181
+ declare function createDocsMcp(options: DocsMcpOptions): McpServer;
182
+
183
+ interface GraftMcpHandlerOptions extends GraftMcpOptions {
184
+ /**
185
+ * Resolve the caller — the same @usegraft/auth `createActorResolver` seam the
186
+ * functions handler uses. A resolver that throws (TOKEN_INVALID) rejects the
187
+ * request with 401; per-tool authorization lands with function introspection.
188
+ */
189
+ actor?: (request: Request) => FunctionActor | Promise<FunctionActor>;
190
+ /**
191
+ * Serve callers who did not authenticate.
192
+ *
193
+ * Off by default, and deliberately phrased as an opt-*in* to insecurity: this
194
+ * handler is built to be embedded in a Next.js route, a self-host container,
195
+ * Vercel Fluid, or a Worker, and the previous `requireActor` flag defaulted to
196
+ * off — so forgetting it silently published write_content, put_asset,
197
+ * delete_content and decide_approval to anyone who found the URL.
198
+ *
199
+ * Constructing a handler with neither `actor` nor `allowAnonymous: true`
200
+ * throws, so a deployer who forgets gets a startup failure with a fix line
201
+ * rather than an open endpoint.
202
+ */
203
+ allowAnonymous?: boolean;
204
+ }
205
+ type GraftMcpHandler = (request: Request) => Promise<Response>;
206
+ declare function createGraftMcpHandler(options: GraftMcpHandlerOptions): GraftMcpHandler;
207
+ /**
208
+ * A public documentation MCP endpoint, over the same stateless transport.
209
+ *
210
+ * Deliberately has no `actor` and no `allowAnonymous`. The full handler refuses
211
+ * to start without one of them because it serves writes, uploads and approval
212
+ * decisions; this one serves documentation, so there is nothing to authenticate
213
+ * and nothing to accidentally leave open. That is the point of it being a
214
+ * separate function: the closed endpoint gains no new way to be opened.
215
+ *
216
+ * Mount it at `/mcp` on the docs domain, which is where clients look —
217
+ * Mintlify generates one there for every site it hosts, and Cloudflare runs a
218
+ * documentation server separately from its authenticated API server.
219
+ */
220
+ declare function createDocsMcpHandler(options: DocsMcpOptions): GraftMcpHandler;
221
+
222
+ /**
223
+ * The self-teaching error knowledge base behind the `explain_error` tool.
224
+ *
225
+ * Every ErrorCode in @usegraft/contracts has an entry: what the failure means, what
226
+ * usually causes it, and how to recover. A GraftError's `fix` is specific to one
227
+ * failure; this registry is the general lesson an agent can apply next time.
228
+ * A test asserts the registry stays in lockstep with ErrorCodes.
229
+ */
230
+
231
+ interface ErrorExplanation {
232
+ code: ErrorCode;
233
+ meaning: string;
234
+ typicalCauses: string[];
235
+ howToRecover: string;
236
+ }
237
+ declare const ERROR_KNOWLEDGE: Record<ErrorCode, ErrorExplanation>;
238
+ /** Explanation for a code, or undefined if the code is unknown. */
239
+ declare function explainCode(code: string): ErrorExplanation | undefined;
240
+
241
+ /**
242
+ * @usegraft/mcp
243
+ * MCP server: content ops + schema/function introspection + run_function +
244
+ * registry browse (list_registry / describe_item) + agent-actionable errors.
245
+ * `createGraftMcp` builds the server; `serveStdio`
246
+ * binds it to stdio for `.mcp.json` / `graft mcp`; `createGraftMcpHandler`
247
+ * serves it over Streamable HTTP as a stateless `Request → Response` handler.
248
+ *
249
+ * `createDocsMcp` / `createDocsMcpHandler` build the other one: a public,
250
+ * read-only documentation server for `/mcp` on a docs domain. It is a separate
251
+ * factory rather than a flag, so the authenticated endpoint gains no new way to
252
+ * be opened.
253
+ *
254
+ * See docs/design-notes/agent-mcp.md.
255
+ */
256
+
257
+ /**
258
+ * Serve an MCP server over stdio (the transport agents' `.mcp.json` entries use).
259
+ * Resolves when the client disconnects (stdin EOF / transport close), NOT at
260
+ * connect time — callers await this for the server's whole lifetime and then
261
+ * clean up. Resolving at connect let `graft mcp` fall through to its
262
+ * process.exit right after the initialize handshake (latent P6.2 bug caught
263
+ * by the P6.5 live smoke; the example's `pnpm mcp` script masked it by never
264
+ * exiting after the await).
265
+ */
266
+ declare function serveStdio(server: McpServer): Promise<void>;
267
+
268
+ export { type DocsMcpOptions, ERROR_KNOWLEDGE, type ErrorExplanation, type GraftMcpHandler, type GraftMcpHandlerOptions, type GraftMcpOptions, createDocsMcp, createDocsMcpHandler, createGraftMcp, createGraftMcpHandler, explainCode, serveStdio };