@aigne/afs-registry 1.11.0-beta.11 → 1.11.0-beta.13

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/dist/index.mjs CHANGED
@@ -1,519 +1,8 @@
1
- import { AFSBaseProvider, AFSNotFoundError, AFSValidationError, Actions, Explain, List, Meta, Read, Search, Stat } from "@aigne/afs";
1
+ import { n as scanInstalledProviders, r as __require, t as clearScanCache } from "./scanner-CI1h9bn6.mjs";
2
+ import { AFSBaseProvider, AFSNotFoundError, AFS_CATEGORIES, Actions, CAPABILITY_TAGS, Explain, List, Meta, Read, Search, Stat } from "@aigne/afs";
2
3
  import { buildURI, getTemplateVariableNames } from "@aigne/afs/utils/uri-template";
3
4
  import { joinURL } from "ufo";
4
5
 
5
- //#region schema/providers.json
6
- var providers = [
7
- {
8
- "name": "fs",
9
- "description": "Local filesystem directory access.\n- Browse directories, read/write files, search content by pattern\n- Exec actions: `archive` (create tar.gz/zip), `checksum` (MD5/SHA)\n- Path structure: direct filesystem paths (e.g., `/docs/guide.md`)",
10
- "category": "storage",
11
- "uriTemplate": "fs://{localPath+}",
12
- "schema": {
13
- "type": "object",
14
- "properties": { "localPath": {
15
- "type": "string",
16
- "description": "Local directory path"
17
- } },
18
- "required": ["localPath"]
19
- },
20
- "tags": ["local", "filesystem"],
21
- "useCases": [
22
- "source code access",
23
- "local data files",
24
- "project navigation"
25
- ]
26
- },
27
- {
28
- "name": "git",
29
- "description": "Git repository browser with branch-based access.\n- Browse branches, read files at any ref, search across repository\n- Exec actions (readwrite): `diff`, `create-branch`, `commit`, `merge`\n- Virtual `.log/` tree exposes commit history per branch\n- Path structure: `/{branch}/{file-path}` (branch `/` encoded as `~`)",
30
- "category": "version-control",
31
- "uriTemplate": "git://{localPath+}",
32
- "schema": {
33
- "type": "object",
34
- "properties": {
35
- "localPath": {
36
- "type": "string",
37
- "description": "Local git repository path"
38
- },
39
- "branch": {
40
- "type": "string",
41
- "description": "Branch name (default: current branch)"
42
- },
43
- "remoteUrl": {
44
- "type": "string",
45
- "description": "Remote URL for cloning"
46
- }
47
- },
48
- "required": ["localPath"]
49
- },
50
- "tags": ["git", "version-control"],
51
- "useCases": [
52
- "code review",
53
- "version history",
54
- "branch comparison"
55
- ]
56
- },
57
- {
58
- "name": "sqlite",
59
- "description": "SQLite database — tables as directories, rows as nodes.\n- Browse tables and rows, full-text search (FTS5), schema introspection\n- Exec actions: `insert`, `update`, `delete` at table/row level, custom SQL\n- Path structure: `/{table}/{primary-key}`",
60
- "category": "database",
61
- "uriTemplate": "sqlite://{localPath+}",
62
- "schema": {
63
- "type": "object",
64
- "properties": { "localPath": {
65
- "type": "string",
66
- "description": "Path to SQLite database file"
67
- } },
68
- "required": ["localPath"]
69
- },
70
- "tags": ["sqlite", "database"],
71
- "useCases": [
72
- "structured data",
73
- "application state",
74
- "data analysis"
75
- ]
76
- },
77
- {
78
- "name": "json",
79
- "description": "JSON or YAML file — navigate and edit structured data as a tree.\n- Objects and arrays become directories, primitives become leaf nodes\n- Read/write individual values, search across structure\n- Path structure: `/{key}/{nested-key}` (arrays indexed by position)",
80
- "category": "structured-data",
81
- "uriTemplate": "json://{localPath+}",
82
- "schema": {
83
- "type": "object",
84
- "properties": { "localPath": {
85
- "type": "string",
86
- "description": "Path to JSON or YAML file"
87
- } },
88
- "required": ["localPath"]
89
- },
90
- "tags": [
91
- "json",
92
- "yaml",
93
- "structured-data"
94
- ],
95
- "useCases": [
96
- "configuration files",
97
- "data inspection",
98
- "API response exploration"
99
- ]
100
- },
101
- {
102
- "name": "toml",
103
- "description": "TOML config file — navigate and edit structured data as a tree.\n- Tables become directories, values become leaf nodes\n- Supports TOML-specific types (datetime, array of tables)\n- Path structure: `/{table}/{key}`",
104
- "category": "structured-data",
105
- "uriTemplate": "toml://{localPath+}",
106
- "schema": {
107
- "type": "object",
108
- "properties": { "localPath": {
109
- "type": "string",
110
- "description": "Path to TOML file"
111
- } },
112
- "required": ["localPath"]
113
- },
114
- "tags": ["toml", "structured-data"],
115
- "useCases": ["configuration files", "Cargo.toml inspection"]
116
- },
117
- {
118
- "name": "sandbox",
119
- "description": "Sandboxed JavaScript execution environment for LLM-generated code.\n- Create, run, and manage scripts with version history\n- Execution history tracking with metrics (duration, CPU, memory)\n- Rate limiting and audit logging for safe agent operation",
120
- "category": "compute",
121
- "uriTemplate": "sandbox://",
122
- "schema": {
123
- "type": "object",
124
- "properties": {}
125
- },
126
- "tags": [
127
- "sandbox",
128
- "compute",
129
- "javascript"
130
- ],
131
- "useCases": [
132
- "code execution",
133
- "data transformation",
134
- "scripting"
135
- ]
136
- },
137
- {
138
- "name": "workspace",
139
- "description": "Workspace container — groups multiple providers under one mount.\n- Configuration-driven sub-mounts with shared access control\n- Delegates all operations to appropriate sub-provider by path",
140
- "category": "composite",
141
- "uriTemplate": "workspace://{localPath+}",
142
- "schema": {
143
- "type": "object",
144
- "properties": { "localPath": {
145
- "type": "string",
146
- "description": "Workspace directory path"
147
- } },
148
- "required": ["localPath"]
149
- },
150
- "tags": ["workspace", "composite"],
151
- "useCases": [
152
- "project organization",
153
- "multi-source access",
154
- "monorepo navigation"
155
- ]
156
- },
157
- {
158
- "name": "github",
159
- "description": "GitHub repository — issues, pull requests, and workflow runs.\n- Browse, create, comment on, and close issues and PRs\n- Read workflow run status and logs\n- Path structure: `/issues/{number}`, `/pulls/{number}`",
160
- "category": "integration",
161
- "uriTemplate": "github://{owner}/{repo}",
162
- "schema": {
163
- "type": "object",
164
- "properties": {
165
- "owner": {
166
- "type": "string",
167
- "description": "GitHub organization or user"
168
- },
169
- "repo": {
170
- "type": "string",
171
- "description": "Repository name"
172
- },
173
- "token": {
174
- "type": "string",
175
- "description": "GitHub personal access token"
176
- }
177
- },
178
- "required": ["owner", "repo"]
179
- },
180
- "tags": [
181
- "github",
182
- "git",
183
- "integration"
184
- ],
185
- "useCases": [
186
- "issue tracking",
187
- "PR review",
188
- "CI/CD monitoring"
189
- ]
190
- },
191
- {
192
- "name": "http",
193
- "description": "Remote AFS instance — proxy all operations to another AFS server over HTTP.\n- Transparently forwards read, write, search, exec, and all other operations\n- Supports bearer token authentication and configurable retry/timeout",
194
- "category": "integration",
195
- "uriTemplate": "http://{host+}",
196
- "schema": {
197
- "type": "object",
198
- "properties": {
199
- "host": {
200
- "type": "string",
201
- "description": "Server host and path"
202
- },
203
- "token": {
204
- "type": "string",
205
- "description": "Authentication token"
206
- }
207
- },
208
- "required": ["host"]
209
- },
210
- "tags": [
211
- "http",
212
- "remote",
213
- "integration"
214
- ]
215
- },
216
- {
217
- "name": "https",
218
- "description": "Remote AFS instance — proxy all operations to another AFS server over HTTPS.\n- Transparently forwards read, write, search, exec, and all other operations\n- Supports bearer token authentication and configurable retry/timeout",
219
- "category": "integration",
220
- "uriTemplate": "https://{host+}",
221
- "schema": {
222
- "type": "object",
223
- "properties": {
224
- "host": {
225
- "type": "string",
226
- "description": "Server host and path"
227
- },
228
- "token": {
229
- "type": "string",
230
- "description": "Authentication token"
231
- }
232
- },
233
- "required": ["host"]
234
- },
235
- "tags": [
236
- "https",
237
- "remote",
238
- "integration"
239
- ]
240
- },
241
- {
242
- "name": "mcp-stdio",
243
- "description": "MCP (Model Context Protocol) server — access external tools and resources via stdio.\n- Discover and execute tools exposed by the MCP server\n- Browse resources as virtual files, access prompt templates\n- Path structure: `/tools/{name}`, `/resources/{uri}`, `/prompts/{name}`",
244
- "category": "integration",
245
- "uriTemplate": "mcp+stdio://{command+}",
246
- "schema": {
247
- "type": "object",
248
- "properties": {
249
- "command": {
250
- "type": "string",
251
- "description": "Command to run the MCP server"
252
- },
253
- "args": {
254
- "type": "array",
255
- "items": { "type": "string" },
256
- "description": "Command arguments"
257
- },
258
- "env": {
259
- "type": "object",
260
- "description": "Environment variables"
261
- }
262
- },
263
- "required": ["command"]
264
- },
265
- "tags": [
266
- "mcp",
267
- "stdio",
268
- "integration"
269
- ]
270
- },
271
- {
272
- "name": "mcp-http",
273
- "description": "MCP (Model Context Protocol) server — access external tools and resources via HTTP.\n- Discover and execute tools exposed by the MCP server\n- Browse resources as virtual files, access prompt templates\n- Path structure: `/tools/{name}`, `/resources/{uri}`, `/prompts/{name}`",
274
- "category": "integration",
275
- "uriTemplate": "mcp+http://{url+}",
276
- "schema": {
277
- "type": "object",
278
- "properties": { "url": {
279
- "type": "string",
280
- "description": "HTTP URL of the MCP server"
281
- } },
282
- "required": ["url"]
283
- },
284
- "tags": [
285
- "mcp",
286
- "http",
287
- "integration"
288
- ]
289
- },
290
- {
291
- "name": "mcp-sse",
292
- "description": "MCP (Model Context Protocol) server — access external tools and resources via SSE.\n- Discover and execute tools exposed by the MCP server\n- Browse resources as virtual files, access prompt templates\n- Path structure: `/tools/{name}`, `/resources/{uri}`, `/prompts/{name}`",
293
- "category": "integration",
294
- "uriTemplate": "mcp+sse://{url+}",
295
- "schema": {
296
- "type": "object",
297
- "properties": { "url": {
298
- "type": "string",
299
- "description": "SSE URL of the MCP server"
300
- } },
301
- "required": ["url"]
302
- },
303
- "tags": [
304
- "mcp",
305
- "sse",
306
- "integration"
307
- ]
308
- },
309
- {
310
- "name": "s3",
311
- "description": "AWS S3 and S3-compatible object storage (MinIO, R2, B2).\n- Browse, read, write, and delete objects; search by key prefix\n- Exec actions: `presign-download`, `presign-upload`, `multipart-upload`, `select` (SQL on objects)\n- Path structure: `/{key-prefix}/{object-key}`",
312
- "category": "cloud-storage",
313
- "uriTemplate": "s3://{bucket}/{prefix+?}",
314
- "schema": {
315
- "type": "object",
316
- "properties": {
317
- "bucket": {
318
- "type": "string",
319
- "description": "S3 bucket name"
320
- },
321
- "prefix": {
322
- "type": "string",
323
- "description": "Object key prefix"
324
- },
325
- "region": {
326
- "type": "string",
327
- "description": "AWS region"
328
- },
329
- "accessKeyId": {
330
- "type": "string",
331
- "description": "AWS access key ID"
332
- },
333
- "secretAccessKey": {
334
- "type": "string",
335
- "description": "AWS secret access key"
336
- },
337
- "endpoint": {
338
- "type": "string",
339
- "description": "Custom S3 endpoint URL"
340
- },
341
- "profile": {
342
- "type": "string",
343
- "description": "AWS CLI profile name"
344
- }
345
- },
346
- "required": ["bucket"]
347
- },
348
- "tags": [
349
- "aws",
350
- "s3",
351
- "cloud",
352
- "storage"
353
- ],
354
- "useCases": [
355
- "cloud file storage",
356
- "data lake access",
357
- "backup management"
358
- ]
359
- },
360
- {
361
- "name": "gcs",
362
- "description": "Google Cloud Storage bucket.\n- Browse, read, write, and delete objects; search by key prefix\n- Exec actions: `presign-download`, `presign-upload`, `compose` (concatenate objects), `rewrite` (copy with transform)\n- Path structure: `/{key-prefix}/{object-key}`",
363
- "category": "cloud-storage",
364
- "uriTemplate": "gcs://{bucket}/{prefix+?}",
365
- "schema": {
366
- "type": "object",
367
- "properties": {
368
- "bucket": {
369
- "type": "string",
370
- "description": "GCS bucket name"
371
- },
372
- "prefix": {
373
- "type": "string",
374
- "description": "Object key prefix"
375
- },
376
- "projectId": {
377
- "type": "string",
378
- "description": "Google Cloud project ID"
379
- },
380
- "keyFilename": {
381
- "type": "string",
382
- "description": "Path to service account key file"
383
- }
384
- },
385
- "required": ["bucket"]
386
- },
387
- "tags": [
388
- "gcp",
389
- "gcs",
390
- "cloud",
391
- "storage"
392
- ],
393
- "useCases": [
394
- "cloud file storage",
395
- "data lake access",
396
- "ML training data"
397
- ]
398
- },
399
- {
400
- "name": "ec2",
401
- "description": "AWS EC2 compute instances in a region.\n- List and inspect instances, view metadata, tags, and security groups\n- Exec actions: `start`, `stop`, `reboot`, `terminate`, `run-instances`\n- Path structure: `/instances/{instance-id}`, `/by-state/{state}/{instance-id}`",
402
- "category": "cloud-compute",
403
- "uriTemplate": "ec2://{region?}",
404
- "schema": {
405
- "type": "object",
406
- "properties": {
407
- "region": {
408
- "type": "string",
409
- "description": "AWS region"
410
- },
411
- "profile": {
412
- "type": "string",
413
- "description": "AWS CLI profile name"
414
- },
415
- "endpoint": {
416
- "type": "string",
417
- "description": "Custom EC2 endpoint URL"
418
- }
419
- }
420
- },
421
- "tags": [
422
- "aws",
423
- "ec2",
424
- "cloud",
425
- "compute"
426
- ],
427
- "useCases": [
428
- "instance management",
429
- "cloud operations",
430
- "infrastructure monitoring"
431
- ]
432
- },
433
- {
434
- "name": "gce",
435
- "description": "Google Compute Engine VM instances in a project/zone.\n- List and inspect instances, view metadata, labels, and network config\n- Exec actions: `start`, `stop`, `restart`, `delete`\n- Path structure: `/instances/{instance-name}`",
436
- "category": "cloud-compute",
437
- "uriTemplate": "gce://{project}/{zone}",
438
- "schema": {
439
- "type": "object",
440
- "properties": {
441
- "project": {
442
- "type": "string",
443
- "description": "Google Cloud project ID"
444
- },
445
- "zone": {
446
- "type": "string",
447
- "description": "Compute zone"
448
- },
449
- "keyFilename": {
450
- "type": "string",
451
- "description": "Path to service account key file"
452
- }
453
- },
454
- "required": ["project", "zone"]
455
- },
456
- "tags": [
457
- "gcp",
458
- "gce",
459
- "cloud",
460
- "compute"
461
- ],
462
- "useCases": [
463
- "instance management",
464
- "cloud operations",
465
- "infrastructure monitoring"
466
- ]
467
- },
468
- {
469
- "name": "dns",
470
- "description": "DNS zone management — Route53 and Google Cloud DNS.\n- List, create, update, and delete DNS records (A, AAAA, CNAME, MX, TXT, etc.)\n- Audit logging and rate limiting for safe zone modifications\n- Path structure: `/{zone}/{record-name}`",
471
- "category": "cloud-dns",
472
- "uriTemplate": "dns://{zone}",
473
- "schema": {
474
- "type": "object",
475
- "properties": { "zone": {
476
- "type": "string",
477
- "description": "DNS zone name"
478
- } },
479
- "required": ["zone"]
480
- },
481
- "tags": ["dns", "cloud"],
482
- "useCases": ["DNS record management", "domain configuration"]
483
- },
484
- {
485
- "name": "cloudflare",
486
- "description": "Cloudflare account — Workers, KV namespaces, and Pages projects.\n- Manage Workers: create, deploy, rollback, delete\n- Manage KV: create/delete namespaces, read/write keys\n- Manage Pages: create, deploy, rollback, configure bindings",
487
- "category": "cloud-platform",
488
- "uriTemplate": "cloudflare://{accountId}",
489
- "schema": {
490
- "type": "object",
491
- "properties": {
492
- "accountId": {
493
- "type": "string",
494
- "description": "Cloudflare account ID"
495
- },
496
- "apiToken": {
497
- "type": "string",
498
- "description": "Cloudflare API token"
499
- }
500
- }
501
- },
502
- "tags": [
503
- "cloudflare",
504
- "cloud",
505
- "workers",
506
- "kv"
507
- ],
508
- "useCases": [
509
- "edge computing",
510
- "KV storage",
511
- "CDN management"
512
- ]
513
- }
514
- ];
515
-
516
- //#endregion
517
6
  //#region \0@oxc-project+runtime@0.108.0/helpers/decorate.js
518
7
  function __decorate(decorators, target, key, desc) {
519
8
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -538,47 +27,38 @@ function __decorate(decorators, target, key, desc) {
538
27
  * └── .actions/search
539
28
  * ```
540
29
  */
30
+ /** Trim description to first sentence, max 120 chars. */
31
+ function firstSentence(desc) {
32
+ const line = desc.split("\n")[0] ?? desc;
33
+ const sentence = line.split(/\.\s/)[0] ?? line;
34
+ if (sentence.length > 120) return `${sentence.slice(0, 117)}...`;
35
+ return sentence.endsWith(".") ? sentence : `${sentence}.`;
36
+ }
541
37
  var AFSRegistry = class extends AFSBaseProvider {
542
38
  name = "registry";
543
39
  description = "AFS Provider Registry — discover and install available providers.\n- Browse providers by name, category, or tag\n- Read provider manifests, README docs, and configuration schemas\n- Search providers by keyword (name, description, tags, use cases)\n- Exec actions: `search` (full-text query), `mount` (per-provider, install to AFS)\n- Path structure: `/providers/{name}/manifest.json`, `/by-category/{cat}`, `/by-tag/{tag}`";
544
40
  accessMode = "readwrite";
545
41
  providers;
546
- remoteUrl;
547
- fetchPromise;
42
+ scanPromise;
548
43
  loadProviderFn;
549
44
  constructor(options = {}) {
550
45
  super();
551
46
  if (options.providers && options.providers.length > 0) this.providers = JSON.parse(JSON.stringify(options.providers));
552
47
  else {
553
- this.providers = JSON.parse(JSON.stringify(providers));
554
- this.remoteUrl = options.url;
555
- if (this.remoteUrl) this.fetchPromise = this.fetchRemoteProviders(this.remoteUrl);
48
+ this.providers = [];
49
+ this.scanPromise = this.loadFromScan();
556
50
  }
557
51
  }
558
- async fetchRemoteProviders(url) {
52
+ async loadFromScan() {
559
53
  try {
560
- const response = await fetch(url);
561
- if (!response.ok) return;
562
- const data = await response.json();
563
- if (Array.isArray(data?.providers) && data.providers.length > 0) this.providers = data.providers.map((p) => {
564
- const entry = { ...p };
565
- if (!entry.uriTemplate && entry.uri) {
566
- entry.uriTemplate = entry.uri;
567
- delete entry.uri;
568
- }
569
- if (!entry.useCases && entry.use_cases) {
570
- entry.useCases = entry.use_cases;
571
- delete entry.use_cases;
572
- }
573
- delete entry.capabilities_summary;
574
- return entry;
575
- });
54
+ const { scanInstalledProviders: scanInstalledProviders$1 } = await import("./scanner-LVTipem0.mjs");
55
+ this.providers = await scanInstalledProviders$1();
576
56
  } catch {}
577
57
  }
578
58
  async ensureLoaded() {
579
- if (this.fetchPromise) {
580
- await this.fetchPromise;
581
- this.fetchPromise = void 0;
59
+ if (this.scanPromise) {
60
+ await this.scanPromise;
61
+ this.scanPromise = void 0;
582
62
  }
583
63
  }
584
64
  onMount(root) {
@@ -619,16 +99,36 @@ var AFSRegistry = class extends AFSBaseProvider {
619
99
  }
620
100
  buildByTag() {
621
101
  const map = /* @__PURE__ */ new Map();
622
- for (const p of this.providers) for (const tag of p.tags || []) {
623
- const list = map.get(tag) || [];
624
- list.push(p);
625
- map.set(tag, list);
102
+ for (const p of this.providers) {
103
+ const tags = p.capabilityTags?.length ? p.capabilityTags : p.tags || [];
104
+ for (const tag of tags) {
105
+ const list = map.get(tag) || [];
106
+ list.push(p);
107
+ map.set(tag, list);
108
+ }
626
109
  }
627
110
  return map;
628
111
  }
629
112
  findProvider(name) {
630
113
  return this.providers.find((p) => p.name === name);
631
114
  }
115
+ getRecipes() {
116
+ return this.providers.filter((p) => p.type === "recipe");
117
+ }
118
+ findRecipe(name) {
119
+ return this.getRecipes().find((p) => p.name === name);
120
+ }
121
+ /** Load AGENT.md content: inline first, then lazy-read from disk. */
122
+ loadAgentMd(provider) {
123
+ if (provider.agentMd) return provider.agentMd;
124
+ if (!provider.agentMdPath) return void 0;
125
+ try {
126
+ const { readFileSync } = __require("node:fs");
127
+ return readFileSync(provider.agentMdPath, "utf-8");
128
+ } catch {
129
+ return;
130
+ }
131
+ }
632
132
  async listRoot(_ctx) {
633
133
  return { data: [
634
134
  {
@@ -654,25 +154,55 @@ var AFSRegistry = class extends AFSBaseProvider {
654
154
  kind: "afs:directory",
655
155
  childrenCount: this.buildByTag().size
656
156
  }
157
+ },
158
+ {
159
+ id: "recipes",
160
+ path: "/recipes",
161
+ meta: {
162
+ kind: "afs:directory",
163
+ childrenCount: this.getRecipes().length
164
+ }
165
+ },
166
+ {
167
+ id: ".vocabulary",
168
+ path: "/.vocabulary",
169
+ meta: {
170
+ kind: "afs:directory",
171
+ childrenCount: 2
172
+ }
657
173
  }
658
174
  ] };
659
175
  }
660
176
  async listProviders(_ctx) {
661
- return { data: this.providers.map((p) => ({
662
- id: p.name,
663
- path: joinURL("/providers", p.name),
664
- summary: p.description,
665
- meta: {
666
- kind: "registry:provider",
667
- childrenCount: 3
177
+ return { data: this.providers.map((p) => {
178
+ let childrenCount = 3;
179
+ if (p.agentMd || p.agentMdPath) childrenCount++;
180
+ if (p.treeSchema) childrenCount++;
181
+ const meta = {
182
+ kind: p.type === "recipe" ? "registry:recipe" : "registry:provider",
183
+ childrenCount,
184
+ category: p.category,
185
+ capabilityTags: p.capabilityTags,
186
+ type: p.type || "provider"
187
+ };
188
+ if (p.treeSchema && typeof p.treeSchema === "object" && "operations" in p.treeSchema) meta.operations = p.treeSchema.operations;
189
+ if (p.treeSchema && typeof p.treeSchema === "object" && "auth" in p.treeSchema) {
190
+ const auth = p.treeSchema.auth;
191
+ if (auth?.type) meta.authType = auth.type;
668
192
  }
669
- })) };
193
+ return {
194
+ id: p.name,
195
+ path: joinURL("/providers", p.name),
196
+ summary: firstSentence(p.description),
197
+ meta
198
+ };
199
+ }) };
670
200
  }
671
201
  async listProviderContents(ctx) {
672
202
  const provider = this.findProvider(ctx.params.name);
673
203
  if (!provider) throw new AFSNotFoundError(ctx.path);
674
204
  const base = joinURL("/providers", provider.name);
675
- return { data: [{
205
+ const entries = [{
676
206
  id: "manifest.json",
677
207
  path: joinURL(base, "manifest.json"),
678
208
  meta: { kind: "afs:file" }
@@ -680,38 +210,52 @@ var AFSRegistry = class extends AFSBaseProvider {
680
210
  id: "README.md",
681
211
  path: joinURL(base, "README.md"),
682
212
  meta: { kind: "afs:file" }
683
- }] };
213
+ }];
214
+ if (provider.treeSchema) entries.push({
215
+ id: "tree-schema.json",
216
+ path: joinURL(base, "tree-schema.json"),
217
+ meta: { kind: "afs:file" }
218
+ });
219
+ if (provider.agentMd || provider.agentMdPath) entries.push({
220
+ id: "AGENT.md",
221
+ path: joinURL(base, "AGENT.md"),
222
+ meta: { kind: "afs:file" }
223
+ });
224
+ return { data: entries };
684
225
  }
685
226
  async listCategories(_ctx) {
686
- return { data: Array.from(this.buildByCategory().entries()).map(([cat, providers$1]) => ({
227
+ return { data: Array.from(this.buildByCategory().entries()).map(([cat, providers]) => ({
687
228
  id: cat,
688
229
  path: joinURL("/by-category", cat),
689
230
  meta: {
690
231
  kind: "afs:directory",
691
- childrenCount: providers$1.length
232
+ childrenCount: providers.length
692
233
  }
693
234
  })) };
694
235
  }
695
236
  async listCategoryProviders(ctx) {
696
- const providers$1 = this.buildByCategory().get(ctx.params.category);
697
- if (!providers$1) throw new AFSNotFoundError(ctx.path);
698
- return { data: providers$1.map((p) => ({
237
+ const providers = this.buildByCategory().get(ctx.params.category);
238
+ if (!providers) throw new AFSNotFoundError(ctx.path);
239
+ return { data: providers.map((p) => ({
699
240
  id: p.name,
700
241
  path: joinURL("/by-category", ctx.params.category, p.name),
701
242
  summary: p.description,
702
243
  meta: {
703
- kind: "registry:provider",
704
- childrenCount: 2
244
+ kind: p.type === "recipe" ? "registry:recipe" : "registry:provider",
245
+ childrenCount: 2,
246
+ category: p.category,
247
+ tags: p.tags,
248
+ type: p.type || "provider"
705
249
  }
706
250
  })) };
707
251
  }
708
252
  async listTags(_ctx) {
709
- return { data: Array.from(this.buildByTag().entries()).map(([tag, providers$1]) => ({
253
+ return { data: Array.from(this.buildByTag().entries()).map(([tag, providers]) => ({
710
254
  id: tag,
711
255
  path: joinURL("/by-tag", tag),
712
256
  meta: {
713
257
  kind: "afs:directory",
714
- childrenCount: providers$1.length
258
+ childrenCount: providers.length
715
259
  }
716
260
  })) };
717
261
  }
@@ -720,13 +264,18 @@ var AFSRegistry = class extends AFSBaseProvider {
720
264
  return { data: [] };
721
265
  }
722
266
  async listTagProviders(ctx) {
723
- const providers$1 = this.buildByTag().get(ctx.params.tag);
724
- if (!providers$1) throw new AFSNotFoundError(ctx.path);
725
- return { data: providers$1.map((p) => ({
267
+ const providers = this.buildByTag().get(ctx.params.tag);
268
+ if (!providers) throw new AFSNotFoundError(ctx.path);
269
+ return { data: providers.map((p) => ({
726
270
  id: p.name,
727
271
  path: joinURL("/by-tag", ctx.params.tag, p.name),
728
272
  summary: p.description,
729
- meta: { kind: "registry:provider" }
273
+ meta: {
274
+ kind: p.type === "recipe" ? "registry:recipe" : "registry:provider",
275
+ category: p.category,
276
+ tags: p.tags,
277
+ type: p.type || "provider"
278
+ }
730
279
  })) };
731
280
  }
732
281
  async listTagProviderChildren(ctx) {
@@ -737,6 +286,35 @@ var AFSRegistry = class extends AFSBaseProvider {
737
286
  if (!this.findProvider(ctx.params.name)) throw new AFSNotFoundError(ctx.path);
738
287
  return { data: [] };
739
288
  }
289
+ async listRecipes(_ctx) {
290
+ return { data: this.getRecipes().map((r) => ({
291
+ id: r.name,
292
+ path: joinURL("/recipes", r.name),
293
+ summary: r.description,
294
+ meta: {
295
+ kind: "registry:recipe",
296
+ childrenCount: 2
297
+ }
298
+ })) };
299
+ }
300
+ async listRecipeContents(ctx) {
301
+ const recipe = this.findRecipe(ctx.params.name);
302
+ if (!recipe) throw new AFSNotFoundError(ctx.path);
303
+ const base = joinURL("/recipes", recipe.name);
304
+ return { data: [{
305
+ id: "manifest.json",
306
+ path: joinURL(base, "manifest.json"),
307
+ meta: { kind: "afs:file" }
308
+ }, {
309
+ id: "README.md",
310
+ path: joinURL(base, "README.md"),
311
+ meta: { kind: "afs:file" }
312
+ }] };
313
+ }
314
+ async listRecipeFileNode(ctx) {
315
+ if (!this.findRecipe(ctx.params.name)) throw new AFSNotFoundError(ctx.path);
316
+ return { data: [] };
317
+ }
740
318
  async readRoot(_ctx) {
741
319
  return {
742
320
  id: "/",
@@ -747,7 +325,7 @@ var AFSRegistry = class extends AFSBaseProvider {
747
325
  },
748
326
  meta: {
749
327
  kind: "registry:root",
750
- childrenCount: 3
328
+ childrenCount: 5
751
329
  }
752
330
  };
753
331
  }
@@ -824,6 +402,29 @@ var AFSRegistry = class extends AFSBaseProvider {
824
402
  meta: { kind: "afs:file" }
825
403
  };
826
404
  }
405
+ async readTreeSchema(ctx) {
406
+ const provider = this.findProvider(ctx.params.name);
407
+ if (!provider) throw new AFSNotFoundError(ctx.path);
408
+ if (!provider.treeSchema) throw new AFSNotFoundError(ctx.path);
409
+ return {
410
+ id: "tree-schema.json",
411
+ path: ctx.path,
412
+ content: provider.treeSchema,
413
+ meta: { kind: "afs:file" }
414
+ };
415
+ }
416
+ async readAgentMd(ctx) {
417
+ const provider = this.findProvider(ctx.params.name);
418
+ if (!provider) throw new AFSNotFoundError(ctx.path);
419
+ const content = this.loadAgentMd(provider);
420
+ if (!content) throw new AFSNotFoundError(ctx.path);
421
+ return {
422
+ id: "AGENT.md",
423
+ path: ctx.path,
424
+ content,
425
+ meta: { kind: "afs:file" }
426
+ };
427
+ }
827
428
  async readByCategory(_ctx) {
828
429
  return {
829
430
  id: "by-category",
@@ -839,18 +440,18 @@ var AFSRegistry = class extends AFSBaseProvider {
839
440
  };
840
441
  }
841
442
  async readCategory(ctx) {
842
- const providers$1 = this.buildByCategory().get(ctx.params.category);
843
- if (!providers$1) throw new AFSNotFoundError(ctx.path);
443
+ const providers = this.buildByCategory().get(ctx.params.category);
444
+ if (!providers) throw new AFSNotFoundError(ctx.path);
844
445
  return {
845
446
  id: ctx.params.category,
846
447
  path: joinURL("/by-category", ctx.params.category),
847
448
  content: {
848
449
  type: "category",
849
- count: providers$1.length
450
+ count: providers.length
850
451
  },
851
452
  meta: {
852
453
  kind: "afs:directory",
853
- childrenCount: providers$1.length
454
+ childrenCount: providers.length
854
455
  }
855
456
  };
856
457
  }
@@ -883,18 +484,18 @@ var AFSRegistry = class extends AFSBaseProvider {
883
484
  };
884
485
  }
885
486
  async readTag(ctx) {
886
- const providers$1 = this.buildByTag().get(ctx.params.tag);
887
- if (!providers$1) throw new AFSNotFoundError(ctx.path);
487
+ const providers = this.buildByTag().get(ctx.params.tag);
488
+ if (!providers) throw new AFSNotFoundError(ctx.path);
888
489
  return {
889
490
  id: ctx.params.tag,
890
491
  path: joinURL("/by-tag", ctx.params.tag),
891
492
  content: {
892
493
  type: "tag",
893
- count: providers$1.length
494
+ count: providers.length
894
495
  },
895
496
  meta: {
896
497
  kind: "afs:directory",
897
- childrenCount: providers$1.length
498
+ childrenCount: providers.length
898
499
  }
899
500
  };
900
501
  }
@@ -912,6 +513,68 @@ var AFSRegistry = class extends AFSBaseProvider {
912
513
  meta: { kind: "registry:provider" }
913
514
  };
914
515
  }
516
+ async readRecipes(_ctx) {
517
+ const recipes = this.getRecipes();
518
+ return {
519
+ id: "recipes",
520
+ path: "/recipes",
521
+ content: {
522
+ type: "directory",
523
+ count: recipes.length
524
+ },
525
+ meta: {
526
+ kind: "afs:directory",
527
+ childrenCount: recipes.length
528
+ }
529
+ };
530
+ }
531
+ async readRecipe(ctx) {
532
+ const recipe = this.findRecipe(ctx.params.name);
533
+ if (!recipe) throw new AFSNotFoundError(ctx.path);
534
+ return {
535
+ id: recipe.name,
536
+ path: joinURL("/recipes", recipe.name),
537
+ content: {
538
+ type: "recipe",
539
+ name: recipe.name,
540
+ description: recipe.description
541
+ },
542
+ meta: {
543
+ kind: "registry:recipe",
544
+ childrenCount: 2
545
+ }
546
+ };
547
+ }
548
+ async readRecipeManifest(ctx) {
549
+ const recipe = this.findRecipe(ctx.params.name);
550
+ if (!recipe) throw new AFSNotFoundError(ctx.path);
551
+ return {
552
+ id: "manifest.json",
553
+ path: ctx.path,
554
+ content: { ...recipe },
555
+ meta: { kind: "afs:file" }
556
+ };
557
+ }
558
+ async readRecipeReadme(ctx) {
559
+ const recipe = this.findRecipe(ctx.params.name);
560
+ if (!recipe) throw new AFSNotFoundError(ctx.path);
561
+ const lines = [
562
+ `# ${recipe.name}`,
563
+ "",
564
+ recipe.description,
565
+ "",
566
+ `**Category:** ${recipe.category}`,
567
+ `**URI:** \`${recipe.uriTemplate}\``
568
+ ];
569
+ if (recipe.tags?.length) lines.push("", `**Tags:** ${recipe.tags.join(", ")}`);
570
+ lines.push("");
571
+ return {
572
+ id: "README.md",
573
+ path: ctx.path,
574
+ content: lines.join("\n"),
575
+ meta: { kind: "afs:file" }
576
+ };
577
+ }
915
578
  async readCapabilities(_ctx) {
916
579
  return {
917
580
  id: "/.meta/.capabilities",
@@ -929,6 +592,53 @@ var AFSRegistry = class extends AFSBaseProvider {
929
592
  meta: { kind: "afs:capabilities" }
930
593
  };
931
594
  }
595
+ async listVocabularyLeaf(_ctx) {
596
+ return { data: [] };
597
+ }
598
+ async listVocabulary(_ctx) {
599
+ return { data: [{
600
+ id: "categories",
601
+ path: "/.vocabulary/categories",
602
+ meta: {
603
+ kind: "registry:vocabulary",
604
+ childrenCount: AFS_CATEGORIES.length
605
+ }
606
+ }, {
607
+ id: "tags",
608
+ path: "/.vocabulary/tags",
609
+ meta: {
610
+ kind: "registry:vocabulary",
611
+ childrenCount: CAPABILITY_TAGS.length
612
+ }
613
+ }] };
614
+ }
615
+ async readVocabulary(_ctx) {
616
+ return {
617
+ id: ".vocabulary",
618
+ path: "/.vocabulary",
619
+ content: "# AFS Controlled Vocabulary\n\n- categories — provider classification (single-select)\n- tags — capability tags (multi-select)\n",
620
+ meta: {
621
+ kind: "afs:directory",
622
+ childrenCount: 2
623
+ }
624
+ };
625
+ }
626
+ async readVocabularyCategories(_ctx) {
627
+ return {
628
+ id: "categories",
629
+ path: "/.vocabulary/categories",
630
+ content: `# AFS Provider Categories\n\nControlled vocabulary — single-select per provider.\n\n${AFS_CATEGORIES.map((c) => `- ${c}`).join("\n")}\n`,
631
+ meta: { kind: "registry:vocabulary" }
632
+ };
633
+ }
634
+ async readVocabularyTags(_ctx) {
635
+ return {
636
+ id: "tags",
637
+ path: "/.vocabulary/tags",
638
+ content: `# AFS Capability Tags\n\nControlled vocabulary — multi-select per provider.\n\n${CAPABILITY_TAGS.map((t) => `- ${t}`).join("\n")}\n`,
639
+ meta: { kind: "registry:vocabulary" }
640
+ };
641
+ }
932
642
  async rootMeta(_ctx) {
933
643
  return {
934
644
  id: ".meta",
@@ -940,10 +650,58 @@ var AFSRegistry = class extends AFSBaseProvider {
940
650
  },
941
651
  meta: {
942
652
  kind: "registry:root",
943
- childrenCount: 3
653
+ childrenCount: 5
654
+ }
655
+ };
656
+ }
657
+ async recipesMeta(_ctx) {
658
+ const recipes = this.getRecipes();
659
+ return {
660
+ id: ".meta",
661
+ path: "/recipes/.meta",
662
+ content: { count: recipes.length },
663
+ meta: {
664
+ kind: "afs:directory",
665
+ childrenCount: recipes.length
944
666
  }
945
667
  };
946
668
  }
669
+ async recipeMeta(ctx) {
670
+ const recipe = this.findRecipe(ctx.params.name);
671
+ if (!recipe) throw new AFSNotFoundError(ctx.path);
672
+ return {
673
+ id: ".meta",
674
+ path: joinURL("/recipes", recipe.name, ".meta"),
675
+ content: {
676
+ name: recipe.name,
677
+ category: recipe.category
678
+ },
679
+ meta: {
680
+ kind: "registry:recipe",
681
+ childrenCount: 2
682
+ }
683
+ };
684
+ }
685
+ async recipeManifestMeta(ctx) {
686
+ const recipe = this.findRecipe(ctx.params.name);
687
+ if (!recipe) throw new AFSNotFoundError(ctx.path);
688
+ return {
689
+ id: ".meta",
690
+ path: joinURL("/recipes", recipe.name, "manifest.json", ".meta"),
691
+ content: { format: "json" },
692
+ meta: { kind: "afs:file" }
693
+ };
694
+ }
695
+ async recipeReadmeMeta(ctx) {
696
+ const recipe = this.findRecipe(ctx.params.name);
697
+ if (!recipe) throw new AFSNotFoundError(ctx.path);
698
+ return {
699
+ id: ".meta",
700
+ path: joinURL("/recipes", recipe.name, "README.md", ".meta"),
701
+ content: { format: "markdown" },
702
+ meta: { kind: "afs:file" }
703
+ };
704
+ }
947
705
  async providersMeta(_ctx) {
948
706
  return {
949
707
  id: ".meta",
@@ -992,6 +750,26 @@ var AFSRegistry = class extends AFSBaseProvider {
992
750
  meta: { kind: "afs:file" }
993
751
  };
994
752
  }
753
+ async treeSchemaJsonMeta(ctx) {
754
+ const provider = this.findProvider(ctx.params.name);
755
+ if (!provider) throw new AFSNotFoundError(ctx.path);
756
+ return {
757
+ id: ".meta",
758
+ path: joinURL("/providers", provider.name, "tree-schema.json", ".meta"),
759
+ content: { format: "json" },
760
+ meta: { kind: "afs:file" }
761
+ };
762
+ }
763
+ async agentMdMeta(ctx) {
764
+ const provider = this.findProvider(ctx.params.name);
765
+ if (!provider) throw new AFSNotFoundError(ctx.path);
766
+ return {
767
+ id: ".meta",
768
+ path: joinURL("/providers", provider.name, "AGENT.md", ".meta"),
769
+ content: { format: "markdown" },
770
+ meta: { kind: "afs:file" }
771
+ };
772
+ }
995
773
  async byCategoryMeta(_ctx) {
996
774
  return {
997
775
  id: ".meta",
@@ -1004,18 +782,18 @@ var AFSRegistry = class extends AFSBaseProvider {
1004
782
  };
1005
783
  }
1006
784
  async categoryMeta(ctx) {
1007
- const providers$1 = this.buildByCategory().get(ctx.params.category);
1008
- if (!providers$1) throw new AFSNotFoundError(ctx.path);
785
+ const providers = this.buildByCategory().get(ctx.params.category);
786
+ if (!providers) throw new AFSNotFoundError(ctx.path);
1009
787
  return {
1010
788
  id: ".meta",
1011
789
  path: joinURL("/by-category", ctx.params.category, ".meta"),
1012
790
  content: {
1013
791
  category: ctx.params.category,
1014
- count: providers$1.length
792
+ count: providers.length
1015
793
  },
1016
794
  meta: {
1017
795
  kind: "afs:directory",
1018
- childrenCount: providers$1.length
796
+ childrenCount: providers.length
1019
797
  }
1020
798
  };
1021
799
  }
@@ -1041,18 +819,18 @@ var AFSRegistry = class extends AFSBaseProvider {
1041
819
  };
1042
820
  }
1043
821
  async tagMeta(ctx) {
1044
- const providers$1 = this.buildByTag().get(ctx.params.tag);
1045
- if (!providers$1) throw new AFSNotFoundError(ctx.path);
822
+ const providers = this.buildByTag().get(ctx.params.tag);
823
+ if (!providers) throw new AFSNotFoundError(ctx.path);
1046
824
  return {
1047
825
  id: ".meta",
1048
826
  path: joinURL("/by-tag", ctx.params.tag, ".meta"),
1049
827
  content: {
1050
828
  tag: ctx.params.tag,
1051
- count: providers$1.length
829
+ count: providers.length
1052
830
  },
1053
831
  meta: {
1054
832
  kind: "afs:directory",
1055
- childrenCount: providers$1.length
833
+ childrenCount: providers.length
1056
834
  }
1057
835
  };
1058
836
  }
@@ -1066,6 +844,30 @@ var AFSRegistry = class extends AFSBaseProvider {
1066
844
  meta: { kind: "registry:provider" }
1067
845
  };
1068
846
  }
847
+ async vocabularyMeta(_ctx) {
848
+ return {
849
+ id: ".meta",
850
+ path: "/.vocabulary/.meta",
851
+ content: { childrenCount: 2 },
852
+ meta: { kind: "afs:directory" }
853
+ };
854
+ }
855
+ async vocabularyCategoriesMeta(_ctx) {
856
+ return {
857
+ id: ".meta",
858
+ path: "/.vocabulary/categories/.meta",
859
+ content: { count: AFS_CATEGORIES.length },
860
+ meta: { kind: "registry:vocabulary" }
861
+ };
862
+ }
863
+ async vocabularyTagsMeta(_ctx) {
864
+ return {
865
+ id: ".meta",
866
+ path: "/.vocabulary/tags/.meta",
867
+ content: { count: CAPABILITY_TAGS.length },
868
+ meta: { kind: "registry:vocabulary" }
869
+ };
870
+ }
1069
871
  async listRootActions(_ctx) {
1070
872
  return { data: [{
1071
873
  id: "search",
@@ -1076,23 +878,64 @@ var AFSRegistry = class extends AFSBaseProvider {
1076
878
  },
1077
879
  actions: [{
1078
880
  name: "search",
1079
- description: "Search for providers matching a query",
881
+ description: "Search for providers. Use category/tags for structured filtering, query for full-text search.",
1080
882
  inputSchema: {
1081
883
  type: "object",
1082
- properties: { query: {
1083
- type: "string",
1084
- description: "Search query"
1085
- } }
884
+ properties: {
885
+ query: {
886
+ type: "string",
887
+ description: "Full-text search across name, description, tags"
888
+ },
889
+ category: {
890
+ type: "string",
891
+ description: "Exact category match (e.g. storage, database, messaging)"
892
+ },
893
+ tags: {
894
+ type: "array",
895
+ items: { type: "string" },
896
+ description: "Filter by tags — providers must have ALL specified tags"
897
+ },
898
+ type: {
899
+ type: "string",
900
+ enum: ["provider", "recipe"],
901
+ description: "Filter by entry type"
902
+ }
903
+ }
1086
904
  }
1087
905
  }]
1088
906
  }] };
1089
907
  }
1090
908
  async searchProviders(_ctx, args) {
909
+ const query = args.query || "";
910
+ const filterCategory = args.category;
911
+ const filterTags = args.tags;
912
+ const filterType = args.type;
913
+ let matched = [...this.providers];
914
+ if (filterCategory) matched = matched.filter((p) => p.category === filterCategory);
915
+ if (filterTags && filterTags.length > 0) matched = matched.filter((p) => {
916
+ const caps = p.capabilityTags || [];
917
+ return filterTags.every((t) => caps.includes(t));
918
+ });
919
+ if (filterType) matched = matched.filter((p) => (p.type || "provider") === filterType);
920
+ if (query) {
921
+ const q = query.toLowerCase();
922
+ matched = matched.filter((p) => p.name.toLowerCase().includes(q) || p.description.toLowerCase().includes(q) || p.category.toLowerCase().includes(q) || (p.tags || []).some((t) => t.toLowerCase().includes(q)) || (p.capabilityTags || []).some((t) => t.toLowerCase().includes(q)) || (p.useCases || []).some((u) => u.toLowerCase().includes(q)));
923
+ }
1091
924
  return {
1092
925
  success: true,
1093
926
  data: {
1094
- query: args.query || "",
1095
- providers: this.providers.map((p) => ({ ...p }))
927
+ query,
928
+ ...filterCategory ? { category: filterCategory } : {},
929
+ ...filterTags ? { tags: filterTags } : {},
930
+ ...filterType ? { type: filterType } : {},
931
+ count: matched.length,
932
+ providers: matched.map((p) => ({
933
+ name: p.name,
934
+ category: p.category,
935
+ capabilityTags: p.capabilityTags,
936
+ type: p.type || "provider",
937
+ description: firstSentence(p.description)
938
+ }))
1096
939
  }
1097
940
  };
1098
941
  }
@@ -1105,7 +948,7 @@ var AFSRegistry = class extends AFSBaseProvider {
1105
948
  properties: {
1106
949
  path: {
1107
950
  type: "string",
1108
- description: "Mount path"
951
+ description: "Mount path (default: /{provider-name})"
1109
952
  },
1110
953
  uri: {
1111
954
  type: "string",
@@ -1140,7 +983,96 @@ var AFSRegistry = class extends AFSBaseProvider {
1140
983
  },
1141
984
  ...provider.schema?.properties ?? {}
1142
985
  },
1143
- required: ["path", ...provider.schema?.required ?? []]
986
+ required: [...provider.schema?.required ?? []]
987
+ };
988
+ }
989
+ async listRecipeActions(ctx) {
990
+ const recipe = this.findRecipe(ctx.params.name);
991
+ if (!recipe) throw new AFSNotFoundError(ctx.path);
992
+ const mountSchema = {
993
+ type: "object",
994
+ properties: {
995
+ path: {
996
+ type: "string",
997
+ description: `Mount path (default: /modules/${recipe.name})`
998
+ },
999
+ uri: {
1000
+ type: "string",
1001
+ description: `Full MCP URI (default: ${recipe.uriTemplate})`
1002
+ },
1003
+ accessMode: {
1004
+ type: "string",
1005
+ enum: ["readonly", "readwrite"],
1006
+ description: "Access mode (default: readonly)"
1007
+ },
1008
+ description: {
1009
+ type: "string",
1010
+ description: "Human-readable description"
1011
+ }
1012
+ }
1013
+ };
1014
+ return { data: [{
1015
+ id: "mount",
1016
+ path: joinURL("/recipes", recipe.name, ".actions", "mount"),
1017
+ meta: {
1018
+ kind: "afs:executable",
1019
+ kinds: ["afs:executable", "afs:node"],
1020
+ description: `Mount the ${recipe.name} recipe`,
1021
+ inputSchema: mountSchema
1022
+ },
1023
+ actions: [{
1024
+ name: "mount",
1025
+ description: `Mount the ${recipe.name} recipe`,
1026
+ inputSchema: mountSchema
1027
+ }]
1028
+ }] };
1029
+ }
1030
+ async readRecipeMountAction(ctx) {
1031
+ const recipe = this.findRecipe(ctx.params.name);
1032
+ if (!recipe) throw new AFSNotFoundError(ctx.path);
1033
+ const mountSchema = {
1034
+ type: "object",
1035
+ properties: {
1036
+ path: {
1037
+ type: "string",
1038
+ description: `Mount path (default: /modules/${recipe.name})`
1039
+ },
1040
+ uri: {
1041
+ type: "string",
1042
+ description: `Full MCP URI (default: ${recipe.uriTemplate})`
1043
+ }
1044
+ }
1045
+ };
1046
+ return {
1047
+ id: "mount",
1048
+ path: joinURL("/recipes", recipe.name, ".actions", "mount"),
1049
+ content: {
1050
+ description: `Mount the ${recipe.name} recipe`,
1051
+ inputSchema: mountSchema
1052
+ },
1053
+ meta: {
1054
+ kind: "afs:executable",
1055
+ kinds: ["afs:executable", "afs:node"],
1056
+ description: `Mount the ${recipe.name} recipe`,
1057
+ inputSchema: mountSchema
1058
+ }
1059
+ };
1060
+ }
1061
+ async mountRecipe(ctx, args) {
1062
+ const recipe = this.findRecipe(ctx.params.name);
1063
+ if (!recipe) throw new AFSNotFoundError(ctx.path);
1064
+ if (!this.loadProviderFn) throw new Error("loadProvider not configured — Registry was not mounted on an AFS instance");
1065
+ const mountPath = typeof args.path === "string" && args.path.trim() !== "" ? args.path.trim() : `/modules/${recipe.name}`;
1066
+ const uri = typeof args.uri === "string" && args.uri.trim() !== "" ? args.uri.trim() : recipe.uriTemplate;
1067
+ const { path: _path, uri: _uri, ...options } = args;
1068
+ await this.loadProviderFn(uri, mountPath, Object.keys(options).length > 0 ? options : void 0);
1069
+ return {
1070
+ success: true,
1071
+ data: {
1072
+ recipe: recipe.name,
1073
+ uri,
1074
+ path: mountPath
1075
+ }
1144
1076
  };
1145
1077
  }
1146
1078
  async listProviderActions(ctx) {
@@ -1152,7 +1084,9 @@ var AFSRegistry = class extends AFSBaseProvider {
1152
1084
  path: joinURL("/providers", provider.name, ".actions", "mount"),
1153
1085
  meta: {
1154
1086
  kind: "afs:executable",
1155
- kinds: ["afs:executable", "afs:node"]
1087
+ kinds: ["afs:executable", "afs:node"],
1088
+ description: `Mount the ${provider.name} provider`,
1089
+ inputSchema: mountSchema
1156
1090
  },
1157
1091
  actions: [{
1158
1092
  name: "mount",
@@ -1184,8 +1118,7 @@ var AFSRegistry = class extends AFSBaseProvider {
1184
1118
  const provider = this.findProvider(ctx.params.name);
1185
1119
  if (!provider) throw new AFSNotFoundError(ctx.path);
1186
1120
  if (!this.loadProviderFn) throw new Error("loadProvider not configured — Registry was not mounted on an AFS instance");
1187
- if (typeof args.path !== "string" || args.path.trim() === "") throw new AFSValidationError("Input validation failed: path: must be a non-empty string");
1188
- const mountPath = args.path;
1121
+ const mountPath = typeof args.path === "string" && args.path.trim() !== "" ? args.path.trim() : `/${provider.name}`;
1189
1122
  let uri = args.uri;
1190
1123
  if (!uri) {
1191
1124
  const varNames = getTemplateVariableNames(provider.uriTemplate);
@@ -1214,32 +1147,119 @@ var AFSRegistry = class extends AFSBaseProvider {
1214
1147
  }
1215
1148
  async searchRoot(_ctx, query, options) {
1216
1149
  const q = query.toLowerCase();
1217
- let matched = this.providers.filter((p) => p.name.toLowerCase().includes(q) || p.description.toLowerCase().includes(q) || p.category.toLowerCase().includes(q) || p.uriTemplate.toLowerCase().includes(q) || (p.tags || []).some((t) => t.toLowerCase().includes(q)) || (p.useCases || []).some((u) => u.toLowerCase().includes(q)));
1150
+ let matched = this.providers.filter((p) => p.name.toLowerCase().includes(q) || p.description.toLowerCase().includes(q) || p.category.toLowerCase().includes(q) || p.uriTemplate.toLowerCase().includes(q) || (p.tags || []).some((t) => t.toLowerCase().includes(q)) || (p.capabilityTags || []).some((t) => t.toLowerCase().includes(q)) || (p.useCases || []).some((u) => u.toLowerCase().includes(q)));
1218
1151
  if (options?.limit !== void 0 && matched.length > options.limit) matched = matched.slice(0, options.limit);
1219
1152
  return { data: matched.map((p) => ({
1220
1153
  id: p.name,
1221
1154
  path: joinURL("/providers", p.name),
1222
- summary: p.description,
1223
- meta: { kind: "registry:provider" }
1155
+ summary: firstSentence(p.description),
1156
+ meta: {
1157
+ kind: "registry:provider",
1158
+ category: p.category,
1159
+ capabilityTags: p.capabilityTags
1160
+ }
1224
1161
  })) };
1225
1162
  }
1226
- async explainPath(_ctx) {
1163
+ async explainProvider(ctx) {
1164
+ const provider = this.findProvider(ctx.params.name);
1165
+ if (!provider) throw new AFSNotFoundError(ctx.path);
1166
+ const agentMd = this.loadAgentMd(provider);
1167
+ if (agentMd) {
1168
+ const lines$1 = [agentMd];
1169
+ if (provider.schema) {
1170
+ const props = provider.schema.properties;
1171
+ const required = new Set(provider.schema.required ?? []);
1172
+ if (props) {
1173
+ lines$1.push("", "## Configuration", "");
1174
+ for (const [key, val] of Object.entries(props)) {
1175
+ const req = required.has(key) ? " **(required)**" : "";
1176
+ lines$1.push(`- \`${key}\` (${val.type ?? "string"})${req} — ${val.description ?? ""}`);
1177
+ }
1178
+ }
1179
+ }
1180
+ lines$1.push("", "## Actions", "", "- `mount` — Mount this provider into AFS");
1181
+ return {
1182
+ format: "markdown",
1183
+ content: lines$1.join("\n")
1184
+ };
1185
+ }
1186
+ const lines = [
1187
+ `# ${provider.name}`,
1188
+ "",
1189
+ provider.description,
1190
+ "",
1191
+ `**Category:** ${provider.category}`,
1192
+ `**URI Template:** \`${provider.uriTemplate}\``,
1193
+ `**Type:** ${provider.type || "provider"}`
1194
+ ];
1195
+ if (provider.tags?.length) lines.push(`**Tags:** ${provider.tags.join(", ")}`);
1196
+ if (provider.useCases?.length) lines.push("", "## Use Cases", "", ...provider.useCases.map((u) => `- ${u}`));
1197
+ if (provider.schema) {
1198
+ const props = provider.schema.properties;
1199
+ const required = new Set(provider.schema.required ?? []);
1200
+ if (props) {
1201
+ lines.push("", "## Configuration", "");
1202
+ for (const [key, val] of Object.entries(props)) {
1203
+ const req = required.has(key) ? " **(required)**" : "";
1204
+ lines.push(`- \`${key}\` (${val.type ?? "string"})${req} — ${val.description ?? ""}`);
1205
+ }
1206
+ }
1207
+ }
1208
+ lines.push("", "## Actions", "", "- `mount` — Mount this provider into AFS");
1209
+ return {
1210
+ format: "markdown",
1211
+ content: lines.join("\n")
1212
+ };
1213
+ }
1214
+ async explainRecipe(ctx) {
1215
+ const recipe = this.findRecipe(ctx.params.name);
1216
+ if (!recipe) throw new AFSNotFoundError(ctx.path);
1217
+ const lines = [
1218
+ `# ${recipe.name} (recipe)`,
1219
+ "",
1220
+ recipe.description,
1221
+ "",
1222
+ `**Category:** ${recipe.category}`,
1223
+ `**URI:** \`${recipe.uriTemplate}\``,
1224
+ `**Type:** recipe`
1225
+ ];
1226
+ if (recipe.tags?.length) lines.push(`**Tags:** ${recipe.tags.join(", ")}`);
1227
+ lines.push("", "## Actions", "", "- `mount` — Mount this recipe as an MCP provider", "", "*Note: Recipe capabilities depend on the external MCP server and cannot be verified until mounted.*");
1228
+ return {
1229
+ format: "markdown",
1230
+ content: lines.join("\n")
1231
+ };
1232
+ }
1233
+ async explainFallback(ctx) {
1234
+ if (ctx.path === "/" || ctx.path === "") {
1235
+ const categories = Array.from(this.buildByCategory().keys()).sort();
1236
+ return {
1237
+ format: "markdown",
1238
+ content: [
1239
+ "# AFS Provider Registry",
1240
+ "",
1241
+ "Virtual file tree for discovering and mounting AFS providers.",
1242
+ "",
1243
+ "## Structure",
1244
+ "- `/providers/{name}/` — Provider details (manifest.json, README.md, .actions/mount)",
1245
+ "- `/by-category/{category}/` — Browse by category",
1246
+ "- `/by-tag/{tag}/` — Browse by tag",
1247
+ "- `/recipes/{name}/` — MCP recipe details",
1248
+ "- `/.actions/search` — Search with structured filters (category, tags, query)",
1249
+ "",
1250
+ `**Providers:** ${this.providers.filter((p) => (p.type || "provider") === "provider").length}`,
1251
+ `**Recipes:** ${this.getRecipes().length}`,
1252
+ `**Categories:** ${categories.join(", ")}`
1253
+ ].join("\n")
1254
+ };
1255
+ }
1227
1256
  return {
1228
1257
  format: "markdown",
1229
1258
  content: [
1230
1259
  "# AFS Provider Registry",
1231
1260
  "",
1232
- "This provider exposes a virtual file tree of available AFS providers.",
1233
- "",
1234
- "## Structure",
1235
- "- `/providers/{name}/manifest.json` — Provider metadata",
1236
- "- `/providers/{name}/README.md` — Human-readable description",
1237
- "- `/providers/{name}/.actions/mount` — Mount action",
1238
- "- `/by-category/{category}/` — Browse by category",
1239
- "- `/by-tag/{tag}/` — Browse by tag",
1240
- "- `/.actions/search` — Full-text search across providers",
1241
- "",
1242
- `**Total providers:** ${this.providers.length}`
1261
+ "Use `explain /registry/providers/{name}` for provider-specific information.",
1262
+ "Use `search` action with `category` or `tags` params for structured discovery."
1243
1263
  ].join("\n")
1244
1264
  };
1245
1265
  }
@@ -1258,51 +1278,94 @@ __decorate([List("/by-tag")], AFSRegistry.prototype, "listTags", null);
1258
1278
  __decorate([List("/by-category/:category/:name")], AFSRegistry.prototype, "listCategoryProviderChildren", null);
1259
1279
  __decorate([List("/by-tag/:tag")], AFSRegistry.prototype, "listTagProviders", null);
1260
1280
  __decorate([List("/by-tag/:tag/:name")], AFSRegistry.prototype, "listTagProviderChildren", null);
1261
- __decorate([List("/providers/:name/manifest.json"), List("/providers/:name/README.md")], AFSRegistry.prototype, "listFileNode", null);
1281
+ __decorate([
1282
+ List("/providers/:name/manifest.json"),
1283
+ List("/providers/:name/README.md"),
1284
+ List("/providers/:name/tree-schema.json"),
1285
+ List("/providers/:name/AGENT.md")
1286
+ ], AFSRegistry.prototype, "listFileNode", null);
1287
+ __decorate([List("/recipes")], AFSRegistry.prototype, "listRecipes", null);
1288
+ __decorate([List("/recipes/:name")], AFSRegistry.prototype, "listRecipeContents", null);
1289
+ __decorate([List("/recipes/:name/manifest.json"), List("/recipes/:name/README.md")], AFSRegistry.prototype, "listRecipeFileNode", null);
1262
1290
  __decorate([Read("/")], AFSRegistry.prototype, "readRoot", null);
1263
1291
  __decorate([Read("/providers")], AFSRegistry.prototype, "readProviders", null);
1264
1292
  __decorate([Read("/providers/:name")], AFSRegistry.prototype, "readProvider", null);
1265
1293
  __decorate([Read("/providers/:name/manifest.json")], AFSRegistry.prototype, "readManifest", null);
1266
1294
  __decorate([Read("/providers/:name/README.md")], AFSRegistry.prototype, "readReadme", null);
1295
+ __decorate([Read("/providers/:name/tree-schema.json")], AFSRegistry.prototype, "readTreeSchema", null);
1296
+ __decorate([Read("/providers/:name/AGENT.md")], AFSRegistry.prototype, "readAgentMd", null);
1267
1297
  __decorate([Read("/by-category")], AFSRegistry.prototype, "readByCategory", null);
1268
1298
  __decorate([Read("/by-category/:category")], AFSRegistry.prototype, "readCategory", null);
1269
1299
  __decorate([Read("/by-category/:category/:name")], AFSRegistry.prototype, "readCategoryProvider", null);
1270
1300
  __decorate([Read("/by-tag")], AFSRegistry.prototype, "readByTag", null);
1271
1301
  __decorate([Read("/by-tag/:tag")], AFSRegistry.prototype, "readTag", null);
1272
1302
  __decorate([Read("/by-tag/:tag/:name")], AFSRegistry.prototype, "readTagProvider", null);
1303
+ __decorate([Read("/recipes")], AFSRegistry.prototype, "readRecipes", null);
1304
+ __decorate([Read("/recipes/:name")], AFSRegistry.prototype, "readRecipe", null);
1305
+ __decorate([Read("/recipes/:name/manifest.json")], AFSRegistry.prototype, "readRecipeManifest", null);
1306
+ __decorate([Read("/recipes/:name/README.md")], AFSRegistry.prototype, "readRecipeReadme", null);
1273
1307
  __decorate([Read("/.meta/.capabilities")], AFSRegistry.prototype, "readCapabilities", null);
1308
+ __decorate([List("/.vocabulary/categories"), List("/.vocabulary/tags")], AFSRegistry.prototype, "listVocabularyLeaf", null);
1309
+ __decorate([List("/.vocabulary")], AFSRegistry.prototype, "listVocabulary", null);
1310
+ __decorate([Read("/.vocabulary")], AFSRegistry.prototype, "readVocabulary", null);
1311
+ __decorate([Read("/.vocabulary/categories")], AFSRegistry.prototype, "readVocabularyCategories", null);
1312
+ __decorate([Read("/.vocabulary/tags")], AFSRegistry.prototype, "readVocabularyTags", null);
1274
1313
  __decorate([Meta("/")], AFSRegistry.prototype, "rootMeta", null);
1314
+ __decorate([Meta("/recipes")], AFSRegistry.prototype, "recipesMeta", null);
1315
+ __decorate([Meta("/recipes/:name")], AFSRegistry.prototype, "recipeMeta", null);
1316
+ __decorate([Meta("/recipes/:name/manifest.json")], AFSRegistry.prototype, "recipeManifestMeta", null);
1317
+ __decorate([Meta("/recipes/:name/README.md")], AFSRegistry.prototype, "recipeReadmeMeta", null);
1275
1318
  __decorate([Meta("/providers")], AFSRegistry.prototype, "providersMeta", null);
1276
1319
  __decorate([Meta("/providers/:name")], AFSRegistry.prototype, "providerMeta", null);
1277
1320
  __decorate([Meta("/providers/:name/manifest.json")], AFSRegistry.prototype, "manifestMeta", null);
1278
1321
  __decorate([Meta("/providers/:name/README.md")], AFSRegistry.prototype, "readmeMeta", null);
1322
+ __decorate([Meta("/providers/:name/tree-schema.json")], AFSRegistry.prototype, "treeSchemaJsonMeta", null);
1323
+ __decorate([Meta("/providers/:name/AGENT.md")], AFSRegistry.prototype, "agentMdMeta", null);
1279
1324
  __decorate([Meta("/by-category")], AFSRegistry.prototype, "byCategoryMeta", null);
1280
1325
  __decorate([Meta("/by-category/:category")], AFSRegistry.prototype, "categoryMeta", null);
1281
1326
  __decorate([Meta("/by-category/:category/:name")], AFSRegistry.prototype, "categoryProviderMeta", null);
1282
1327
  __decorate([Meta("/by-tag")], AFSRegistry.prototype, "byTagMeta", null);
1283
1328
  __decorate([Meta("/by-tag/:tag")], AFSRegistry.prototype, "tagMeta", null);
1284
1329
  __decorate([Meta("/by-tag/:tag/:name")], AFSRegistry.prototype, "tagProviderMeta", null);
1330
+ __decorate([Meta("/.vocabulary")], AFSRegistry.prototype, "vocabularyMeta", null);
1331
+ __decorate([Meta("/.vocabulary/categories")], AFSRegistry.prototype, "vocabularyCategoriesMeta", null);
1332
+ __decorate([Meta("/.vocabulary/tags")], AFSRegistry.prototype, "vocabularyTagsMeta", null);
1285
1333
  __decorate([Actions("/")], AFSRegistry.prototype, "listRootActions", null);
1286
1334
  __decorate([Actions.Exec("/", "search")], AFSRegistry.prototype, "searchProviders", null);
1335
+ __decorate([Actions("/recipes/:name")], AFSRegistry.prototype, "listRecipeActions", null);
1336
+ __decorate([Read("/recipes/:name/.actions/mount")], AFSRegistry.prototype, "readRecipeMountAction", null);
1337
+ __decorate([Actions.Exec("/recipes/:name", "mount")], AFSRegistry.prototype, "mountRecipe", null);
1287
1338
  __decorate([Actions("/providers/:name")], AFSRegistry.prototype, "listProviderActions", null);
1288
1339
  __decorate([Read("/providers/:name/.actions/mount")], AFSRegistry.prototype, "readMountAction", null);
1289
1340
  __decorate([Actions.Exec("/providers/:name", "mount")], AFSRegistry.prototype, "mountProvider", null);
1290
1341
  __decorate([Search("/")], AFSRegistry.prototype, "searchRoot", null);
1291
- __decorate([Explain("/:path*")], AFSRegistry.prototype, "explainPath", null);
1342
+ __decorate([Explain("/providers/:name")], AFSRegistry.prototype, "explainProvider", null);
1343
+ __decorate([Explain("/recipes/:name")], AFSRegistry.prototype, "explainRecipe", null);
1344
+ __decorate([Explain("/:path*")], AFSRegistry.prototype, "explainFallback", null);
1292
1345
  __decorate([
1293
1346
  Stat("/"),
1294
1347
  Stat("/providers"),
1295
1348
  Stat("/providers/:name"),
1296
1349
  Stat("/providers/:name/manifest.json"),
1297
1350
  Stat("/providers/:name/README.md"),
1351
+ Stat("/providers/:name/tree-schema.json"),
1352
+ Stat("/providers/:name/AGENT.md"),
1298
1353
  Stat("/by-category"),
1299
1354
  Stat("/by-category/:category"),
1300
1355
  Stat("/by-category/:category/:name"),
1301
1356
  Stat("/by-tag"),
1302
1357
  Stat("/by-tag/:tag"),
1303
- Stat("/by-tag/:tag/:name")
1358
+ Stat("/by-tag/:tag/:name"),
1359
+ Stat("/recipes"),
1360
+ Stat("/recipes/:name"),
1361
+ Stat("/recipes/:name/manifest.json"),
1362
+ Stat("/recipes/:name/README.md"),
1363
+ Stat("/recipes/:name/.actions/mount"),
1364
+ Stat("/.vocabulary"),
1365
+ Stat("/.vocabulary/categories"),
1366
+ Stat("/.vocabulary/tags")
1304
1367
  ], AFSRegistry.prototype, "statPath", null);
1305
1368
 
1306
1369
  //#endregion
1307
- export { AFSRegistry };
1370
+ export { AFSRegistry, clearScanCache, scanInstalledProviders };
1308
1371
  //# sourceMappingURL=index.mjs.map