@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 +21 -0
- package/README.md +53 -0
- package/dist/index.d.ts +268 -0
- package/dist/index.js +2211 -0
- package/package.json +61 -0
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)
|
package/dist/index.d.ts
ADDED
|
@@ -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 };
|