@aigne/afs-mcp 1.11.0-beta.6

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.cjs ADDED
@@ -0,0 +1,932 @@
1
+ const require_kinds = require('./kinds.cjs');
2
+ const require_decorate = require('./_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs');
3
+ let node_path = require("node:path");
4
+ let _aigne_afs = require("@aigne/afs");
5
+ let _aigne_afs_provider = require("@aigne/afs/provider");
6
+ let _aigne_afs_utils_zod = require("@aigne/afs/utils/zod");
7
+ let _modelcontextprotocol_sdk_client_index_js = require("@modelcontextprotocol/sdk/client/index.js");
8
+ let _modelcontextprotocol_sdk_client_sse_js = require("@modelcontextprotocol/sdk/client/sse.js");
9
+ let _modelcontextprotocol_sdk_client_stdio_js = require("@modelcontextprotocol/sdk/client/stdio.js");
10
+ let _modelcontextprotocol_sdk_client_streamableHttp_js = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
11
+ let zod = require("zod");
12
+
13
+ //#region src/index.ts
14
+ /**
15
+ * AFS MCP Provider
16
+ *
17
+ * 将 MCP Server 挂载为 AFS 可访问的世界。
18
+ * - Tools → 可执行的 AFS entries(通过 `exec()`)
19
+ * - Prompts → 可读取的世界描述
20
+ * - Resources → 可读取的世界状态(展开为 AFS 目录结构)
21
+ */
22
+ /**
23
+ * Zod schema for options validation
24
+ */
25
+ const afsMCPOptionsSchema = (0, _aigne_afs_utils_zod.camelize)(zod.z.object({
26
+ name: (0, _aigne_afs_utils_zod.optionalize)(zod.z.string()),
27
+ description: (0, _aigne_afs_utils_zod.optionalize)(zod.z.string()),
28
+ transport: zod.z.enum([
29
+ "stdio",
30
+ "http",
31
+ "sse"
32
+ ]),
33
+ command: (0, _aigne_afs_utils_zod.optionalize)(zod.z.string()),
34
+ args: (0, _aigne_afs_utils_zod.optionalize)(zod.z.array(zod.z.string())),
35
+ env: (0, _aigne_afs_utils_zod.optionalize)(zod.z.record(zod.z.string())),
36
+ url: (0, _aigne_afs_utils_zod.optionalize)(zod.z.string()),
37
+ headers: (0, _aigne_afs_utils_zod.optionalize)(zod.z.record(zod.z.string())),
38
+ timeout: (0, _aigne_afs_utils_zod.optionalize)(zod.z.number()),
39
+ maxReconnects: (0, _aigne_afs_utils_zod.optionalize)(zod.z.number())
40
+ }).refine((data) => {
41
+ if (data.transport === "stdio" && !data.command) return false;
42
+ if ((data.transport === "http" || data.transport === "sse") && !data.url) return false;
43
+ return true;
44
+ }, { message: "stdio transport requires 'command', http/sse transport requires 'url'" }));
45
+ /**
46
+ * AFS Module for MCP Server integration
47
+ */
48
+ var AFSMCP = class AFSMCP extends _aigne_afs_provider.AFSBaseProvider {
49
+ name;
50
+ description;
51
+ accessMode = "readwrite";
52
+ /**
53
+ * Get the Zod schema for options validation
54
+ */
55
+ static schema() {
56
+ return afsMCPOptionsSchema;
57
+ }
58
+ /**
59
+ * Load module from configuration file
60
+ */
61
+ static async load({ filepath, parsed }) {
62
+ return new AFSMCP({
63
+ ...await AFSMCP.schema().parseAsync(parsed),
64
+ cwd: (0, node_path.dirname)(filepath)
65
+ });
66
+ }
67
+ /**
68
+ * Parse a resource URI into its components
69
+ *
70
+ * Examples:
71
+ * - "file:///path/to/file.txt" -> { scheme: "file", path: "/path/to/file.txt" }
72
+ * - "sqlite://posts" -> { scheme: "sqlite", path: "/posts" }
73
+ * - "github://repos/owner/repo" -> { scheme: "github", path: "/repos/owner/repo" }
74
+ */
75
+ static parseResourceUri(uri) {
76
+ const match = uri.match(/^(\w+):\/\/\/?(.*)$/);
77
+ if (match) {
78
+ const scheme = match[1];
79
+ const rest = match[2];
80
+ return {
81
+ scheme,
82
+ path: rest.startsWith("/") ? rest : `/${rest}`
83
+ };
84
+ }
85
+ return {
86
+ scheme: "unknown",
87
+ path: uri
88
+ };
89
+ }
90
+ /**
91
+ * Parse a URI template and extract variable names
92
+ *
93
+ * Examples:
94
+ * - "sqlite://posts/{id}" -> ["id"]
95
+ * - "github://repos/{owner}/{repo}/issues/{number}" -> ["owner", "repo", "number"]
96
+ */
97
+ static parseUriTemplate(template) {
98
+ const vars = [];
99
+ const regex = /\{(\w+)\}/g;
100
+ let match = regex.exec(template);
101
+ while (match !== null) {
102
+ vars.push(match[1]);
103
+ match = regex.exec(template);
104
+ }
105
+ return vars;
106
+ }
107
+ /**
108
+ * Match a path against a URI template and extract parameters
109
+ *
110
+ * Examples:
111
+ * - matchPathToTemplate("/posts/123", "sqlite://posts/{id}") -> { id: "123" }
112
+ * - matchPathToTemplate("/repos/arcblock/afs/issues/42", "github://repos/{owner}/{repo}/issues/{number}")
113
+ * -> { owner: "arcblock", repo: "afs", number: "42" }
114
+ *
115
+ * Returns null if path doesn't match the template
116
+ */
117
+ static matchPathToTemplate(path, uriTemplate) {
118
+ const templatePath = AFSMCP.parseResourceUri(uriTemplate).path;
119
+ const vars = [];
120
+ const regexStr = templatePath.replace(/\{(\w+)\}/g, (_, varName) => {
121
+ vars.push(varName);
122
+ return "([^/]+)";
123
+ });
124
+ const regex = /* @__PURE__ */ new RegExp(`^${regexStr}$`);
125
+ const match = path.match(regex);
126
+ if (!match) return null;
127
+ const params = {};
128
+ for (let i = 0; i < vars.length; i++) {
129
+ const varName = vars[i];
130
+ const value = match[i + 1];
131
+ if (varName && value) params[varName] = value;
132
+ }
133
+ return params;
134
+ }
135
+ /**
136
+ * Build a complete URI from a template and parameters
137
+ */
138
+ static buildUriFromTemplate(template, params) {
139
+ let uri = template;
140
+ for (const [key, value] of Object.entries(params)) uri = uri.replace(`{${key}}`, value);
141
+ return uri;
142
+ }
143
+ client = null;
144
+ transport = null;
145
+ _tools = [];
146
+ _prompts = [];
147
+ _resources = [];
148
+ _resourceTemplates = [];
149
+ _resourcePathMap = /* @__PURE__ */ new Map();
150
+ _isConnected = false;
151
+ constructor(options) {
152
+ super();
153
+ this.options = options;
154
+ (0, _aigne_afs_utils_zod.zodParse)(afsMCPOptionsSchema, options);
155
+ this.name = options.name || "mcp";
156
+ this.description = options.description;
157
+ }
158
+ /**
159
+ * Define static entry tree for MCP provider structure.
160
+ *
161
+ * This async method calls ensureConnected() to initialize provider state,
162
+ * then returns the tree with dynamic values based on connected data.
163
+ *
164
+ * Static entries:
165
+ * - /WORLD.md: Always present
166
+ * - /tools: With dynamic childrenCount
167
+ * - /prompts: Only if prompts exist, with dynamic childrenCount
168
+ * - /resources: Only if resources exist, with dynamic childrenCount
169
+ */
170
+ async defineEntries() {
171
+ await this.ensureConnected();
172
+ const children = {
173
+ "WORLD.md": {
174
+ content: this.generateWorldMd(),
175
+ metadata: {
176
+ kind: "afs:document",
177
+ kinds: require_kinds.getKindsArray("afs:document"),
178
+ description: "MCP Server World Documentation",
179
+ mimeType: "text/markdown",
180
+ mcp: { type: "world" }
181
+ }
182
+ },
183
+ tools: { metadata: {
184
+ kind: "afs:node",
185
+ kinds: require_kinds.getKindsArray("afs:node"),
186
+ description: `${this._tools.length} tools available`,
187
+ childrenCount: this._tools.length
188
+ } }
189
+ };
190
+ if (this._prompts.length > 0) children.prompts = { metadata: {
191
+ kind: "afs:node",
192
+ kinds: require_kinds.getKindsArray("afs:node"),
193
+ description: `${this._prompts.length} prompts available`,
194
+ childrenCount: this._prompts.length
195
+ } };
196
+ if (this._resources.length > 0 || this._resourceTemplates.length > 0) children.resources = { metadata: {
197
+ kind: "afs:node",
198
+ kinds: require_kinds.getKindsArray("afs:node"),
199
+ description: `${this._resources.length} resources available`,
200
+ childrenCount: this._resources.length
201
+ } };
202
+ return {
203
+ metadata: {
204
+ kind: "mcp:module",
205
+ kinds: require_kinds.getKindsArray("mcp:module"),
206
+ description: this.description || "MCP Server",
207
+ mcp: {
208
+ server: { name: this.name },
209
+ capabilities: {
210
+ tools: this._tools.length > 0,
211
+ prompts: this._prompts.length > 0,
212
+ resources: this._resources.length > 0
213
+ }
214
+ }
215
+ },
216
+ children
217
+ };
218
+ }
219
+ get isConnected() {
220
+ return this._isConnected;
221
+ }
222
+ /**
223
+ * Get cached tools
224
+ */
225
+ get tools() {
226
+ return this._tools;
227
+ }
228
+ /**
229
+ * Get cached prompts
230
+ */
231
+ get prompts() {
232
+ return this._prompts;
233
+ }
234
+ /**
235
+ * Get cached resources
236
+ */
237
+ get resources() {
238
+ return this._resources;
239
+ }
240
+ /**
241
+ * Get cached resource templates
242
+ */
243
+ get resourceTemplates() {
244
+ return this._resourceTemplates;
245
+ }
246
+ /** Promise for in-progress connection */
247
+ _connectPromise = null;
248
+ /**
249
+ * Ensure connection is established (lazy connect)
250
+ */
251
+ async ensureConnected() {
252
+ if (this._isConnected) return;
253
+ if (this._connectPromise) return this._connectPromise;
254
+ this._connectPromise = this.connect();
255
+ try {
256
+ await this._connectPromise;
257
+ } finally {
258
+ this._connectPromise = null;
259
+ }
260
+ }
261
+ /**
262
+ * Connect to the MCP server
263
+ */
264
+ async connect() {
265
+ if (this._isConnected) return;
266
+ this.transport = this.createTransport();
267
+ this.client = new _modelcontextprotocol_sdk_client_index_js.Client({
268
+ name: "afs-mcp-client",
269
+ version: "1.0.0"
270
+ }, { capabilities: {} });
271
+ await this.client.connect(this.transport);
272
+ this._isConnected = true;
273
+ await this.refreshCapabilities();
274
+ this.buildResourcePathMap();
275
+ }
276
+ /**
277
+ * Disconnect from the MCP server
278
+ */
279
+ async disconnect() {
280
+ if (!this._isConnected) return;
281
+ try {
282
+ await this.client?.close();
283
+ } catch (error) {
284
+ if (!(error instanceof Error && (error.message.includes("EPIPE") || error.code === "EPIPE"))) throw error;
285
+ }
286
+ this.client = null;
287
+ this.transport = null;
288
+ this._isConnected = false;
289
+ this._tools = [];
290
+ this._prompts = [];
291
+ this._resources = [];
292
+ this._resourceTemplates = [];
293
+ this._resourcePathMap.clear();
294
+ }
295
+ /**
296
+ * Create transport based on configuration
297
+ */
298
+ createTransport() {
299
+ switch (this.options.transport) {
300
+ case "stdio": return new _modelcontextprotocol_sdk_client_stdio_js.StdioClientTransport({
301
+ command: this.options.command,
302
+ args: this.options.args,
303
+ env: {
304
+ ...process.env,
305
+ ...this.options.env
306
+ },
307
+ stderr: "pipe"
308
+ });
309
+ case "http": return new _modelcontextprotocol_sdk_client_streamableHttp_js.StreamableHTTPClientTransport(new URL(this.options.url), { requestInit: { headers: this.options.headers } });
310
+ case "sse": return new _modelcontextprotocol_sdk_client_sse_js.SSEClientTransport(new URL(this.options.url), { requestInit: { headers: this.options.headers } });
311
+ default: throw new Error(`Unknown transport: ${this.options.transport}`);
312
+ }
313
+ }
314
+ /**
315
+ * Refresh cached capabilities from the server
316
+ */
317
+ async refreshCapabilities() {
318
+ if (!this.client) return;
319
+ try {
320
+ this._tools = (await this.client.listTools()).tools || [];
321
+ } catch {
322
+ this._tools = [];
323
+ }
324
+ try {
325
+ this._prompts = (await this.client.listPrompts()).prompts || [];
326
+ } catch {
327
+ this._prompts = [];
328
+ }
329
+ try {
330
+ this._resources = (await this.client.listResources()).resources || [];
331
+ } catch {
332
+ this._resources = [];
333
+ }
334
+ try {
335
+ this._resourceTemplates = (await this.client.listResourceTemplates()).resourceTemplates || [];
336
+ } catch {
337
+ this._resourceTemplates = [];
338
+ }
339
+ }
340
+ /**
341
+ * Build the resource path mapping from cached resources
342
+ */
343
+ buildResourcePathMap() {
344
+ this._resourcePathMap.clear();
345
+ for (const resource of this._resources) {
346
+ const path = this.resourceUriToPath(resource.uri);
347
+ if (path) this._resourcePathMap.set(path, { resource });
348
+ }
349
+ for (const template of this._resourceTemplates) {
350
+ const basePath = this.getTemplateBasePath(template.uriTemplate);
351
+ if (basePath) this._resourcePathMap.set(basePath, { template });
352
+ }
353
+ }
354
+ /**
355
+ * Convert a resource URI to an AFS path
356
+ *
357
+ * Examples:
358
+ * - "file:///path/to/file.txt" -> "/path/to/file.txt"
359
+ * - "sqlite://posts" -> "/posts"
360
+ * - "github://repos" -> "/repos"
361
+ */
362
+ resourceUriToPath(uri) {
363
+ return AFSMCP.parseResourceUri(uri).path;
364
+ }
365
+ /**
366
+ * Get the base path from a URI template (path before first variable)
367
+ *
368
+ * Examples:
369
+ * - "sqlite://posts/{id}" -> "/posts"
370
+ * - "github://repos/{owner}/{repo}" -> "/repos"
371
+ */
372
+ getTemplateBasePath(uriTemplate) {
373
+ const parsed = AFSMCP.parseResourceUri(uriTemplate);
374
+ const varIndex = parsed.path.indexOf("{");
375
+ if (varIndex === -1) return parsed.path;
376
+ const basePath = parsed.path.substring(0, varIndex);
377
+ return basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
378
+ }
379
+ /**
380
+ * Find a resource or template that matches a given path
381
+ */
382
+ findResourceForPath(path) {
383
+ for (const resource of this._resources) if (this.resourceUriToPath(resource.uri) === path) return { resource };
384
+ for (const template of this._resourceTemplates) {
385
+ const params = AFSMCP.matchPathToTemplate(path, template.uriTemplate);
386
+ if (params) return {
387
+ template,
388
+ params
389
+ };
390
+ }
391
+ return null;
392
+ }
393
+ /**
394
+ * Convert a Tool to an AFSEntry (Meta Spec compliant)
395
+ */
396
+ toolToEntry(tool) {
397
+ return {
398
+ id: `/tools/${tool.name}`,
399
+ path: `/tools/${tool.name}`,
400
+ summary: tool.description,
401
+ metadata: {
402
+ kind: "mcp:tool",
403
+ kinds: require_kinds.getKindsArray("mcp:tool"),
404
+ description: tool.description,
405
+ inputSchema: tool.inputSchema,
406
+ mcp: { name: tool.name }
407
+ }
408
+ };
409
+ }
410
+ /**
411
+ * Convert a Prompt to an AFSEntry (Meta Spec compliant)
412
+ */
413
+ promptToEntry(prompt) {
414
+ return {
415
+ id: `/prompts/${prompt.name}`,
416
+ path: `/prompts/${prompt.name}`,
417
+ summary: prompt.description,
418
+ metadata: {
419
+ kind: "mcp:prompt",
420
+ kinds: require_kinds.getKindsArray("mcp:prompt"),
421
+ description: prompt.description,
422
+ mcp: {
423
+ name: prompt.name,
424
+ arguments: prompt.arguments
425
+ }
426
+ }
427
+ };
428
+ }
429
+ /**
430
+ * Convert a Resource to an AFSEntry (Meta Spec compliant)
431
+ */
432
+ resourceToEntry(resource, path) {
433
+ return {
434
+ id: path,
435
+ path,
436
+ summary: resource.description || resource.name,
437
+ metadata: {
438
+ kind: "mcp:resource",
439
+ kinds: require_kinds.getKindsArray("mcp:resource"),
440
+ description: resource.description,
441
+ mimeType: resource.mimeType,
442
+ mcp: {
443
+ uri: resource.uri,
444
+ name: resource.name
445
+ }
446
+ }
447
+ };
448
+ }
449
+ /**
450
+ * Convert a ResourceTemplate to an AFSEntry (Meta Spec compliant)
451
+ */
452
+ resourceTemplateToEntry(template, path) {
453
+ return {
454
+ id: path,
455
+ path,
456
+ summary: template.description || template.name,
457
+ metadata: {
458
+ kind: "mcp:resource-template",
459
+ kinds: require_kinds.getKindsArray("mcp:resource"),
460
+ description: template.description,
461
+ mimeType: template.mimeType,
462
+ mcp: {
463
+ uriTemplate: template.uriTemplate,
464
+ name: template.name
465
+ }
466
+ }
467
+ };
468
+ }
469
+ /**
470
+ * List tools.
471
+ *
472
+ * Returns tool children only. The /tools directory entry comes from @StaticEntries
473
+ * and will be merged with dynamic childrenCount.
474
+ */
475
+ async listToolsHandler(_ctx) {
476
+ await this.ensureConnected();
477
+ return { data: this._tools.map((tool) => this.toolToEntry(tool)) };
478
+ }
479
+ /**
480
+ * List prompts.
481
+ *
482
+ * Returns prompt children only. The /prompts directory entry comes from @StaticEntries
483
+ * and will be merged with dynamic childrenCount.
484
+ */
485
+ async listPromptsHandler(_ctx) {
486
+ await this.ensureConnected();
487
+ return { data: this._prompts.map((prompt) => this.promptToEntry(prompt)) };
488
+ }
489
+ /**
490
+ * List specific tool
491
+ */
492
+ async listToolHandler(ctx) {
493
+ await this.ensureConnected();
494
+ const tool = this._tools.find((t) => t.name === ctx.params.name);
495
+ if (!tool) throw new _aigne_afs.AFSNotFoundError(ctx.path);
496
+ return { data: [this.toolToEntry(tool)] };
497
+ }
498
+ /**
499
+ * List specific prompt
500
+ */
501
+ async listPromptHandler(ctx) {
502
+ await this.ensureConnected();
503
+ const prompt = this._prompts.find((p) => p.name === ctx.params.name);
504
+ if (!prompt) throw new _aigne_afs.AFSNotFoundError(ctx.path);
505
+ return { data: [this.promptToEntry(prompt)] };
506
+ }
507
+ /**
508
+ * List resources directory.
509
+ *
510
+ * Returns all resources as children of /resources.
511
+ */
512
+ async listResourcesHandler(_ctx) {
513
+ await this.ensureConnected();
514
+ const immediateChildren = /* @__PURE__ */ new Map();
515
+ for (const resource of this._resources) {
516
+ const resourcePath = this.resourceUriToPath(resource.uri);
517
+ if (!resourcePath) continue;
518
+ const segments = resourcePath.split("/").filter(Boolean);
519
+ if (segments.length === 0) continue;
520
+ const childPath = `/resources/${segments[0]}`;
521
+ if (segments.length === 1) immediateChildren.set(childPath, {
522
+ isDir: false,
523
+ resource
524
+ });
525
+ else if (!immediateChildren.has(childPath)) immediateChildren.set(childPath, { isDir: true });
526
+ }
527
+ const entries = [];
528
+ for (const [path, info] of immediateChildren) if (info.isDir) entries.push({
529
+ id: path,
530
+ path,
531
+ summary: `Resource directory: ${path.replace("/resources", "")}`,
532
+ metadata: {
533
+ kind: "afs:node",
534
+ kinds: require_kinds.getKindsArray("afs:node"),
535
+ mcp: { isResource: true }
536
+ }
537
+ });
538
+ else if (info.resource) entries.push(this.resourceToEntry(info.resource, path));
539
+ return { data: entries };
540
+ }
541
+ /**
542
+ * List resource paths (wildcard handler under /resources)
543
+ */
544
+ async listResourceHandler(ctx) {
545
+ await this.ensureConnected();
546
+ const resourcePath = `/${ctx.params.path}`;
547
+ const afsPath = `/resources${resourcePath}`;
548
+ const entries = [];
549
+ let exactMatch = null;
550
+ const immediateChildren = /* @__PURE__ */ new Map();
551
+ const depth = resourcePath.split("/").filter(Boolean).length;
552
+ for (const resource of this._resources) {
553
+ const rPath = this.resourceUriToPath(resource.uri);
554
+ if (!rPath) continue;
555
+ if (rPath === resourcePath) exactMatch = resource;
556
+ else if (rPath.startsWith(`${resourcePath}/`)) {
557
+ const segments = rPath.split("/").filter(Boolean);
558
+ if (segments.length <= depth) continue;
559
+ const childPath = `/resources${resourcePath}/${segments[depth]}`;
560
+ if (segments.length === depth + 1) immediateChildren.set(childPath, {
561
+ isDir: false,
562
+ resource
563
+ });
564
+ else if (!immediateChildren.has(childPath)) immediateChildren.set(childPath, { isDir: true });
565
+ }
566
+ }
567
+ if (exactMatch) entries.push(this.resourceToEntry(exactMatch, afsPath));
568
+ else if (immediateChildren.size > 0) {
569
+ entries.push({
570
+ id: afsPath,
571
+ path: afsPath,
572
+ summary: `Resource directory: ${resourcePath}`,
573
+ metadata: {
574
+ kind: "afs:node",
575
+ kinds: require_kinds.getKindsArray("afs:node"),
576
+ childrenCount: immediateChildren.size,
577
+ mcp: { isResource: true }
578
+ }
579
+ });
580
+ for (const [path, info] of immediateChildren) if (info.isDir) entries.push({
581
+ id: path,
582
+ path,
583
+ summary: `Resource directory: ${path.replace("/resources", "")}`,
584
+ metadata: {
585
+ kind: "afs:node",
586
+ kinds: require_kinds.getKindsArray("afs:node"),
587
+ mcp: { isResource: true }
588
+ }
589
+ });
590
+ else if (info.resource) entries.push(this.resourceToEntry(info.resource, path));
591
+ } else throw new _aigne_afs.AFSNotFoundError(ctx.path);
592
+ return { data: entries };
593
+ }
594
+ /**
595
+ * Read metadata for tools (dynamic entries not in static tree).
596
+ * Static entries metadata is handled by AFSBaseProvider.
597
+ */
598
+ async readToolMeta(ctx) {
599
+ await this.ensureConnected();
600
+ const tool = this._tools.find((t) => t.name === ctx.params.name);
601
+ if (!tool) throw new _aigne_afs.AFSNotFoundError(ctx.path);
602
+ const entry = this.toolToEntry(tool);
603
+ return {
604
+ id: `/tools/${ctx.params.name}/.meta`,
605
+ path: `/tools/${ctx.params.name}/.meta`,
606
+ metadata: entry.metadata
607
+ };
608
+ }
609
+ /**
610
+ * Read metadata for prompts (dynamic entries not in static tree).
611
+ */
612
+ async readPromptMeta(ctx) {
613
+ await this.ensureConnected();
614
+ const prompt = this._prompts.find((p) => p.name === ctx.params.name);
615
+ if (!prompt) throw new _aigne_afs.AFSNotFoundError(ctx.path);
616
+ const entry = this.promptToEntry(prompt);
617
+ return {
618
+ id: `/prompts/${ctx.params.name}/.meta`,
619
+ path: `/prompts/${ctx.params.name}/.meta`,
620
+ metadata: entry.metadata
621
+ };
622
+ }
623
+ /**
624
+ * Read metadata for resources (dynamic entries not in static tree).
625
+ * Handles both actual resources and synthesized intermediate directories.
626
+ */
627
+ async readResourceMeta(ctx) {
628
+ await this.ensureConnected();
629
+ const resourcePath = `/${ctx.params.path}`;
630
+ const metaPath = `/resources${resourcePath}/.meta`;
631
+ const resourceMatch = this.findResourceForPath(resourcePath);
632
+ if (resourceMatch) {
633
+ if (resourceMatch.resource) return {
634
+ id: metaPath,
635
+ path: metaPath,
636
+ metadata: this.resourceToEntry(resourceMatch.resource, `/resources${resourcePath}`).metadata
637
+ };
638
+ else if (resourceMatch.template) return {
639
+ id: metaPath,
640
+ path: metaPath,
641
+ metadata: this.resourceTemplateToEntry(resourceMatch.template, `/resources${resourcePath}`).metadata
642
+ };
643
+ }
644
+ if (this._resources.some((resource) => {
645
+ return this.resourceUriToPath(resource.uri)?.startsWith(`${resourcePath}/`);
646
+ })) return {
647
+ id: metaPath,
648
+ path: metaPath,
649
+ metadata: {
650
+ kind: "afs:node",
651
+ kinds: require_kinds.getKindsArray("afs:node"),
652
+ mcp: { isResource: true }
653
+ }
654
+ };
655
+ throw new _aigne_afs.AFSNotFoundError(ctx.path);
656
+ }
657
+ /**
658
+ * Read tool
659
+ */
660
+ async readToolHandler(ctx) {
661
+ await this.ensureConnected();
662
+ const tool = this._tools.find((t) => t.name === ctx.params.name);
663
+ if (!tool) throw new _aigne_afs.AFSNotFoundError(ctx.path);
664
+ return this.toolToEntry(tool);
665
+ }
666
+ /**
667
+ * Read prompt
668
+ */
669
+ async readPromptHandler(ctx) {
670
+ await this.ensureConnected();
671
+ const prompt = this._prompts.find((p) => p.name === ctx.params.name);
672
+ if (!prompt) throw new _aigne_afs.AFSNotFoundError(ctx.path);
673
+ return this.promptToEntry(prompt);
674
+ }
675
+ /**
676
+ * Read resource (wildcard handler under /resources)
677
+ */
678
+ async readResourceHandler(ctx) {
679
+ await this.ensureConnected();
680
+ const resourcePath = `/${ctx.params.path}`;
681
+ const afsPath = `/resources${resourcePath}`;
682
+ const resourceMatch = this.findResourceForPath(resourcePath);
683
+ if (resourceMatch) {
684
+ if (resourceMatch.resource) {
685
+ const result = await this.readResourceByUri(resourceMatch.resource.uri);
686
+ if (!result.data) throw new _aigne_afs.AFSNotFoundError(ctx.path, result.message || `Resource not found: ${ctx.path}`);
687
+ result.data.path = `/resources${result.data.path}`;
688
+ result.data.id = result.data.path;
689
+ return result.data;
690
+ } else if (resourceMatch.template && resourceMatch.params) {
691
+ const uri = AFSMCP.buildUriFromTemplate(resourceMatch.template.uriTemplate, resourceMatch.params);
692
+ const result = await this.readResourceByUri(uri);
693
+ if (!result.data) throw new _aigne_afs.AFSNotFoundError(ctx.path, result.message || `Resource not found: ${ctx.path}`);
694
+ result.data.path = `/resources${result.data.path}`;
695
+ result.data.id = result.data.path;
696
+ return result.data;
697
+ }
698
+ }
699
+ if (this._resources.some((resource) => {
700
+ return this.resourceUriToPath(resource.uri)?.startsWith(`${resourcePath}/`);
701
+ })) return {
702
+ id: afsPath,
703
+ path: afsPath,
704
+ summary: `Resource directory: ${resourcePath}`,
705
+ metadata: {
706
+ kind: "afs:node",
707
+ kinds: require_kinds.getKindsArray("afs:node"),
708
+ mcp: { isResource: true }
709
+ }
710
+ };
711
+ throw new _aigne_afs.AFSNotFoundError(ctx.path);
712
+ }
713
+ /**
714
+ * Read a resource by its URI (internal helper)
715
+ */
716
+ async readResourceByUri(uri) {
717
+ if (!this.client) return {
718
+ data: void 0,
719
+ message: "MCP client not connected"
720
+ };
721
+ try {
722
+ const result = await this.client.readResource({ uri });
723
+ const path = this.resourceUriToPath(uri) || uri;
724
+ let content;
725
+ let mimeType;
726
+ if (result.contents && result.contents.length > 0) {
727
+ const firstContent = result.contents[0];
728
+ mimeType = firstContent.mimeType;
729
+ if ("text" in firstContent) content = firstContent.text;
730
+ else if ("blob" in firstContent) content = firstContent.blob;
731
+ }
732
+ return { data: {
733
+ id: path,
734
+ path,
735
+ content,
736
+ metadata: { mcp: {
737
+ uri,
738
+ mimeType
739
+ } }
740
+ } };
741
+ } catch (error) {
742
+ return {
743
+ data: void 0,
744
+ message: `Failed to read resource: ${error.message}`
745
+ };
746
+ }
747
+ }
748
+ /**
749
+ * Read a prompt with arguments, returning the prompt content
750
+ *
751
+ * This is a specialized method that calls the MCP getPrompt API
752
+ * to get the actual prompt content with substituted arguments.
753
+ */
754
+ async readPrompt(path, args) {
755
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
756
+ if (!normalizedPath.startsWith("/prompts/")) return {
757
+ data: void 0,
758
+ message: `readPrompt only supported on /prompts/* paths, got: ${normalizedPath}`
759
+ };
760
+ if (!this.client) return {
761
+ data: void 0,
762
+ message: "MCP client not connected"
763
+ };
764
+ const promptName = normalizedPath.slice(9);
765
+ const prompt = this._prompts.find((p) => p.name === promptName);
766
+ if (!prompt) return {
767
+ data: void 0,
768
+ message: `Prompt not found: ${promptName}`
769
+ };
770
+ try {
771
+ const result = await this.client.getPrompt({
772
+ name: promptName,
773
+ arguments: args
774
+ });
775
+ return { data: {
776
+ id: `/prompts/${promptName}`,
777
+ path: `/prompts/${promptName}`,
778
+ summary: prompt.description,
779
+ content: result.messages,
780
+ metadata: {
781
+ arguments: prompt.arguments,
782
+ mcp: {
783
+ name: prompt.name,
784
+ description: prompt.description,
785
+ arguments: prompt.arguments
786
+ }
787
+ }
788
+ } };
789
+ } catch (error) {
790
+ return {
791
+ data: void 0,
792
+ message: `Failed to get prompt: ${error.message}`
793
+ };
794
+ }
795
+ }
796
+ /**
797
+ * Generate WORLD.md content describing this MCP server's capabilities
798
+ */
799
+ generateWorldMd() {
800
+ const lines = [];
801
+ lines.push(`# ${this.name}`);
802
+ lines.push("");
803
+ if (this.description) {
804
+ lines.push(this.description);
805
+ lines.push("");
806
+ }
807
+ lines.push("## Server Information");
808
+ lines.push("");
809
+ lines.push(`- **Name**: ${this.name}`);
810
+ lines.push(`- **Transport**: ${this.options.transport}`);
811
+ if (this.options.transport === "stdio") lines.push(`- **Command**: ${this.options.command}`);
812
+ else lines.push(`- **URL**: ${this.options.url}`);
813
+ lines.push("");
814
+ lines.push("## Capabilities");
815
+ lines.push("");
816
+ lines.push(`- Tools: ${this._tools.length}`);
817
+ lines.push(`- Prompts: ${this._prompts.length}`);
818
+ lines.push(`- Resources: ${this._resources.length}`);
819
+ lines.push(`- Resource Templates: ${this._resourceTemplates.length}`);
820
+ lines.push("");
821
+ if (this._tools.length > 0) {
822
+ lines.push("## Tools");
823
+ lines.push("");
824
+ for (const tool of this._tools) {
825
+ lines.push(`### ${tool.name}`);
826
+ lines.push("");
827
+ if (tool.description) {
828
+ lines.push(tool.description);
829
+ lines.push("");
830
+ }
831
+ lines.push(`**Path**: \`/tools/${tool.name}\``);
832
+ lines.push("");
833
+ if (tool.inputSchema) {
834
+ lines.push("**Input Schema**:");
835
+ lines.push("```json");
836
+ lines.push(JSON.stringify(tool.inputSchema, null, 2));
837
+ lines.push("```");
838
+ lines.push("");
839
+ }
840
+ }
841
+ }
842
+ if (this._prompts.length > 0) {
843
+ lines.push("## Prompts");
844
+ lines.push("");
845
+ for (const prompt of this._prompts) {
846
+ lines.push(`### ${prompt.name}`);
847
+ lines.push("");
848
+ if (prompt.description) {
849
+ lines.push(prompt.description);
850
+ lines.push("");
851
+ }
852
+ lines.push(`**Path**: \`/prompts/${prompt.name}\``);
853
+ lines.push("");
854
+ if (prompt.arguments && prompt.arguments.length > 0) {
855
+ lines.push("**Arguments**:");
856
+ for (const arg of prompt.arguments) {
857
+ const required = arg.required ? " (required)" : " (optional)";
858
+ lines.push(`- \`${arg.name}\`${required}: ${arg.description || ""}`);
859
+ }
860
+ lines.push("");
861
+ }
862
+ }
863
+ }
864
+ if (this._resources.length > 0) {
865
+ lines.push("## Resources");
866
+ lines.push("");
867
+ for (const resource of this._resources) {
868
+ lines.push(`### ${resource.name}`);
869
+ lines.push("");
870
+ if (resource.description) {
871
+ lines.push(resource.description);
872
+ lines.push("");
873
+ }
874
+ lines.push(`**URI**: \`${resource.uri}\``);
875
+ const afsPath = this.resourceUriToPath(resource.uri);
876
+ if (afsPath) lines.push(`**AFS Path**: \`${afsPath}\``);
877
+ if (resource.mimeType) lines.push(`**MIME Type**: ${resource.mimeType}`);
878
+ lines.push("");
879
+ }
880
+ }
881
+ if (this._resourceTemplates.length > 0) {
882
+ lines.push("## Resource Templates");
883
+ lines.push("");
884
+ for (const template of this._resourceTemplates) {
885
+ lines.push(`### ${template.name}`);
886
+ lines.push("");
887
+ if (template.description) {
888
+ lines.push(template.description);
889
+ lines.push("");
890
+ }
891
+ lines.push(`**URI Template**: \`${template.uriTemplate}\``);
892
+ const vars = AFSMCP.parseUriTemplate(template.uriTemplate);
893
+ if (vars.length > 0) lines.push(`**Variables**: ${vars.map((v) => `\`{${v}}\``).join(", ")}`);
894
+ if (template.mimeType) lines.push(`**MIME Type**: ${template.mimeType}`);
895
+ lines.push("");
896
+ }
897
+ }
898
+ return lines.join("\n");
899
+ }
900
+ /**
901
+ * Execute a tool
902
+ */
903
+ async execToolHandler(ctx, args) {
904
+ await this.ensureConnected();
905
+ if (!this.client) throw new Error("MCP client not connected");
906
+ if (!this._tools.find((t) => t.name === ctx.params.name)) throw new Error(`Tool not found: ${ctx.params.name}`);
907
+ return {
908
+ success: true,
909
+ data: await this.client.callTool({
910
+ name: ctx.params.name,
911
+ arguments: args
912
+ })
913
+ };
914
+ }
915
+ };
916
+ require_decorate.__decorate([(0, _aigne_afs_provider.StaticEntries)()], AFSMCP.prototype, "defineEntries", null);
917
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/tools")], AFSMCP.prototype, "listToolsHandler", null);
918
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/prompts")], AFSMCP.prototype, "listPromptsHandler", null);
919
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/tools/:name")], AFSMCP.prototype, "listToolHandler", null);
920
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/prompts/:name")], AFSMCP.prototype, "listPromptHandler", null);
921
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/resources")], AFSMCP.prototype, "listResourcesHandler", null);
922
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/resources/:path+")], AFSMCP.prototype, "listResourceHandler", null);
923
+ require_decorate.__decorate([(0, _aigne_afs_provider.Meta)("/tools/:name")], AFSMCP.prototype, "readToolMeta", null);
924
+ require_decorate.__decorate([(0, _aigne_afs_provider.Meta)("/prompts/:name")], AFSMCP.prototype, "readPromptMeta", null);
925
+ require_decorate.__decorate([(0, _aigne_afs_provider.Meta)("/resources/:path+")], AFSMCP.prototype, "readResourceMeta", null);
926
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/tools/:name")], AFSMCP.prototype, "readToolHandler", null);
927
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/prompts/:name")], AFSMCP.prototype, "readPromptHandler", null);
928
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/resources/:path+")], AFSMCP.prototype, "readResourceHandler", null);
929
+ require_decorate.__decorate([(0, _aigne_afs_provider.Exec)("/tools/:name")], AFSMCP.prototype, "execToolHandler", null);
930
+
931
+ //#endregion
932
+ exports.AFSMCP = AFSMCP;