aem-mcp-server 1.0.0

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.
@@ -0,0 +1,152 @@
1
+ export interface AEMErrorDetails {
2
+ [key: string]: any;
3
+ }
4
+
5
+ export class AEMOperationError extends Error {
6
+ code: string;
7
+ details?: AEMErrorDetails;
8
+ recoverable?: boolean;
9
+ retryAfter?: number;
10
+
11
+ constructor(error: {
12
+ code: string;
13
+ message: string;
14
+ details?: AEMErrorDetails;
15
+ recoverable?: boolean;
16
+ retryAfter?: number;
17
+ }) {
18
+ super(error.message);
19
+ this.name = 'AEMOperationError';
20
+ this.code = error.code;
21
+ this.details = error.details;
22
+ this.recoverable = error.recoverable;
23
+ this.retryAfter = error.retryAfter;
24
+ }
25
+ }
26
+
27
+ export const AEM_ERROR_CODES = {
28
+ CONNECTION_FAILED: 'CONNECTION_FAILED',
29
+ TIMEOUT: 'TIMEOUT',
30
+ AUTHENTICATION_FAILED: 'AUTHENTICATION_FAILED',
31
+ UNAUTHORIZED: 'UNAUTHORIZED',
32
+ INVALID_PATH: 'INVALID_PATH',
33
+ INVALID_COMPONENT_TYPE: 'INVALID_COMPONENT_TYPE',
34
+ INVALID_LOCALE: 'INVALID_LOCALE',
35
+ INVALID_PARAMETERS: 'INVALID_PARAMETERS',
36
+ RESOURCE_NOT_FOUND: 'RESOURCE_NOT_FOUND',
37
+ COMPONENT_NOT_FOUND: 'COMPONENT_NOT_FOUND',
38
+ PAGE_NOT_FOUND: 'PAGE_NOT_FOUND',
39
+ UPDATE_FAILED: 'UPDATE_FAILED',
40
+ VALIDATION_FAILED: 'VALIDATION_FAILED',
41
+ REPLICATION_FAILED: 'REPLICATION_FAILED',
42
+ QUERY_FAILED: 'QUERY_FAILED',
43
+ INSUFFICIENT_PERMISSIONS: 'INSUFFICIENT_PERMISSIONS',
44
+ SYSTEM_ERROR: 'SYSTEM_ERROR',
45
+ RATE_LIMITED: 'RATE_LIMITED',
46
+ } as const;
47
+
48
+ export function createAEMError(
49
+ code: string,
50
+ message: string,
51
+ details?: AEMErrorDetails,
52
+ recoverable = false,
53
+ retryAfter?: number
54
+ ): AEMOperationError {
55
+ return new AEMOperationError({ code, message, details, recoverable, retryAfter });
56
+ }
57
+
58
+ export function handleAEMHttpError(error: any, operation: string): AEMOperationError {
59
+ if (error.response) {
60
+ const status = error.response.status;
61
+ const data = error.response.data;
62
+ switch (status) {
63
+ case 401:
64
+ return createAEMError(AEM_ERROR_CODES.AUTHENTICATION_FAILED, 'Authentication failed. Check AEM credentials.', { status, data });
65
+ case 403:
66
+ return createAEMError(AEM_ERROR_CODES.INSUFFICIENT_PERMISSIONS, 'Insufficient permissions for this operation.', { status, data, operation });
67
+ case 404:
68
+ return createAEMError(AEM_ERROR_CODES.RESOURCE_NOT_FOUND, 'Resource not found in AEM.', { status, data, operation });
69
+ case 429:
70
+ const retryAfter = error.response.headers['retry-after'];
71
+ return createAEMError(AEM_ERROR_CODES.RATE_LIMITED, 'Rate limit exceeded. Please try again later.', { status, data }, true, retryAfter ? parseInt(retryAfter) * 1000 : 60000);
72
+ case 500:
73
+ case 502:
74
+ case 503:
75
+ return createAEMError(AEM_ERROR_CODES.SYSTEM_ERROR, 'AEM system error. Please try again later.', { status, data }, true, 30000);
76
+ default:
77
+ return createAEMError(AEM_ERROR_CODES.SYSTEM_ERROR, `HTTP ${status}: ${data?.message || 'Unknown error'}`, { status, data, operation });
78
+ }
79
+ } else if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND') {
80
+ return createAEMError(AEM_ERROR_CODES.CONNECTION_FAILED, 'Cannot connect to AEM instance. Check host and network.', { originalError: error.message }, true, 5000);
81
+ } else if (error.code === 'ETIMEDOUT') {
82
+ return createAEMError(AEM_ERROR_CODES.TIMEOUT, 'Request to AEM timed out.', { originalError: error.message }, true, 10000);
83
+ } else {
84
+ return createAEMError(AEM_ERROR_CODES.SYSTEM_ERROR, `Unexpected error during ${operation}: ${error.message}`, { originalError: error.message });
85
+ }
86
+ }
87
+
88
+ export async function safeExecute<T>(operation: () => Promise<T>, operationName: string, maxRetries = 3): Promise<T> {
89
+ let lastError: any;
90
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
91
+ try {
92
+ return await operation();
93
+ } catch (error: any) {
94
+ lastError = error instanceof AEMOperationError
95
+ ? error
96
+ : handleAEMHttpError(error, operationName);
97
+ if (!lastError.recoverable || attempt === maxRetries) {
98
+ break;
99
+ }
100
+ const delay = lastError.retryAfter || Math.pow(2, attempt) * 1000;
101
+ // eslint-disable-next-line no-console
102
+ console.warn(`[${operationName}] Attempt ${attempt} failed, retrying in ${delay}ms:`, lastError.message);
103
+ await new Promise(resolve => setTimeout(resolve, delay));
104
+ }
105
+ }
106
+ throw lastError;
107
+ }
108
+
109
+ export function validateComponentOperation(locale: string, pagePath: string, component: string, props: any): void {
110
+ const errors: string[] = [];
111
+ if (!locale || typeof locale !== 'string') {
112
+ errors.push('Locale is required and must be a string');
113
+ }
114
+ if (!pagePath || typeof pagePath !== 'string') {
115
+ errors.push('Page path is required and must be a string');
116
+ } else if (!pagePath.startsWith('/content')) {
117
+ errors.push('Page path must start with /content');
118
+ }
119
+ if (!component || typeof component !== 'string') {
120
+ errors.push('Component type is required and must be a string');
121
+ }
122
+ if (!props || typeof props !== 'object') {
123
+ errors.push('Component properties are required and must be an object');
124
+ }
125
+ if (errors.length > 0) {
126
+ throw createAEMError(AEM_ERROR_CODES.INVALID_PARAMETERS, 'Invalid component operation parameters', { errors });
127
+ }
128
+ }
129
+
130
+ export function createSuccessResponse<T>(data: T, operation: string) {
131
+ return {
132
+ success: true,
133
+ operation,
134
+ timestamp: new Date().toISOString(),
135
+ data
136
+ };
137
+ }
138
+
139
+ export function createErrorResponse(error: AEMOperationError, operation: string) {
140
+ return {
141
+ success: false,
142
+ operation,
143
+ timestamp: new Date().toISOString(),
144
+ error: {
145
+ code: error.code,
146
+ message: error.message,
147
+ details: error.details,
148
+ recoverable: error.recoverable,
149
+ retryAfter: error.retryAfter
150
+ }
151
+ };
152
+ }
package/src/config.ts ADDED
@@ -0,0 +1,12 @@
1
+ const SERVER_PORT = parseInt(process.env.SERVER_PORT || '3000', 10);
2
+ const MCP_PORT = parseInt(process.env.MCP_PORT || '8080', 10);
3
+ const MCP_USERNAME = process.env.MCP_USERNAME || 'admin';
4
+ const MCP_PASSWORD = process.env.MCP_PASSWORD || 'admin';
5
+ const APP_VERSION = process.env.npm_package_version || '1.0.0';
6
+
7
+ export const config = {
8
+ SERVER_PORT,
9
+ MCP_USERNAME,
10
+ MCP_PASSWORD,
11
+ APP_VERSION
12
+ }
@@ -0,0 +1,30 @@
1
+ import swaggerUi from 'swagger-ui-express';
2
+ import swaggerJSDoc from 'swagger-jsdoc';
3
+ import { Express, Request, Response } from 'express';
4
+ import { config } from '../config.js';
5
+ import { apiPaths } from './api.spec.js';
6
+
7
+ const swaggerDefinition = {
8
+ openapi: '3.0.0',
9
+ info: {
10
+ title: 'AEM MCP Gateway API',
11
+ version: '1.0.0',
12
+ description: 'API documentation for the AEM MCP Gateway Server',
13
+ },
14
+ servers: [
15
+ { url: `http://localhost:${config.SERVER_PORT}` },
16
+ ],
17
+ };
18
+
19
+ const options = {
20
+ swaggerDefinition,
21
+ apis: [],
22
+ };
23
+
24
+ const openapiSpec: any = swaggerJSDoc(options);
25
+ openapiSpec.paths = apiPaths;
26
+
27
+ export const useExplorer = (app: Express) => {
28
+ app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(openapiSpec));
29
+ app.get('/openapi.json', (req: Request, res: Response) => { res.json(openapiSpec); });
30
+ }
@@ -0,0 +1,79 @@
1
+ export const apiPaths = {
2
+ '/mcp': {
3
+ post: {
4
+ summary: 'JSON-RPC endpoint for MCP calls',
5
+ description: 'Call MCP methods using JSON-RPC 2.0. The method and params must be provided in the request body.',
6
+ requestBody: {
7
+ required: true,
8
+ content: {
9
+ 'application/json': {
10
+ schema: {
11
+ type: 'object',
12
+ properties: {
13
+ jsonrpc: { type: 'string', example: '2.0' },
14
+ id: { type: 'integer', example: 1 },
15
+ method: { type: 'string', example: 'listMethods' },
16
+ params: { type: 'object' },
17
+ },
18
+ required: ['jsonrpc', 'id', 'method'],
19
+ },
20
+ },
21
+ },
22
+ },
23
+ responses: {
24
+ 200: {
25
+ description: 'JSON-RPC response',
26
+ content: {
27
+ 'application/json': {
28
+ schema: {
29
+ type: 'object',
30
+ properties: {
31
+ jsonrpc: { type: 'string', example: '2.0' },
32
+ id: { type: 'integer', example: 1 },
33
+ result: { type: 'object' },
34
+ error: { type: 'object' },
35
+ },
36
+ },
37
+ },
38
+ },
39
+ },
40
+ },
41
+ },
42
+ },
43
+ '/mcp/methods': {
44
+ get: {
45
+ summary: 'List all available MCP methods',
46
+ description: 'Returns a list of all available MCP methods and their parameters.',
47
+ responses: {
48
+ 200: {
49
+ description: 'A list of MCP methods',
50
+ content: {
51
+ 'application/json': {
52
+ schema: {
53
+ type: 'object',
54
+ properties: {
55
+ methods: {
56
+ type: 'array',
57
+ items: {
58
+ type: 'object',
59
+ properties: {
60
+ name: { type: 'string' },
61
+ description: { type: 'string' },
62
+ parameters: {
63
+ type: 'array',
64
+ items: { type: 'string' },
65
+ },
66
+ },
67
+ },
68
+ },
69
+ total: { type: 'integer' },
70
+ timestamp: { type: 'string', format: 'date-time' },
71
+ },
72
+ },
73
+ },
74
+ },
75
+ },
76
+ },
77
+ },
78
+ },
79
+ };
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ import { config } from './config.js';
2
+ import { startServer } from './server/app.server.js';
3
+
4
+ startServer(config.SERVER_PORT);
@@ -0,0 +1,158 @@
1
+ import { AEMConnector } from '../aem/aem.connector.js';
2
+ import { config } from '../config.js';
3
+
4
+ export class MCPRequestHandler {
5
+ aemConnector: AEMConnector;
6
+
7
+ constructor(aemConnector: AEMConnector) {
8
+ this.aemConnector = aemConnector;
9
+ }
10
+
11
+ async handleRequest(method: string, params: any) {
12
+ try {
13
+ switch (method) {
14
+ case 'validateComponent':
15
+ return await this.aemConnector.validateComponent(params);
16
+ case 'updateComponent':
17
+ return await this.aemConnector.updateComponent(params);
18
+ case 'undoChanges':
19
+ return await this.aemConnector.undoChanges(params);
20
+ case 'scanPageComponents':
21
+ return await this.aemConnector.scanPageComponents(params.pagePath);
22
+ case 'fetchSites':
23
+ return await this.aemConnector.fetchSites();
24
+ case 'fetchLanguageMasters':
25
+ return await this.aemConnector.fetchLanguageMasters(params.site);
26
+ case 'fetchAvailableLocales':
27
+ return await this.aemConnector.fetchAvailableLocales(params.site, params.languageMasterPath);
28
+ case 'replicateAndPublish':
29
+ return await this.aemConnector.replicateAndPublish(params.selectedLocales, params.componentData, params.localizedOverrides);
30
+ case 'getAllTextContent':
31
+ return await this.aemConnector.getAllTextContent(params.pagePath);
32
+ case 'getPageTextContent':
33
+ return await this.aemConnector.getPageTextContent(params.pagePath);
34
+ case 'getPageImages':
35
+ return await this.aemConnector.getPageImages(params.pagePath);
36
+ case 'updateImagePath':
37
+ return await this.aemConnector.updateImagePath(params.componentPath, params.newImagePath);
38
+ case 'getPageContent':
39
+ return await this.aemConnector.getPageContent(params.pagePath);
40
+ case 'listPages':
41
+ return await this.aemConnector.listPages(params.siteRoot || params.path || '/content', params.depth || 1, params.limit || 20);
42
+ case 'getNodeContent':
43
+ return await this.aemConnector.getNodeContent(params.path, params.depth || 1);
44
+ case 'listChildren':
45
+ return await this.aemConnector.listChildren(params.path);
46
+ case 'getPageProperties':
47
+ return await this.aemConnector.getPageProperties(params.pagePath);
48
+ case 'searchContent':
49
+ return await this.aemConnector.searchContent(params);
50
+ case 'executeJCRQuery':
51
+ return await this.aemConnector.executeJCRQuery(params.query, params.limit);
52
+ case 'getAssetMetadata':
53
+ return await this.aemConnector.getAssetMetadata(params.assetPath);
54
+ case 'getStatus':
55
+ return this.getWorkflowStatus(params.workflowId);
56
+ case 'listMethods':
57
+ return { methods: this.getAvailableMethods() };
58
+ case 'enhancedPageSearch':
59
+ return await this.aemConnector.searchContent({
60
+ fulltext: params.searchTerm,
61
+ path: params.basePath,
62
+ type: 'cq:Page',
63
+ limit: 20
64
+ });
65
+ case 'createPage':
66
+ return await this.aemConnector.createPage(params);
67
+ case 'deletePage':
68
+ return await this.aemConnector.deletePage(params);
69
+ case 'createComponent':
70
+ return await this.aemConnector.createComponent(params);
71
+ case 'deleteComponent':
72
+ return await this.aemConnector.deleteComponent(params);
73
+ case 'unpublishContent':
74
+ return await this.aemConnector.unpublishContent(params);
75
+ case 'activatePage':
76
+ return await this.aemConnector.activatePage(params);
77
+ case 'deactivatePage':
78
+ return await this.aemConnector.deactivatePage(params);
79
+ case 'uploadAsset':
80
+ return await this.aemConnector.uploadAsset(params);
81
+ case 'updateAsset':
82
+ return await this.aemConnector.updateAsset(params);
83
+ case 'deleteAsset':
84
+ return await this.aemConnector.deleteAsset(params);
85
+ case 'getTemplates':
86
+ return await this.aemConnector.getTemplates(params.sitePath);
87
+ case 'getTemplateStructure':
88
+ return await this.aemConnector.getTemplateStructure(params.templatePath);
89
+ default:
90
+ throw new Error(`Unknown method: ${method}`);
91
+ }
92
+ } catch (error: any) {
93
+ return { error: error.message, method, params };
94
+ }
95
+ }
96
+
97
+ getWorkflowStatus(workflowId: string) {
98
+ return {
99
+ success: true,
100
+ workflowId: workflowId,
101
+ status: 'completed',
102
+ message: 'Mock workflow status - always returns completed',
103
+ timestamp: new Date().toISOString()
104
+ };
105
+ }
106
+
107
+ getAvailableMethods() {
108
+ return [
109
+ { name: 'validateComponent', description: 'Validate component changes before applying them', parameters: ['locale', 'page_path', 'component', 'props'] },
110
+ { name: 'updateComponent', description: 'Update component properties in AEM', parameters: ['componentPath', 'properties'] },
111
+ { name: 'undoChanges', description: 'Undo the last component changes', parameters: ['job_id'] },
112
+ { name: 'scanPageComponents', description: 'Scan a page to discover all components and their properties', parameters: ['pagePath'] },
113
+ { name: 'fetchSites', description: 'Get all available sites in AEM', parameters: [] },
114
+ { name: 'fetchLanguageMasters', description: 'Get language masters for a specific site', parameters: ['site'] },
115
+ { name: 'fetchAvailableLocales', description: 'Get available locales for a site and language master', parameters: ['site', 'languageMasterPath'] },
116
+ { name: 'replicateAndPublish', description: 'Replicate and publish content to selected locales', parameters: ['selectedLocales', 'componentData', 'localizedOverrides'] },
117
+ { name: 'getAllTextContent', description: 'Get all text content from a page including titles, text components, and descriptions', parameters: ['pagePath'] },
118
+ { name: 'getPageTextContent', description: 'Get text content from a specific page', parameters: ['pagePath'] },
119
+ { name: 'getPageImages', description: 'Get all images from a page, including those within Experience Fragments', parameters: ['pagePath'] },
120
+ { name: 'updateImagePath', description: 'Update the image path for an image component and verify the update', parameters: ['componentPath', 'newImagePath'] },
121
+ { name: 'getPageContent', description: 'Get all content from a page including Experience Fragments and Content Fragments', parameters: ['pagePath'] },
122
+ { name: 'listPages', description: 'List all pages under a site root', parameters: ['siteRoot', 'depth', 'limit'] },
123
+ { name: 'getNodeContent', description: 'Legacy: Get JCR node content', parameters: ['path', 'depth'] },
124
+ { name: 'listChildren', description: 'Legacy: List child nodes', parameters: ['path'] },
125
+ { name: 'getPageProperties', description: 'Get page properties', parameters: ['pagePath'] },
126
+ { name: 'searchContent', description: 'Search content using Query Builder', parameters: ['type', 'fulltext', 'path', 'limit'] },
127
+ { name: 'executeJCRQuery', description: 'Execute JCR query', parameters: ['query', 'limit'] },
128
+ { name: 'getAssetMetadata', description: 'Get asset metadata', parameters: ['assetPath'] },
129
+ { name: 'getStatus', description: 'Get workflow status by ID', parameters: ['workflowId'] },
130
+ { name: 'listMethods', description: 'Get list of available MCP methods', parameters: [] },
131
+ { name: 'enhancedPageSearch', description: 'Intelligent page search with comprehensive fallback strategies and cross-section search', parameters: ['searchTerm', 'basePath', 'includeAlternateLocales'] },
132
+ { name: 'createPage', description: 'Create a new page in AEM', parameters: ['parentPath', 'title', 'template', 'name', 'properties'] },
133
+ { name: 'deletePage', description: 'Delete a page from AEM', parameters: ['pagePath', 'force'] },
134
+ { name: 'createComponent', description: 'Create a new component on a page', parameters: ['pagePath', 'componentType', 'resourceType', 'properties', 'name'] },
135
+ { name: 'deleteComponent', description: 'Delete a component from AEM', parameters: ['componentPath', 'force'] },
136
+ { name: 'unpublishContent', description: 'Unpublish content from the publish environment', parameters: ['contentPaths', 'unpublishTree'] },
137
+ { name: 'activatePage', description: 'Activate (publish) a single page', parameters: ['pagePath', 'activateTree'] },
138
+ { name: 'deactivatePage', description: 'Deactivate (unpublish) a single page', parameters: ['pagePath', 'deactivateTree'] },
139
+ { name: 'uploadAsset', description: 'Upload a new asset to AEM DAM', parameters: ['parentPath', 'fileName', 'fileContent', 'mimeType', 'metadata'] },
140
+ { name: 'updateAsset', description: 'Update an existing asset in AEM DAM', parameters: ['assetPath', 'metadata', 'fileContent', 'mimeType'] },
141
+ { name: 'deleteAsset', description: 'Delete an asset from AEM DAM', parameters: ['assetPath', 'force'] },
142
+ { name: 'getTemplates', description: 'Get available page templates', parameters: ['sitePath'] },
143
+ { name: 'getTemplateStructure', description: 'Get detailed structure of a specific template', parameters: ['templatePath'] },
144
+ ];
145
+ }
146
+
147
+ async handleHealthCheck() {
148
+ const aemConnected = await this.aemConnector.testConnection();
149
+ return {
150
+ status: 'healthy',
151
+ aem: aemConnected ? 'connected' : 'disconnected',
152
+ mcp: 'ready',
153
+ timestamp: new Date().toISOString(),
154
+ version: config.APP_VERSION || '1.0.0',
155
+ port: config.SERVER_PORT,
156
+ };
157
+ }
158
+ }
@@ -0,0 +1,73 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { Request, Response } from 'express';
3
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
4
+ import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
5
+ import { transports } from './mcp.transports.js';
6
+ import { createMCPServer } from './mcp.server.js';
7
+
8
+ export const handleRequest = async (req: Request, res: Response) => {
9
+ console.log('Received MCP request:', req.body);
10
+ const { jsonrpc, id, method, params } = req.body;
11
+ if (jsonrpc !== '2.0' || !method) {
12
+ res.status(400).json({
13
+ jsonrpc: '2.0',
14
+ id: id || null,
15
+ error: { code: -32600, message: 'Invalid Request', data: 'Must be valid JSON-RPC 2.0' },
16
+ });
17
+ return;
18
+ }
19
+ try {
20
+ // Check for existing session ID
21
+ const sessionId = req.headers['mcp-session-id'] as string | undefined;
22
+ let transport: StreamableHTTPServerTransport;
23
+
24
+ if (sessionId && transports[sessionId]) {
25
+ // Reuse existing transport
26
+ transport = transports[sessionId]
27
+ } else if (!sessionId && isInitializeRequest(req.body)) {
28
+ // New initialization request - use JSON response mode
29
+ transport = new StreamableHTTPServerTransport({
30
+ sessionIdGenerator: () => randomUUID(),
31
+ enableJsonResponse: true, // Enable JSON response mode
32
+ onsessioninitialized: (sessionId) => {
33
+ // Store the transport by session ID when session is initialized
34
+ // This avoids race conditions where requests might come in before the session is stored
35
+ console.log(`Session initialized with ID: ${sessionId}`);
36
+ transports[sessionId] = transport;
37
+ }
38
+ });
39
+
40
+ // Connect the transport to the MCP server BEFORE handling the request
41
+ const server = createMCPServer();
42
+ await server.connect(transport);
43
+ await transport.handleRequest(req, res, req.body);
44
+ return; // Already handled
45
+ } else {
46
+ // Invalid request - no session ID or not initialization request
47
+ res.status(400).json({
48
+ jsonrpc: '2.0',
49
+ error: {
50
+ code: -32000,
51
+ message: 'Bad Request: No valid session ID provided',
52
+ },
53
+ id: null,
54
+ });
55
+ return;
56
+ }
57
+
58
+ // Handle the request with existing transport - no need to reconnect
59
+ await transport.handleRequest(req, res, req.body);
60
+ } catch (error) {
61
+ console.error('Error handling MCP request:', error);
62
+ if (!res.headersSent) {
63
+ res.status(500).json({
64
+ jsonrpc: '2.0',
65
+ error: {
66
+ code: -32603,
67
+ message: 'Internal server error',
68
+ },
69
+ id: null,
70
+ });
71
+ }
72
+ }
73
+ };
@@ -0,0 +1,51 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { CallToolResult, CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
4
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
5
+ import { tools } from './mcp.tools.js';
6
+ import { AEMConnector } from '../aem/aem.connector.js';
7
+ import { MCPRequestHandler } from './mcp.aem-handler.js';
8
+
9
+ export const createMCPServer = () => {
10
+ const aemConnector = new AEMConnector();
11
+ const mcpHandler = new MCPRequestHandler(aemConnector);
12
+
13
+ const server = new Server({
14
+ name: 'aem-mcp-server',
15
+ version: '1.0.0',
16
+ }, {
17
+ capabilities: {
18
+ resources: {},
19
+ tools: {},
20
+ prompts: {},
21
+ },
22
+ });
23
+
24
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
25
+ return { tools };
26
+ });
27
+
28
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29
+ const { name, arguments: args } = request.params;
30
+
31
+ if (!args) {
32
+ return {
33
+ content: [
34
+ { type: 'text', text: 'Error: No arguments provided' },
35
+ ],
36
+ isError: true,
37
+ };
38
+ }
39
+ try {
40
+ const result = await mcpHandler.handleRequest(name, args);
41
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
42
+ } catch (error: any) {
43
+ return {
44
+ content: [{ type: 'text', text: `Error: ${error.message}` }],
45
+ isError: true,
46
+ };
47
+ }
48
+ });
49
+
50
+ return server;
51
+ }