@graspful/mcp 0.1.0 → 0.2.1
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 +273 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +85 -13
- package/dist/index.js.map +1 -1
- package/package.json +9 -2
- package/dist/quality-gate.d.ts +0 -20
- package/dist/quality-gate.js +0 -16
- package/dist/quality-gate.js.map +0 -1
- package/dist/schemas/academy-manifest.schema.d.ts +0 -163
- package/dist/schemas/academy-manifest.schema.js +0 -70
- package/dist/schemas/academy-manifest.schema.js.map +0 -1
- package/dist/schemas/brand-yaml.schema.d.ts +0 -651
- package/dist/schemas/brand-yaml.schema.js +0 -97
- package/dist/schemas/brand-yaml.schema.js.map +0 -1
- package/dist/schemas/course-yaml.schema.d.ts +0 -1793
- package/dist/schemas/course-yaml.schema.js +0 -161
- package/dist/schemas/course-yaml.schema.js.map +0 -1
- package/dist/schemas/index.d.ts +0 -3
- package/dist/schemas/index.js +0 -20
- package/dist/schemas/index.js.map +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# @graspful/mcp
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@graspful/mcp)
|
|
4
|
+
[](https://modelcontextprotocol.io)
|
|
5
|
+
[](https://github.com/willwearing/graspful/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
MCP server for creating adaptive learning courses. AI agents scaffold, validate, review, and publish courses as YAML knowledge graphs.
|
|
8
|
+
|
|
9
|
+
Part of [Graspful](https://graspful.ai) -- the agent-first adaptive learning platform. Courses are authored as two YAML files (graph structure + content), validated offline, then imported via API.
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
### 1. Configure your editor
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx @graspful/cli init
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Detects your editor (Claude Code, Cursor, Windsurf) and writes the MCP config automatically.
|
|
20
|
+
|
|
21
|
+
Or add manually to your editor's MCP config (see [Editor Configuration](#editor-configuration) below):
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"mcpServers": {
|
|
26
|
+
"graspful": {
|
|
27
|
+
"command": "npx",
|
|
28
|
+
"args": ["@graspful/mcp"]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 2. Create an account
|
|
35
|
+
|
|
36
|
+
Call the `graspful_register` tool with your email and password. This creates an account, org, and API key — all in one step, no browser needed.
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
graspful_register(email: "you@example.com", password: "your-password")
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Or via CLI: `npx @graspful/cli register --email you@example.com --password your-password`
|
|
43
|
+
|
|
44
|
+
### 3. Build a course
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
graspful_scaffold_course(topic: "Your Topic", estimatedHours: 10)
|
|
48
|
+
→ edit the YAML
|
|
49
|
+
graspful_validate(yaml: "...")
|
|
50
|
+
graspful_review_course(yaml: "...")
|
|
51
|
+
graspful_import_course(yaml: "...", org: "your-org", publish: true)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Scaffold, fill, validate, and review work offline — no account needed. You only need to register before importing or publishing.
|
|
55
|
+
|
|
56
|
+
## Available Tools
|
|
57
|
+
|
|
58
|
+
10 tools. Focused and minimal -- agents degrade above 40 tools.
|
|
59
|
+
|
|
60
|
+
| Tool | Description | Auth Required |
|
|
61
|
+
|------|-------------|:---:|
|
|
62
|
+
| `graspful_scaffold_course` | Generate a course YAML skeleton with sections, concepts, and prerequisite edges | No |
|
|
63
|
+
| `graspful_fill_concept` | Add knowledge points and problem stubs to a specific concept | No |
|
|
64
|
+
| `graspful_validate` | Validate any Graspful YAML against its Zod schema. Auto-detects file type | No |
|
|
65
|
+
| `graspful_review_course` | Run all 10 mechanical quality checks. Returns a score with failure details | No |
|
|
66
|
+
| `graspful_describe_course` | Compute course statistics without importing (concept/KP/problem counts, graph depth) | No |
|
|
67
|
+
| `graspful_create_brand` | Generate brand YAML scaffold for a white-label learning site | No |
|
|
68
|
+
| `graspful_import_course` | Import course YAML into an organization. Creates as draft by default | Yes |
|
|
69
|
+
| `graspful_publish_course` | Publish a draft course. Runs review gate first -- all 10 checks must pass | Yes |
|
|
70
|
+
| `graspful_import_brand` | Import brand YAML to create white-label site config | Yes |
|
|
71
|
+
| `graspful_list_courses` | List all courses in an organization | Yes |
|
|
72
|
+
|
|
73
|
+
## Tool Reference
|
|
74
|
+
|
|
75
|
+
### `graspful_scaffold_course`
|
|
76
|
+
|
|
77
|
+
Generate a course YAML skeleton with sections, concepts, and prerequisite edges. Returns a minimal valid YAML structure with TODO placeholders. This is step 1 of the Graspful two-YAML workflow.
|
|
78
|
+
|
|
79
|
+
| Parameter | Type | Required | Description |
|
|
80
|
+
|-----------|------|:---:|-------------|
|
|
81
|
+
| `topic` | string | Yes | Course topic (e.g., "Linear Algebra") |
|
|
82
|
+
| `estimatedHours` | number | No | Total course hours (default: 10) |
|
|
83
|
+
| `sourceDocument` | string | No | Reference to source material |
|
|
84
|
+
|
|
85
|
+
### `graspful_fill_concept`
|
|
86
|
+
|
|
87
|
+
Add knowledge points (KPs) and problem stubs to a specific concept in a course YAML. Returns the full updated YAML. Fails if the concept already has KPs (prevents accidental overwrites).
|
|
88
|
+
|
|
89
|
+
| Parameter | Type | Required | Description |
|
|
90
|
+
|-----------|------|:---:|-------------|
|
|
91
|
+
| `yaml` | string | Yes | Full course YAML string |
|
|
92
|
+
| `conceptId` | string | Yes | ID of the concept to fill |
|
|
93
|
+
| `kps` | number | No | Number of KP stubs (default: 2) |
|
|
94
|
+
| `problemsPerKp` | number | No | Problems per KP (default: 3) |
|
|
95
|
+
|
|
96
|
+
### `graspful_validate`
|
|
97
|
+
|
|
98
|
+
Validate any Graspful YAML (course, brand, or academy manifest) against its Zod schema. Auto-detects file type. For courses, also checks DAG validity and prerequisite integrity. Runs offline -- no API needed. Use frequently during authoring.
|
|
99
|
+
|
|
100
|
+
| Parameter | Type | Required | Description |
|
|
101
|
+
|-----------|------|:---:|-------------|
|
|
102
|
+
| `yaml` | string | Yes | YAML string to validate |
|
|
103
|
+
|
|
104
|
+
**Returns:** `{ valid, fileType, errors, stats }`
|
|
105
|
+
|
|
106
|
+
### `graspful_review_course`
|
|
107
|
+
|
|
108
|
+
Run all 10 mechanical quality checks. Returns a score (e.g., "8/10") with details on each failure. A score of 10/10 is required for publishing.
|
|
109
|
+
|
|
110
|
+
**The 10 checks:**
|
|
111
|
+
|
|
112
|
+
1. Schema validation (valid Zod schema)
|
|
113
|
+
2. Unique problem IDs
|
|
114
|
+
3. Valid prerequisites (all refs point to real concepts)
|
|
115
|
+
4. No DAG cycles
|
|
116
|
+
5. Minimum KPs per concept
|
|
117
|
+
6. Minimum problems per KP
|
|
118
|
+
7. Difficulty distribution (2+ levels per concept)
|
|
119
|
+
8. Explanation coverage (worked examples)
|
|
120
|
+
9. Question deduplication (no near-duplicates at same difficulty)
|
|
121
|
+
10. Concept tag coverage
|
|
122
|
+
|
|
123
|
+
| Parameter | Type | Required | Description |
|
|
124
|
+
|-----------|------|:---:|-------------|
|
|
125
|
+
| `yaml` | string | Yes | Full course YAML string |
|
|
126
|
+
|
|
127
|
+
**Returns:** `{ passed, score, failures, warnings, stats }`
|
|
128
|
+
|
|
129
|
+
### `graspful_import_course`
|
|
130
|
+
|
|
131
|
+
Import course YAML into a Graspful organization. Creates as draft by default. If `publish=true`, the server runs the review gate first.
|
|
132
|
+
|
|
133
|
+
| Parameter | Type | Required | Description |
|
|
134
|
+
|-----------|------|:---:|-------------|
|
|
135
|
+
| `yaml` | string | Yes | Full course YAML string |
|
|
136
|
+
| `org` | string | Yes | Organization slug |
|
|
137
|
+
| `publish` | boolean | No | Publish immediately (default: false) |
|
|
138
|
+
|
|
139
|
+
**Returns:** `{ courseId, url, published, reviewFailures? }`
|
|
140
|
+
|
|
141
|
+
### `graspful_publish_course`
|
|
142
|
+
|
|
143
|
+
Publish a draft course. Server runs the review gate first -- all 10 checks must pass.
|
|
144
|
+
|
|
145
|
+
| Parameter | Type | Required | Description |
|
|
146
|
+
|-----------|------|:---:|-------------|
|
|
147
|
+
| `courseId` | string | Yes | Course ID (UUID) |
|
|
148
|
+
| `org` | string | Yes | Organization slug |
|
|
149
|
+
|
|
150
|
+
**Returns:** `{ courseId, published }`
|
|
151
|
+
|
|
152
|
+
### `graspful_describe_course`
|
|
153
|
+
|
|
154
|
+
Compute statistics for a course YAML without importing it. Use to track authoring progress.
|
|
155
|
+
|
|
156
|
+
| Parameter | Type | Required | Description |
|
|
157
|
+
|-----------|------|:---:|-------------|
|
|
158
|
+
| `yaml` | string | Yes | Full course YAML string |
|
|
159
|
+
|
|
160
|
+
**Returns:** `{ courseName, courseId, version, estimatedHours, concepts, authoredConcepts, stubConcepts, knowledgePoints, problems, graphDepth, conceptsWithoutKps, kpsWithoutProblems, sections }`
|
|
161
|
+
|
|
162
|
+
### `graspful_create_brand`
|
|
163
|
+
|
|
164
|
+
Generate brand YAML scaffold for a white-label learning site. Niche presets set appropriate colors, taglines, and copy.
|
|
165
|
+
|
|
166
|
+
| Parameter | Type | Required | Description |
|
|
167
|
+
|-----------|------|:---:|-------------|
|
|
168
|
+
| `niche` | string | Yes | `education`, `healthcare`, `finance`, `tech`, or `legal` |
|
|
169
|
+
| `name` | string | No | Brand name |
|
|
170
|
+
| `domain` | string | No | Custom domain |
|
|
171
|
+
| `orgSlug` | string | No | Organization slug |
|
|
172
|
+
|
|
173
|
+
### `graspful_import_brand`
|
|
174
|
+
|
|
175
|
+
Import brand YAML into Graspful. Creates the white-label site configuration.
|
|
176
|
+
|
|
177
|
+
| Parameter | Type | Required | Description |
|
|
178
|
+
|-----------|------|:---:|-------------|
|
|
179
|
+
| `yaml` | string | Yes | Full brand YAML string |
|
|
180
|
+
| `orgSlug` | string | Yes | Organization slug |
|
|
181
|
+
|
|
182
|
+
**Returns:** `{ slug, domain, verificationStatus }`
|
|
183
|
+
|
|
184
|
+
### `graspful_list_courses`
|
|
185
|
+
|
|
186
|
+
List all courses in a Graspful organization.
|
|
187
|
+
|
|
188
|
+
| Parameter | Type | Required | Description |
|
|
189
|
+
|-----------|------|:---:|-------------|
|
|
190
|
+
| `org` | string | Yes | Organization slug |
|
|
191
|
+
|
|
192
|
+
## Typical Agent Workflow
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
1. Scaffold graspful_scaffold_course(topic: "Kubernetes Networking", estimatedHours: 8)
|
|
196
|
+
Edit the returned YAML -- add concepts, set prerequisites
|
|
197
|
+
|
|
198
|
+
2. Validate graspful_validate(yaml) -- catch schema errors early
|
|
199
|
+
|
|
200
|
+
3. Fill graspful_fill_concept(yaml, conceptId: "k8s-services")
|
|
201
|
+
Repeat for each concept. Replace TODO placeholders with real content.
|
|
202
|
+
Validate after each fill.
|
|
203
|
+
|
|
204
|
+
4. Review graspful_review_course(yaml) -- run quality gate
|
|
205
|
+
Fix failures, re-review until 10/10
|
|
206
|
+
|
|
207
|
+
5. Import graspful_import_course(yaml, org: "acme") -- creates draft
|
|
208
|
+
|
|
209
|
+
6. Brand graspful_create_brand(niche: "tech", name: "Acme Learn")
|
|
210
|
+
graspful_import_brand(yaml, orgSlug: "acme")
|
|
211
|
+
|
|
212
|
+
7. Publish graspful_publish_course(courseId: "...", org: "acme")
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Offline tools (scaffold, fill, validate, review, describe, create_brand) need no API key. Only import, publish, import_brand, and list_courses require `GRASPFUL_API_KEY`.
|
|
216
|
+
|
|
217
|
+
## Editor Configuration
|
|
218
|
+
|
|
219
|
+
### Claude Code / Claude Desktop
|
|
220
|
+
|
|
221
|
+
Add to `~/.claude/claude_desktop_config.json` or your project's `.mcp.json`:
|
|
222
|
+
|
|
223
|
+
```json
|
|
224
|
+
{
|
|
225
|
+
"mcpServers": {
|
|
226
|
+
"graspful": {
|
|
227
|
+
"command": "npx",
|
|
228
|
+
"args": ["@graspful/mcp"],
|
|
229
|
+
"env": {
|
|
230
|
+
"GRASPFUL_API_KEY": "gsk_your_key_here"
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Cursor
|
|
238
|
+
|
|
239
|
+
Add to `.cursor/mcp.json` in your project root:
|
|
240
|
+
|
|
241
|
+
```json
|
|
242
|
+
{
|
|
243
|
+
"mcpServers": {
|
|
244
|
+
"graspful": {
|
|
245
|
+
"command": "npx",
|
|
246
|
+
"args": ["@graspful/mcp"],
|
|
247
|
+
"env": {
|
|
248
|
+
"GRASPFUL_API_KEY": "gsk_your_key_here"
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Windsurf / Codex / Other MCP-compatible agents
|
|
256
|
+
|
|
257
|
+
Same pattern -- point `command` at `npx` and `args` at `@graspful/mcp`. The server communicates over stdio using the [Model Context Protocol](https://modelcontextprotocol.io).
|
|
258
|
+
|
|
259
|
+
## Environment Variables
|
|
260
|
+
|
|
261
|
+
| Variable | Required | Description |
|
|
262
|
+
|----------|:---:|-------------|
|
|
263
|
+
| `GRASPFUL_API_KEY` | For import/publish/list | API key for authenticated operations |
|
|
264
|
+
| `GRASPFUL_API_URL` | No | API base URL (default: `https://api.graspful.ai`) |
|
|
265
|
+
|
|
266
|
+
## Links
|
|
267
|
+
|
|
268
|
+
- [Graspful](https://graspful.ai) -- Platform
|
|
269
|
+
- [Content Authoring Guide](../../content/README.md) -- YAML schema reference and authoring rules
|
|
270
|
+
- [Course Review Gate](../../docs/course-review-gate.md) -- Full specification of all 10 quality checks
|
|
271
|
+
- [CLI Agent Strategy](../../docs/cli-agent-strategy.md) -- Design philosophy and architecture
|
|
272
|
+
- [@graspful/cli](https://www.npmjs.com/package/@graspful/cli) -- CLI companion package
|
|
273
|
+
- [Model Context Protocol](https://modelcontextprotocol.io) -- MCP specification
|
package/dist/index.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
CHANGED
|
@@ -39,10 +39,22 @@ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
|
39
39
|
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
40
40
|
const yaml = __importStar(require("js-yaml"));
|
|
41
41
|
const crypto = __importStar(require("crypto"));
|
|
42
|
-
const
|
|
42
|
+
const shared_1 = require("@graspful/shared");
|
|
43
|
+
// ─── Auth guard ─────────────────────────────────────────────────────────────
|
|
44
|
+
const AUTH_REQUIRED_ERROR = 'Not authenticated. To authenticate, either:\n' +
|
|
45
|
+
'1. Call the graspful_register tool with an email and password to create an account and get an API key, OR\n' +
|
|
46
|
+
'2. Set the GRASPFUL_API_KEY environment variable (e.g., GRASPFUL_API_KEY=gsk_...).\n\n' +
|
|
47
|
+
'You can scaffold, validate, and review courses without authentication. ' +
|
|
48
|
+
'Authentication is only required for importing, publishing, and listing courses.';
|
|
49
|
+
function requireApiAuth() {
|
|
50
|
+
const apiKey = process.env.GRASPFUL_API_KEY;
|
|
51
|
+
if (!apiKey) {
|
|
52
|
+
throw new Error(AUTH_REQUIRED_ERROR);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
43
55
|
// ─── API Client (mirrors packages/cli/src/lib/api-client.ts) ──────────────
|
|
44
56
|
function getApiCredentials() {
|
|
45
|
-
const baseUrl = (process.env.GRASPFUL_API_URL || 'https://api.graspful.
|
|
57
|
+
const baseUrl = (process.env.GRASPFUL_API_URL || 'https://api.graspful.ai').replace(/\/$/, '');
|
|
46
58
|
const apiKey = process.env.GRASPFUL_API_KEY;
|
|
47
59
|
if (apiKey) {
|
|
48
60
|
return { baseUrl, authHeader: `Bearer ${apiKey}` };
|
|
@@ -226,9 +238,9 @@ function validateYaml(yamlStr) {
|
|
|
226
238
|
return { valid: false, errors: ['Could not detect file type. Expected top-level key: course, brand, or academy'], stats: {} };
|
|
227
239
|
}
|
|
228
240
|
const schemaMap = {
|
|
229
|
-
course:
|
|
230
|
-
brand:
|
|
231
|
-
academy:
|
|
241
|
+
course: shared_1.CourseYamlSchema,
|
|
242
|
+
brand: shared_1.BrandYamlSchema,
|
|
243
|
+
academy: shared_1.AcademyManifestSchema,
|
|
232
244
|
};
|
|
233
245
|
const result = schemaMap[fileType].safeParse(raw);
|
|
234
246
|
if (!result.success) {
|
|
@@ -261,7 +273,7 @@ function validateYaml(yamlStr) {
|
|
|
261
273
|
}
|
|
262
274
|
// ─── Review helpers (mirrors packages/cli/src/commands/review.ts) ───────────
|
|
263
275
|
function checkYamlParses(raw) {
|
|
264
|
-
const result =
|
|
276
|
+
const result = shared_1.CourseYamlSchema.safeParse(raw);
|
|
265
277
|
if (result.success) {
|
|
266
278
|
return { check: 'yaml_parses', passed: true };
|
|
267
279
|
}
|
|
@@ -552,7 +564,7 @@ function runReview(yamlStr) {
|
|
|
552
564
|
stats: { concepts: 0, kps: 0, problems: 0, authoredConcepts: 0, stubConcepts: 0 },
|
|
553
565
|
};
|
|
554
566
|
}
|
|
555
|
-
const data =
|
|
567
|
+
const data = shared_1.CourseYamlSchema.parse(raw);
|
|
556
568
|
const authoredConcepts = data.concepts.filter((c) => c.knowledgePoints.length > 0);
|
|
557
569
|
const stubConcepts = data.concepts.filter((c) => c.knowledgePoints.length === 0);
|
|
558
570
|
const kps = data.concepts.reduce((sum, c) => sum + c.knowledgePoints.length, 0);
|
|
@@ -615,7 +627,7 @@ function computeGraphDepth(concepts) {
|
|
|
615
627
|
}
|
|
616
628
|
function describeCourse(yamlStr) {
|
|
617
629
|
const raw = yaml.load(yamlStr);
|
|
618
|
-
const result =
|
|
630
|
+
const result = shared_1.CourseYamlSchema.safeParse(raw);
|
|
619
631
|
if (!result.success) {
|
|
620
632
|
throw new Error(`Invalid course YAML: ${result.error.issues[0]?.message ?? 'unknown error'}`);
|
|
621
633
|
}
|
|
@@ -672,7 +684,7 @@ function describeCourse(yamlStr) {
|
|
|
672
684
|
// ─── Fill concept helper (mirrors packages/cli/src/commands/fill-concept.ts) ─
|
|
673
685
|
function fillConcept(yamlStr, conceptId, options) {
|
|
674
686
|
const raw = yaml.load(yamlStr);
|
|
675
|
-
const parsed =
|
|
687
|
+
const parsed = shared_1.CourseYamlSchema.safeParse(raw);
|
|
676
688
|
if (!parsed.success) {
|
|
677
689
|
throw new Error(`Invalid course YAML: ${parsed.error.issues[0]?.message ?? 'unknown error'}`);
|
|
678
690
|
}
|
|
@@ -816,7 +828,7 @@ A score of 10/10 is required for publishing. Run this before graspful_import_cou
|
|
|
816
828
|
name: 'graspful_import_course',
|
|
817
829
|
description: `Import a course YAML into a Graspful organization. Creates the course as a draft by default.
|
|
818
830
|
|
|
819
|
-
Requires GRASPFUL_API_KEY environment variable
|
|
831
|
+
IMPORTANT: Requires authentication. If not authenticated, call graspful_register first to create an account and get an API key, or set the GRASPFUL_API_KEY environment variable. Without auth, this tool will fail.
|
|
820
832
|
|
|
821
833
|
If publish=true, the server runs the review gate first — the course must pass all 10 quality checks to be published. If review fails, the course is imported as a draft and failures are returned.
|
|
822
834
|
|
|
@@ -835,7 +847,7 @@ Returns { courseId, url, published, reviewFailures? }.`,
|
|
|
835
847
|
name: 'graspful_publish_course',
|
|
836
848
|
description: `Publish a draft course (sets isPublished = true). The server runs the review gate — course must pass all 10 quality checks.
|
|
837
849
|
|
|
838
|
-
Requires GRASPFUL_API_KEY environment variable
|
|
850
|
+
IMPORTANT: Requires authentication. If not authenticated, call graspful_register first to create an account and get an API key, or set the GRASPFUL_API_KEY environment variable. Without auth, this tool will fail.
|
|
839
851
|
|
|
840
852
|
Returns { courseId, published }.`,
|
|
841
853
|
inputSchema: {
|
|
@@ -895,7 +907,7 @@ Edit the YAML to customize, then import with graspful_import_brand.`,
|
|
|
895
907
|
name: 'graspful_import_brand',
|
|
896
908
|
description: `Import a brand YAML into Graspful. Creates the white-label site configuration.
|
|
897
909
|
|
|
898
|
-
Requires GRASPFUL_API_KEY environment variable
|
|
910
|
+
IMPORTANT: Requires authentication. If not authenticated, call graspful_register first to create an account and get an API key, or set the GRASPFUL_API_KEY environment variable. Without auth, this tool will fail.
|
|
899
911
|
|
|
900
912
|
Returns { slug, domain, verificationStatus }.`,
|
|
901
913
|
inputSchema: {
|
|
@@ -910,7 +922,7 @@ Returns { slug, domain, verificationStatus }.`,
|
|
|
910
922
|
name: 'graspful_list_courses',
|
|
911
923
|
description: `List all courses in a Graspful organization.
|
|
912
924
|
|
|
913
|
-
Requires GRASPFUL_API_KEY environment variable
|
|
925
|
+
IMPORTANT: Requires authentication. If not authenticated, call graspful_register first to create an account and get an API key, or set the GRASPFUL_API_KEY environment variable. Without auth, this tool will fail.
|
|
914
926
|
|
|
915
927
|
Returns an array of courses with their IDs, names, published status, and stats.`,
|
|
916
928
|
inputSchema: {
|
|
@@ -921,6 +933,24 @@ Returns an array of courses with their IDs, names, published status, and stats.`
|
|
|
921
933
|
required: ['org'],
|
|
922
934
|
},
|
|
923
935
|
},
|
|
936
|
+
{
|
|
937
|
+
name: 'graspful_register',
|
|
938
|
+
description: `Create a new Graspful account and organization. Returns an API key that authenticates all subsequent tool calls.
|
|
939
|
+
|
|
940
|
+
Call this BEFORE using graspful_import_course, graspful_publish_course, graspful_import_brand, or graspful_list_courses. Those tools require authentication and will fail without it.
|
|
941
|
+
|
|
942
|
+
You do NOT need to register to use graspful_scaffold_course, graspful_fill_concept, graspful_validate, graspful_review_course, graspful_describe_course, or graspful_create_brand — those work offline.
|
|
943
|
+
|
|
944
|
+
Returns { userId, orgSlug, apiKey }. The API key is automatically used for subsequent authenticated tool calls in this session.`,
|
|
945
|
+
inputSchema: {
|
|
946
|
+
type: 'object',
|
|
947
|
+
properties: {
|
|
948
|
+
email: { type: 'string', description: 'Email address for the new account' },
|
|
949
|
+
password: { type: 'string', description: 'Password (min 8 characters)' },
|
|
950
|
+
},
|
|
951
|
+
required: ['email', 'password'],
|
|
952
|
+
},
|
|
953
|
+
},
|
|
924
954
|
];
|
|
925
955
|
function textResult(text) {
|
|
926
956
|
return { content: [{ type: 'text', text }] };
|
|
@@ -956,6 +986,7 @@ async function handleToolCall(name, args) {
|
|
|
956
986
|
}
|
|
957
987
|
case 'graspful_import_course': {
|
|
958
988
|
try {
|
|
989
|
+
requireApiAuth();
|
|
959
990
|
const result = await apiPost(`/api/v1/orgs/${args.org}/courses/import`, { yaml: args.yaml, publish: args.publish ?? false });
|
|
960
991
|
return textResult(JSON.stringify(result, null, 2));
|
|
961
992
|
}
|
|
@@ -965,6 +996,7 @@ async function handleToolCall(name, args) {
|
|
|
965
996
|
}
|
|
966
997
|
case 'graspful_publish_course': {
|
|
967
998
|
try {
|
|
999
|
+
requireApiAuth();
|
|
968
1000
|
const result = await apiPost(`/api/v1/orgs/${args.org}/courses/${args.courseId}/publish`, {});
|
|
969
1001
|
return textResult(JSON.stringify(result, null, 2));
|
|
970
1002
|
}
|
|
@@ -991,6 +1023,7 @@ async function handleToolCall(name, args) {
|
|
|
991
1023
|
}
|
|
992
1024
|
case 'graspful_import_brand': {
|
|
993
1025
|
try {
|
|
1026
|
+
requireApiAuth();
|
|
994
1027
|
let raw;
|
|
995
1028
|
try {
|
|
996
1029
|
raw = yaml.load(args.yaml);
|
|
@@ -1007,6 +1040,7 @@ async function handleToolCall(name, args) {
|
|
|
1007
1040
|
}
|
|
1008
1041
|
case 'graspful_list_courses': {
|
|
1009
1042
|
try {
|
|
1043
|
+
requireApiAuth();
|
|
1010
1044
|
const result = await apiGet(`/api/v1/orgs/${args.org}/courses`);
|
|
1011
1045
|
return textResult(JSON.stringify(result, null, 2));
|
|
1012
1046
|
}
|
|
@@ -1014,6 +1048,44 @@ async function handleToolCall(name, args) {
|
|
|
1014
1048
|
return errorResult(`List failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1015
1049
|
}
|
|
1016
1050
|
}
|
|
1051
|
+
case 'graspful_register': {
|
|
1052
|
+
try {
|
|
1053
|
+
const email = args.email;
|
|
1054
|
+
const password = args.password;
|
|
1055
|
+
const { baseUrl } = getApiCredentials();
|
|
1056
|
+
const res = await fetch(`${baseUrl}/api/v1/auth/register`, {
|
|
1057
|
+
method: 'POST',
|
|
1058
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1059
|
+
body: JSON.stringify({ email, password }),
|
|
1060
|
+
});
|
|
1061
|
+
if (!res.ok) {
|
|
1062
|
+
const text = await res.text();
|
|
1063
|
+
let message = `Registration failed (${res.status})`;
|
|
1064
|
+
try {
|
|
1065
|
+
const parsed = JSON.parse(text);
|
|
1066
|
+
if (parsed.message)
|
|
1067
|
+
message = parsed.message;
|
|
1068
|
+
}
|
|
1069
|
+
catch {
|
|
1070
|
+
if (text)
|
|
1071
|
+
message = text;
|
|
1072
|
+
}
|
|
1073
|
+
return errorResult(message);
|
|
1074
|
+
}
|
|
1075
|
+
const data = await res.json();
|
|
1076
|
+
// Set the API key in the environment so subsequent tool calls are authenticated
|
|
1077
|
+
process.env.GRASPFUL_API_KEY = data.apiKey;
|
|
1078
|
+
return textResult(JSON.stringify({
|
|
1079
|
+
userId: data.userId,
|
|
1080
|
+
orgSlug: data.orgSlug,
|
|
1081
|
+
apiKey: data.apiKey,
|
|
1082
|
+
message: `Account created. Organization slug: ${data.orgSlug}. API key is now active for this session — you can call graspful_import_course, graspful_publish_course, and other authenticated tools.`,
|
|
1083
|
+
}, null, 2));
|
|
1084
|
+
}
|
|
1085
|
+
catch (e) {
|
|
1086
|
+
return errorResult(`Registration failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1017
1089
|
default:
|
|
1018
1090
|
return errorResult(`Unknown tool: ${name}`);
|
|
1019
1091
|
}
|