In the previous article, we explored the fundamentals of the Model Context Protocol (MCP) and how it enables AI clients (like GitHub Copilot or custom agents) to interact with external systems in a structured, tool-driven way.
Now, let’s take the next step — building a real MCP server using Node.js and connecting it to an MCP client to unlock powerful capabilities such as:
- Calling APIs
- Fetching CMS data (AEM in this case)
- Running structured queries
- Enabling AI-assisted debugging and exploration
- Extending AI beyond its default limitations
What We’re Building
We will build an MCP server that:
- Connects to Adobe Experience Manager (AEM)
- Exposes tools like:
- Fetching JCR nodes
- Querying content using QueryBuilder
- Can be consumed by MCP-compatible clients such as:
- MCP Inspector
- GitHub Copilot
- Microsoft Copilot Studio
- Anthropic Claude
- Cursor IDE
Project Setup
package.json
Project needs just three key dependencies:
{
"name": "aem-mcp-server",
"version": "1.0.0",
"main": "server.js",
"type": "module",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.29.0",
"node-fetch": "^3.3.2",
"zod": "^4.4.3"
}
}
Why these dependencies?
- @modelcontextprotocol/sdk → Core library for building MCP servers
- node-fetch → Used for making HTTP requests to AEM APIs
- zod → Used for making validating schema
MCP Server Implementation
Step 1: Initialize MCP Server
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import fetch from "node-fetch";
import { z } from "zod";
...
- McpServer → Defines tools and capabilities
- StdioServerTransport → Communicates via STDIO (used by Copilot & Inspector)
- zod → Schema validation for tool inputs
Step 2: Configure AEM Connection
const AEM_BASE_URL = "http://localhost:4502";
const AEM_USERNAME = "admin";
const AEM_PASSWORD = "admin";
const AUTH_HEADER =
"Basic " +
Buffer.from(${AEM_USERNAME}:${AEM_PASSWORD}).toString("base64");
...
This allows secure authentication with AEM APIs.
Step 3: Create MCP Server
const server = new McpServer(
{
name: "aem-mcp-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
...
This defines your MCP server metadata and declares support for tools.
Tool 1: Fetch JCR Node JSON
This tool retrieves raw JSON data from a given JCR path.
server.tool(
"get-jcr-node",
{
path: z.string().describe("JCR path e.g. /content/site/en/home"),
},
async ({ path }) => {
const url = ${AEM_BASE_URL}${path}.json;
const res = await fetch(url, {
headers: { Authorization: AUTH_HEADER },
});
if (!res.ok) {
throw new Error(AEM request failed: ${res.status});
}
const json = await res.json();
return {
content: [
{
type: "text",
text: JSON.stringify(json, null, 2),
},
],
};
}
);
What it enables
- Inspect page/content structure
- Debug content issues
- Let AI understand AEM structure dynamically
Tool 2: Query AEM Content (QueryBuilder)
This tool enables structured content search.
server.tool(
"query-aem-data",
{
path: z.string().default("/content"),
type: z.string().optional(),
property: z.string().optional(),
propertyValue: z.string().optional(),
limit: z.number().int().default(50),
returnPagePaths: z.boolean().default(false),
},
async ({ path, type, property, propertyValue, limit, returnPagePaths }) => {
const params = new URLSearchParams();
params.set("path", path);
params.set("p.limit", String(limit));
params.set("p.hits", "full");
if (type) params.set("type", type);
if (property) {
params.set("1_property", property);
if (propertyValue !== undefined) {
params.set("1_property.value", propertyValue);
}
}
const url = ${AEM_BASE_URL}/bin/querybuilder.json?${params.toString()};
const res = await fetch(url, {
headers: { Authorization: AUTH_HEADER },
});
if (!res.ok) {
const errorText = await res.text();
throw new Error(Query failed: ${res.status} - ${errorText.slice(0, 500)});
}
const data = await res.json();
const hits = Array.isArray(data.hits) ? data.hits : [];
const paths = hits
.map((h) => h.path || h["jcr:path"])
.filter(Boolean)
.map((p) =>
returnPagePaths ? p.replace(//jcr:content$/, "") : p
);
return {
content: [
{
type: "text",
text: JSON.stringify(
{
total: Number(data.total) || paths.length,
count: paths.length,
paths,
},
null,
2
),
},
],
};
}
);
What it enables
- Search for content programmatically
- Filter by properties (e.g., hideInNav)
- Allow AI to answer queries like: “Find all pages under /content that are hidden in navigation”
Final Step: Start the MCP Server
const transport = new StdioServerTransport();
await server.connect(transport);
Testing with MCP Inspector
Run:
npx @modelcontextprotocol/inspector node server.js
What you get:
- Interactive UI to test tools
- Validate inputs/outputs
- Debug tool responses
Integrating with GitHub Copilot
mcp.json configuration in VS Code:
{
"servers": {
"aem-local": {
"type": "stdio",
"command": "node",
"args": ["<path to project>/server.js"]
}
}
}
After setup:
- Copilot can call your tools
- AI becomes aware of live AEM data
- Responses become context-aware and dynamic
Using with Copilot Studio
You can also:
- Connect this MCP server to a custom chatbot
- Build enterprise AI assistants
- Combine MCP tools with workflows and prompts
Real-World Applications
1. Local Debugging & Secure Data Access
- Keep sensitive data inside local network
- Avoid exposing APIs externally
- Perfect for enterprise environments
2. CMS Exploration via AI
Instead of manually browsing CRXDE:
“Show me all pages under /content/site/en with template X”
3. API Integration Layer
You can extend this further by connecting:
- REST APIs
- GraphQL endpoints
- Internal microservices
4. Database Connectivity
MCP server can act as a bridge to:
- MySQL / PostgreSQL
- MongoDB
- Enterprise data systems
Allowing queries like:
“Fetch top 10 users who logged in last week”
5. AI-Powered Operations
- Trigger workflows
- Modify content
- Automate repetitive tasks
6. Documentation & Knowledge Layer
MCP tools can:
- Explain system structure
- Generate documentation dynamically
- Act as a knowledge assistant
Final Thoughts
By building a custom MCP server:
- You extend AI capabilities beyond training data
- You integrate enterprise systems securely
- You unlock true AI-driven development workflows
This AEM example is just the beginning — the same pattern can be applied to any system, API, or database.