@usegraft/mcp 0.1.1 → 1.0.0-beta.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/README.md +53 -0
- package/dist/index.d.ts +136 -5
- package/dist/index.js +1739 -680
- package/package.json +24 -10
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
CHANGED
|
@@ -2,8 +2,16 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
2
2
|
import { Storage } from '@usegraft/assets';
|
|
3
3
|
import { AnyCollection, AnyGraftFunction, FunctionActor, RateLimit } from '@usegraft/core';
|
|
4
4
|
import { Database, BranchScope, AuditStore, ApprovalStore } from '@usegraft/db';
|
|
5
|
+
import { MdxTrust } from '@usegraft/mdx-safety';
|
|
5
6
|
import { ErrorCode } from '@usegraft/contracts';
|
|
6
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
|
+
|
|
7
15
|
interface GraftMcpOptions {
|
|
8
16
|
/** Absolute path to the content root (documents live at <contentDir>/<collection>/<slug>.mdx). */
|
|
9
17
|
contentDir: string;
|
|
@@ -27,6 +35,13 @@ interface GraftMcpOptions {
|
|
|
27
35
|
* projects still work.
|
|
28
36
|
*/
|
|
29
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;
|
|
30
45
|
/** Content branch to project into. Defaults to "main". */
|
|
31
46
|
branchId?: string;
|
|
32
47
|
/**
|
|
@@ -46,7 +61,45 @@ interface GraftMcpOptions {
|
|
|
46
61
|
* createFunctionsHandler / createGraftMcpHandler. Defaults to anonymous.
|
|
47
62
|
*/
|
|
48
63
|
actor?: (request: Request) => FunctionActor | Promise<FunctionActor>;
|
|
49
|
-
/**
|
|
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 `approvalPolicy` in graft.config.ts on
|
|
101
|
+
* `graft serve`'s /api/fn routes. It just does not reach the tool surface.
|
|
102
|
+
*/
|
|
50
103
|
approvalPolicy?: "none" | "human";
|
|
51
104
|
rateLimit?: RateLimit;
|
|
52
105
|
gitSha?: string;
|
|
@@ -58,6 +111,36 @@ interface GraftMcpOptions {
|
|
|
58
111
|
* GRAFT_DEV_TOKEN; the HTTP handler forwards the caller's own header.
|
|
59
112
|
*/
|
|
60
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
|
+
};
|
|
61
144
|
/**
|
|
62
145
|
* Audit / approval stores for run_function. Defaults match createFunctionsHandler
|
|
63
146
|
* (db-backed). Pass `audit: false` in unit tests that do not hit a real DB.
|
|
@@ -76,7 +159,26 @@ interface GraftMcpOptions {
|
|
|
76
159
|
*/
|
|
77
160
|
storage?: Storage | (() => Storage | Promise<Storage>);
|
|
78
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">;
|
|
79
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;
|
|
80
182
|
|
|
81
183
|
interface GraftMcpHandlerOptions extends GraftMcpOptions {
|
|
82
184
|
/**
|
|
@@ -86,13 +188,36 @@ interface GraftMcpHandlerOptions extends GraftMcpOptions {
|
|
|
86
188
|
*/
|
|
87
189
|
actor?: (request: Request) => FunctionActor | Promise<FunctionActor>;
|
|
88
190
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
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.
|
|
91
202
|
*/
|
|
92
|
-
|
|
203
|
+
allowAnonymous?: boolean;
|
|
93
204
|
}
|
|
94
205
|
type GraftMcpHandler = (request: Request) => Promise<Response>;
|
|
95
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;
|
|
96
221
|
|
|
97
222
|
/**
|
|
98
223
|
* The self-teaching error knowledge base behind the `explain_error` tool.
|
|
@@ -120,6 +245,12 @@ declare function explainCode(code: string): ErrorExplanation | undefined;
|
|
|
120
245
|
* `createGraftMcp` builds the server; `serveStdio`
|
|
121
246
|
* binds it to stdio for `.mcp.json` / `graft mcp`; `createGraftMcpHandler`
|
|
122
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
|
+
*
|
|
123
254
|
* See docs/design-notes/agent-mcp.md.
|
|
124
255
|
*/
|
|
125
256
|
|
|
@@ -134,4 +265,4 @@ declare function explainCode(code: string): ErrorExplanation | undefined;
|
|
|
134
265
|
*/
|
|
135
266
|
declare function serveStdio(server: McpServer): Promise<void>;
|
|
136
267
|
|
|
137
|
-
export { ERROR_KNOWLEDGE, type ErrorExplanation, type GraftMcpHandler, type GraftMcpHandlerOptions, type GraftMcpOptions, createGraftMcp, createGraftMcpHandler, explainCode, serveStdio };
|
|
268
|
+
export { type DocsMcpOptions, ERROR_KNOWLEDGE, type ErrorExplanation, type GraftMcpHandler, type GraftMcpHandlerOptions, type GraftMcpOptions, createDocsMcp, createDocsMcpHandler, createGraftMcp, createGraftMcpHandler, explainCode, serveStdio };
|