Skip to content

Commit 99bfa52

Browse files
committed
feat: implement plugin-based resource architecture
- Create src/resources directory for resource plugins - Move simulator resource to plugin-based structure - Update build plugin to scan and generate resource loaders - Add generated-resources.ts to auto-load resources at build time - Update resource registration to use dynamic loading - Resources now follow same configuration-by-convention pattern as tools - Add comprehensive tests for resource plugins and loading This enables dropping *.ts files in src/resources to automatically register new MCP resources without manual imports or registration code.
1 parent 26ee4eb commit 99bfa52

7 files changed

Lines changed: 404 additions & 276 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ build/
1212
# Auto-generated files
1313
src/version.ts
1414
src/core/generated-plugins.ts
15+
src/core/generated-resources.ts
1516

1617
# IDE and editor files
1718
.idea/

build-plugins/plugin-discovery.js

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ export function createPluginDiscoveryPlugin() {
1010
build.onStart(async () => {
1111
try {
1212
await generateWorkflowLoaders();
13+
await generateResourceLoaders();
1314
} catch (error) {
14-
console.error('Failed to generate workflow loaders:', error);
15+
console.error('Failed to generate loaders:', error);
1516
throw error;
1617
}
1718
});
@@ -222,4 +223,65 @@ export const WORKFLOW_METADATA = {
222223
${metadataEntries}
223224
};
224225
`;
226+
}
227+
228+
async function generateResourceLoaders() {
229+
const resourcesDir = path.resolve(process.cwd(), 'src/resources');
230+
231+
if (!existsSync(resourcesDir)) {
232+
console.log('Resources directory not found, skipping resource generation');
233+
return;
234+
}
235+
236+
// Scan for resource files
237+
const resourceFiles = readdirSync(resourcesDir, { withFileTypes: true })
238+
.filter(dirent => dirent.isFile())
239+
.map(dirent => dirent.name)
240+
.filter(name =>
241+
(name.endsWith('.ts') || name.endsWith('.js')) &&
242+
!name.endsWith('.test.ts') &&
243+
!name.endsWith('.test.js') &&
244+
!name.startsWith('__') // Exclude test directories
245+
);
246+
247+
const resourceLoaders = {};
248+
249+
for (const fileName of resourceFiles) {
250+
const resourceName = fileName.replace(/\.(ts|js)$/, '');
251+
252+
// Generate dynamic loader for this resource
253+
resourceLoaders[resourceName] = `async () => {
254+
const module = await import('../resources/${resourceName}.js');
255+
return module.default;
256+
}`;
257+
258+
console.log(`✅ Discovered resource: ${resourceName}`);
259+
}
260+
261+
// Generate the content for generated-resources.ts
262+
const generatedContent = generateResourcesFileContent(resourceLoaders);
263+
264+
// Write to the generated file
265+
const outputPath = path.resolve(process.cwd(), 'src/core/generated-resources.ts');
266+
267+
const fs = await import('fs');
268+
await fs.promises.writeFile(outputPath, generatedContent, 'utf8');
269+
270+
console.log(`🔧 Generated resource loaders for ${Object.keys(resourceLoaders).length} resources`);
271+
}
272+
273+
function generateResourcesFileContent(resourceLoaders) {
274+
const loaderEntries = Object.entries(resourceLoaders)
275+
.map(([key, loader]) => ` '${key}': ${loader}`)
276+
.join(',\n');
277+
278+
return `// AUTO-GENERATED - DO NOT EDIT
279+
// This file is generated by the plugin discovery esbuild plugin
280+
281+
export const RESOURCE_LOADERS = {
282+
${loaderEntries}
283+
};
284+
285+
export type ResourceName = keyof typeof RESOURCE_LOADERS;
286+
`;
225287
}

0 commit comments

Comments
 (0)