@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 ADDED
@@ -0,0 +1,273 @@
1
+ # @graspful/mcp
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@graspful/mcp)](https://www.npmjs.com/package/@graspful/mcp)
4
+ [![MCP](https://img.shields.io/badge/MCP-compatible-blue)](https://modelcontextprotocol.io)
5
+ [![License](https://img.shields.io/npm/l/@graspful/mcp)](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
@@ -1,2 +1,3 @@
1
1
  #!/usr/bin/env node
2
2
  export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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 schemas_1 = require("./schemas");
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.com').replace(/\/$/, '');
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: schemas_1.CourseYamlSchema,
230
- brand: schemas_1.BrandYamlSchema,
231
- academy: schemas_1.AcademyManifestSchema,
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 = schemas_1.CourseYamlSchema.safeParse(raw);
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 = schemas_1.CourseYamlSchema.parse(raw);
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 = schemas_1.CourseYamlSchema.safeParse(raw);
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 = schemas_1.CourseYamlSchema.safeParse(raw);
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 to be set.
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 to be set.
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 to be set.
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 to be set.
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
  }