10

Extending AI Capabilities: Building and Connecting a Custom MCP Server

In the previous article, we explored the fundamentals of the Model Context Protocol (MCP) and how it enables AI clients…

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?


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.

Ashish Sharma

I’ve always believed that collaboration is the engine of progress. While many say knowledge is power, I believe the true power lies in its distribution. To that end, I am building a curated knowledge base of my professional journey—refined by AI for maximum clarity and depth. Whether you’re here to master a new skill or sharpen an existing one, my goal is to provide a roadmap for your success. This collection will evolve as I do, and I welcome your insights and dialogue as we grow together.

Leave a Reply

Your email address will not be published. Required fields are marked *