mcp-framework
Version:
Framework for building Model Context Protocol (MCP) servers in Typescript
111 lines (110 loc) • 4.24 kB
JavaScript
import { join, dirname } from "path";
import { promises as fs } from "fs";
import { logger } from "../core/Logger.js";
export class ResourceLoader {
RESOURCES_DIR;
EXCLUDED_FILES = [
"BaseResource.js",
"*.test.js",
"*.spec.js",
];
constructor(basePath) {
const mainModulePath = basePath || process.argv[1];
this.RESOURCES_DIR = join(dirname(mainModulePath), "resources");
logger.debug(`Initialized ResourceLoader with directory: ${this.RESOURCES_DIR}`);
}
async hasResources() {
try {
const stats = await fs.stat(this.RESOURCES_DIR);
if (!stats.isDirectory()) {
logger.debug("Resources path exists but is not a directory");
return false;
}
const files = await fs.readdir(this.RESOURCES_DIR);
const hasValidFiles = files.some((file) => this.isResourceFile(file));
logger.debug(`Resources directory has valid files: ${hasValidFiles}`);
return hasValidFiles;
}
catch (error) {
logger.debug(`No resources directory found: ${error.message}`);
return false;
}
}
isResourceFile(file) {
if (!file.endsWith(".js"))
return false;
const isExcluded = this.EXCLUDED_FILES.some((pattern) => {
if (pattern.includes("*")) {
const regex = new RegExp(pattern.replace("*", ".*"));
return regex.test(file);
}
return file === pattern;
});
logger.debug(`Checking file ${file}: ${isExcluded ? "excluded" : "included"}`);
return !isExcluded;
}
validateResource(resource) {
const isValid = Boolean(resource &&
typeof resource.uri === "string" &&
typeof resource.name === "string" &&
resource.resourceDefinition &&
typeof resource.read === "function");
if (isValid) {
logger.debug(`Validated resource: ${resource.name}`);
}
else {
logger.warn(`Invalid resource found: missing required properties`);
}
return isValid;
}
async loadResources() {
try {
logger.debug(`Attempting to load resources from: ${this.RESOURCES_DIR}`);
let stats;
try {
stats = await fs.stat(this.RESOURCES_DIR);
}
catch (error) {
logger.debug(`No resources directory found: ${error.message}`);
return [];
}
if (!stats.isDirectory()) {
logger.error(`Path is not a directory: ${this.RESOURCES_DIR}`);
return [];
}
const files = await fs.readdir(this.RESOURCES_DIR);
logger.debug(`Found files in directory: ${files.join(", ")}`);
const resources = [];
for (const file of files) {
if (!this.isResourceFile(file)) {
continue;
}
try {
const fullPath = join(this.RESOURCES_DIR, file);
logger.debug(`Attempting to load resource from: ${fullPath}`);
const importPath = `file://${fullPath}`;
const { default: ResourceClass } = await import(importPath);
if (!ResourceClass) {
logger.warn(`No default export found in ${file}`);
continue;
}
const resource = new ResourceClass();
if (this.validateResource(resource)) {
resources.push(resource);
}
}
catch (error) {
logger.error(`Error loading resource ${file}: ${error.message}`);
}
}
logger.debug(`Successfully loaded ${resources.length} resources: ${resources
.map((r) => r.name)
.join(", ")}`);
return resources;
}
catch (error) {
logger.error(`Failed to load resources: ${error.message}`);
return [];
}
}
}