#
tokens: 5930/50000 11/11 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── eslint.config.mjs
├── LICENSE
├── nodemon.json
├── package.json
├── README.md
├── src
│   ├── common
│   │   ├── constants.ts
│   │   ├── tools.ts
│   │   ├── types.ts
│   │   └── utils.ts
│   └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | node_modules
 2 | build
 3 | 
 4 | .env
 5 | .env.local
 6 | .env.development
 7 | .env.production
 8 | .env.test
 9 | .env.test.local
10 | .env.production.local
11 | 
12 | 
13 | yarn.lock
14 | package-lock.json
15 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Custom Context MCP Server
  2 | 
  3 | This Model Context Protocol (MCP) server provides tools for structuring and extracting data from text according to JSON templates.
  4 | 
  5 | ## Features
  6 | 
  7 | ### Text-to-JSON Transformation
  8 | 
  9 | - Group and structure text based on JSON templates with placeholders
 10 | - Extract information from AI-generated text into structured JSON formats
 11 | - Support for any arbitrary JSON structure with nested placeholders
 12 | - Intelligent extraction of key-value pairs from text
 13 | - Process AI outputs into structured data for downstream applications
 14 | 
 15 | ## Getting Started
 16 | 
 17 | ### Installation
 18 | 
 19 | ```bash
 20 | npm install
 21 | ```
 22 | 
 23 | ### Running the server
 24 | 
 25 | ```bash
 26 | npm start
 27 | ```
 28 | 
 29 | For development with hot reloading:
 30 | 
 31 | ```bash
 32 | npm run dev:watch
 33 | ```
 34 | 
 35 | ## Usage
 36 | 
 37 | This MCP server provides two main tools:
 38 | 
 39 | ### 1. Group Text by JSON (`group-text-by-json`)
 40 | 
 41 | This tool takes a JSON template with placeholders and generates a prompt for an AI to group text according to the template's structure.
 42 | 
 43 | ```json
 44 | {
 45 | 	"template": "{ \"type\": \"<type>\", \"text\": \"<text>\" }"
 46 | }
 47 | ```
 48 | 
 49 | The tool analyzes the template, extracts placeholder keys, and returns a prompt that guides the AI to extract information in a key-value format.
 50 | 
 51 | ### 2. Text to JSON (`text-to-json`)
 52 | 
 53 | This tool takes the grouped text output from the previous step and converts it into a structured JSON object based on the original template.
 54 | 
 55 | ```json
 56 | {
 57 | 	"template": "{ \"type\": \"<type>\", \"text\": \"<text>\" }",
 58 | 	"text": "type: pen\ntext: This is a blue pen"
 59 | }
 60 | ```
 61 | 
 62 | It extracts key-value pairs from the text and structures them according to the template.
 63 | 
 64 | ## Example Workflow
 65 | 
 66 | 1. **Define a JSON template with placeholders:**
 67 | 
 68 |    ```json
 69 |    {
 70 |    	"item": {
 71 |    		"name": "<name>",
 72 |    		"price": "<price>",
 73 |    		"description": "<description>"
 74 |    	}
 75 |    }
 76 |    ```
 77 | 
 78 | 2. **Use `group-text-by-json` to create a prompt for AI:**
 79 | 
 80 |    - The tool identifies placeholder keys: name, price, description
 81 |    - Generates a prompt instructing the AI to group information by these keys
 82 | 
 83 | 3. **Send the prompt to an AI model and receive grouped text:**
 84 | 
 85 |    ```
 86 |    name: Blue Pen
 87 |    price: $2.99
 88 |    description: A smooth-writing ballpoint pen with blue ink
 89 |    ```
 90 | 
 91 | 4. **Use `text-to-json` to convert the grouped text to JSON:**
 92 |    - Result:
 93 |    ```json
 94 |    {
 95 |    	"item": {
 96 |    		"name": "Blue Pen",
 97 |    		"price": "$2.99",
 98 |    		"description": "A smooth-writing ballpoint pen with blue ink"
 99 |    	}
100 |    }
101 |    ```
102 | 
103 | ## Template Format
104 | 
105 | Templates can include placeholders anywhere within a valid JSON structure:
106 | 
107 | - Use angle brackets to define placeholders: `<name>`, `<type>`, `<price>`, etc.
108 | - The template must be a valid JSON string
109 | - Placeholders can be at any level of nesting
110 | - Supports complex nested structures
111 | 
112 | Example template with nested placeholders:
113 | 
114 | ```json
115 | {
116 | 	"product": {
117 | 		"details": {
118 | 			"name": "<name>",
119 | 			"category": "<category>"
120 | 		},
121 | 		"pricing": {
122 | 			"amount": "<price>",
123 | 			"currency": "USD"
124 | 		}
125 | 	},
126 | 	"metadata": {
127 | 		"timestamp": "2023-09-01T12:00:00Z"
128 | 	}
129 | }
130 | ```
131 | 
132 | ## Implementation Details
133 | 
134 | The server works by:
135 | 
136 | 1. Analyzing JSON templates to extract placeholder keys
137 | 2. Generating prompts that guide AI models to extract information by these keys
138 | 3. Parsing AI-generated text to extract key-value pairs
139 | 4. Reconstructing JSON objects based on the original template structure
140 | 
141 | ## Development
142 | 
143 | ### Prerequisites
144 | 
145 | - Node.js v18 or higher
146 | - npm or yarn
147 | 
148 | ### Build and Run
149 | 
150 | ```bash
151 | # Install dependencies
152 | npm install
153 | 
154 | # Build the project
155 | npm run build
156 | 
157 | # Run the server
158 | npm start
159 | 
160 | # Development with hot reloading
161 | npm run dev:watch
162 | ```
163 | 
164 | ### Custom Hot Reloading
165 | 
166 | This project includes a custom hot reloading setup that combines:
167 | 
168 | - **nodemon**: Watches for file changes in the src directory and rebuilds TypeScript files
169 | - **browser-sync**: Automatically refreshes the browser when build files change
170 | - **Concurrent execution**: Runs both services simultaneously with output synchronization
171 | 
172 | The setup is configured in:
173 | 
174 | - `nodemon.json`: Controls TypeScript watching and rebuilding
175 | - `package.json`: Uses concurrently to run nodemon and browser-sync together
176 | 
177 | To use the custom hot reloading feature:
178 | 
179 | ```bash
180 | npm run dev:watch
181 | ```
182 | 
183 | This creates a development environment where:
184 | 
185 | 1. TypeScript files are automatically rebuilt when changed
186 | 2. The MCP server restarts with the updated code
187 | 3. Connected browsers refresh to show the latest changes
188 | 
189 | ### Using with MCP Inspector
190 | 
191 | You can use the MCP Inspector for debugging:
192 | 
193 | ```bash
194 | npm run dev
195 | ```
196 | 
197 | This runs the server with the MCP Inspector for visual debugging of requests and responses.
198 | 
```

--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------

```
 1 | import { defineConfig } from "eslint/config";
 2 | import globals from "globals";
 3 | import tseslint from "typescript-eslint";
 4 | 
 5 | 
 6 | export default defineConfig([
 7 |   { files: ["**/*.{js,mjs,cjs,ts}"] },
 8 |   { files: ["**/*.{js,mjs,cjs,ts}"], languageOptions: { globals: globals.browser } },
 9 |   tseslint.configs.recommended,
10 | ]);
```

--------------------------------------------------------------------------------
/nodemon.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 | 	"watch": ["src/**/*.ts"],
 3 | 	"ext": "ts",
 4 | 	"ignore": ["src/**/*.spec.ts", "build/"],
 5 | 	"exec": "mcp-inspector node build/index.js",
 6 | 	"events": {
 7 | 		"restart": "tsc && chmod 755 build/index.js && echo 'Built TypeScript...'",
 8 | 		"start": "tsc && chmod 755 build/index.js && echo 'Built TypeScript (initial)...'"
 9 | 	}
10 | }
11 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 | 	"compilerOptions": {
 3 | 		"target": "ES2022",
 4 | 		"module": "Node16",
 5 | 		"moduleResolution": "Node16",
 6 | 		"outDir": "./build",
 7 | 		"rootDir": "./src",
 8 | 		"strict": true,
 9 | 		"esModuleInterop": true,
10 | 		"skipLibCheck": true,
11 | 		"forceConsistentCasingInFileNames": true
12 | 	},
13 | 	"include": ["src/**/*"],
14 | 	"exclude": ["node_modules"]
15 | }
16 | 
```

--------------------------------------------------------------------------------
/src/common/constants.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ServerCapabilities } from "@modelcontextprotocol/sdk/types.js";
 2 | 
 3 | const VERSION = "0.0.1";
 4 | 
 5 | const MCP_SERVER_NAME = "custom-context-mcp";
 6 | const TOOL_NAMES = {
 7 | 	groupTextByJson: "group-text-by-json",
 8 | 	textToJson: "text-to-json",
 9 | };
10 | const MCP_CAPABILITIES: ServerCapabilities = {
11 | 	tools: {},
12 | };
13 | 
14 | export { VERSION, MCP_SERVER_NAME, TOOL_NAMES, MCP_CAPABILITIES };
15 | 
```

--------------------------------------------------------------------------------
/src/common/types.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import z from "zod";
 2 | 
 3 | const GroupTextByJsonSchema = z.object({
 4 | 	template: z.string().describe("JSON template with placeholders"),
 5 | });
 6 | const TextToJsonSchema = z.object({
 7 | 	template: z.string().describe("JSON template with placeholders"),
 8 | 	text: z.string().describe("Groupped text from groupTextByJson tool"),
 9 | });
10 | 
11 | export type GroupTextByJsonSchemaType = z.infer<typeof GroupTextByJsonSchema>;
12 | export type TextToJsonSchemaType = z.infer<typeof TextToJsonSchema>;
13 | 
14 | export { GroupTextByJsonSchema, TextToJsonSchema };
15 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 | 	"name": "custom-context-mcp",
 3 | 	"version": "1.0.0",
 4 | 	"description": "",
 5 | 	"main": "index.js",
 6 | 	"type": "module",
 7 | 	"bin": {
 8 | 		"custom-context-mcp": "build/index.js"
 9 | 	},
10 | 	"scripts": {
11 | 		"dev": "yarn build && mcp-inspector node build/index.js",
12 | 		"dev:watch": "concurrently \"nodemon\" \"browser-sync start --proxy localhost:6274 --files build/**/* --no-notify --port 3001 --reload-delay 1000\"",
13 | 		"build": "tsc && chmod 755 build/index.js",
14 | 		"check:types": "tsc --noEmit",
15 | 		"check:lint": "eslint . --ext .ts",
16 | 		"check": "npm run check:types && npm run check:lint",
17 | 		"start": "node build/index.js",
18 | 		"test:samples": "yarn build && node build/test-samples.js"
19 | 	},
20 | 	"keywords": [],
21 | 	"author": "",
22 | 	"license": "ISC",
23 | 	"dependencies": {
24 | 		"@modelcontextprotocol/sdk": "^1.8.0",
25 | 		"zod": "^3.24.2",
26 | 		"zod-to-json-schema": "^3.24.5"
27 | 	},
28 | 	"devDependencies": {
29 | 		"@modelcontextprotocol/inspector": "^0.8.1",
30 | 		"@types/node": "^22.14.0",
31 | 		"browser-sync": "^3.0.4",
32 | 		"concurrently": "^9.1.2",
33 | 		"eslint": "^9.24.0",
34 | 		"globals": "^16.0.0",
35 | 		"nodemon": "^3.1.9",
36 | 		"ts-node": "^10.9.2",
37 | 		"typescript": "^5.8.3",
38 | 		"typescript-eslint": "^8.29.0"
39 | 	},
40 | 	"files": [
41 | 		"build"
42 | 	]
43 | }
44 | 
```

--------------------------------------------------------------------------------
/src/common/utils.ts:
--------------------------------------------------------------------------------

```typescript
 1 | const logger = {
 2 | 	info: (...args: any[]) => {
 3 | 		const msg = `[INFO] ${args.join(" ")}`;
 4 | 		process.stderr.write(`${msg}\n`);
 5 | 	},
 6 | 	debug: (...args: any[]) => {
 7 | 		const msg = `\x1b[36m[DEBUG]\x1b[0m ${args.join(" ")}`;
 8 | 		process.stderr.write(`${msg}\n`);
 9 | 	},
10 | 	warn: (...args: any[]) => {
11 | 		const msg = `\x1b[33m[WARN]\x1b[0m ${args.join(" ")}`;
12 | 		process.stderr.write(`${msg}\n`);
13 | 	},
14 | 	error: (...args: any[]) => {
15 | 		const msg = `\x1b[31m[ERROR]\x1b[0m ${args.join(" ")}`;
16 | 		process.stderr.write(`${msg}\n`);
17 | 	},
18 | };
19 | 
20 | function deepObjectKeys(obj: any, onlyPlaceholders: boolean = false): string[] {
21 | 	const keys: string[] = [];
22 | 
23 | 	function traverseObject(obj: Record<string, any>, prefix: string = "") {
24 | 		if (typeof obj !== "object" || !obj) return;
25 | 
26 | 		for (const objKey of Object.keys(obj)) {
27 | 			const currObj = obj[objKey] as Record<string, any> | string;
28 | 			const keyName = prefix ? `${prefix}.${objKey}` : objKey;
29 | 
30 | 			if (onlyPlaceholders) {
31 | 				const bracketsRegex = /<[^>]+>/gi;
32 | 				if (typeof currObj === "string" && bracketsRegex.test(currObj)) {
33 | 					keys.push(keyName);
34 | 				}
35 | 			} else {
36 | 				keys.push(keyName);
37 | 			}
38 | 
39 | 			if (typeof currObj === "object" && !Array.isArray(currObj) && !!currObj) {
40 | 				traverseObject(currObj, objKey);
41 | 			}
42 | 		}
43 | 	}
44 | 
45 | 	traverseObject(obj);
46 | 	return keys;
47 | }
48 | 
49 | function extractKeyValuesFromText(text: string, keys: string[]) {
50 | 	const regex = new RegExp(`(${keys.join("|")}): (.*?)(\n|$)`, "gi");
51 | 	const matches = text.match(regex);
52 | 
53 | 	if (!matches) {
54 | 		return {};
55 | 	}
56 | 
57 | 	const result: Record<string, string> = {};
58 | 
59 | 	matches.forEach((match) => {
60 | 		const [key, value] = match.split(":");
61 | 		const extractedKey = key.trim();
62 | 		const extractedValues = value.trim();
63 | 
64 | 		if (extractedKey) {
65 | 			result[extractedKey] = extractedValues ?? "";
66 | 		}
67 | 	});
68 | 
69 | 	return result;
70 | }
71 | 
72 | export { logger, deepObjectKeys, extractKeyValuesFromText };
73 | 
```

--------------------------------------------------------------------------------
/src/common/tools.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { deepObjectKeys, extractKeyValuesFromText, logger } from "./utils.js";
 2 | 
 3 | const groupTextByJsonTool = (template: string) => {
 4 | 	if (!template) {
 5 | 		throw new Error("Both template and text are required");
 6 | 	}
 7 | 
 8 | 	try {
 9 | 		logger.info("Template:", template);
10 | 
11 | 		let objectKeys: string[] = [];
12 | 
13 | 		try {
14 | 			const templateObj = JSON.parse(template);
15 | 			objectKeys = deepObjectKeys(templateObj, true);
16 | 		} catch (parseError) {
17 | 			logger.error("Failed to parse template:", parseError);
18 | 			throw new Error(`Invalid template format: ${parseError}`);
19 | 		}
20 | 
21 | 		const resultPrompt = `
22 |         You are a helpful assistant that groups text based on JSON keys.
23 |         Here are the keys in the template: ${objectKeys.join(", ")}.
24 |         Please group the text based on the keys. and give me the result in raw text.
25 |         Don't give it in JSON format or object format. It should be in the following format:
26 | 
27 |         Format:
28 |         <key>: <corresponding text found in the text>
29 | 
30 |         Here's an example:
31 | 
32 |         sentence: The MacBook Pro costs $2,499.
33 | 
34 |         result:
35 |         brand: MacBook
36 |         price: $2,499
37 |         description: The MacBook Pro is a powerful laptop with a Retina display.
38 |         
39 |         `;
40 | 
41 | 		return {
42 | 			content: [
43 | 				{
44 | 					type: "text",
45 | 					text: resultPrompt,
46 | 				},
47 | 			],
48 | 		};
49 | 	} catch (error) {
50 | 		logger.error("Error processing template:", error);
51 | 		throw new Error(`Failed to process template: ${error}`);
52 | 	}
53 | };
54 | 
55 | const textToJsonTool = (template: string, text: string) => {
56 | 	if (!template || !text) {
57 | 		throw new Error("Both template and text are required");
58 | 	}
59 | 
60 | 	try {
61 | 		const templateObj = JSON.parse(template);
62 | 		const templateKeys = deepObjectKeys(templateObj, true);
63 | 
64 | 		const jsonResult = extractKeyValuesFromText(text, templateKeys);
65 | 
66 | 		const resultPrompt = `
67 | 		Print this JSON result in JSON format.
68 | 
69 | 		JSON result:
70 | 		${JSON.stringify(jsonResult)}
71 | 
72 | 		`;
73 | 
74 | 		return {
75 | 			content: [
76 | 				{
77 | 					type: "text",
78 | 					text: resultPrompt,
79 | 				},
80 | 			],
81 | 		};
82 | 	} catch (error) {
83 | 		logger.error("Error processing template:", error);
84 | 		throw new Error(`Failed to process template: ${error}`);
85 | 	}
86 | };
87 | 
88 | export { groupTextByJsonTool, textToJsonTool };
89 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
  2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  3 | import {
  4 | 	CallToolRequestSchema,
  5 | 	ListToolsRequestSchema,
  6 | } from "@modelcontextprotocol/sdk/types.js";
  7 | 
  8 | import { zodToJsonSchema } from "zod-to-json-schema";
  9 | 
 10 | import { logger } from "./common/utils.js";
 11 | import {
 12 | 	GroupTextByJsonSchema,
 13 | 	GroupTextByJsonSchemaType,
 14 | 	TextToJsonSchema,
 15 | 	TextToJsonSchemaType,
 16 | } from "./common/types.js";
 17 | import { groupTextByJsonTool, textToJsonTool } from "./common/tools.js";
 18 | import {
 19 | 	MCP_CAPABILITIES,
 20 | 	MCP_SERVER_NAME,
 21 | 	TOOL_NAMES,
 22 | 	VERSION,
 23 | } from "./common/constants.js";
 24 | 
 25 | const server = new Server(
 26 | 	{
 27 | 		name: MCP_SERVER_NAME,
 28 | 		version: VERSION,
 29 | 	},
 30 | 	{
 31 | 		capabilities: MCP_CAPABILITIES,
 32 | 	}
 33 | );
 34 | 
 35 | server.setRequestHandler(ListToolsRequestSchema, async () => {
 36 | 	logger.info("[MCP] Received tools/list request");
 37 | 	const response = {
 38 | 		tools: [
 39 | 			{
 40 | 				name: TOOL_NAMES.groupTextByJson,
 41 | 				description:
 42 | 					"Gives a prompt text for AI to group text based on JSON placeholders. This tool accepts a JSON template with placeholders.",
 43 | 				inputSchema: zodToJsonSchema(GroupTextByJsonSchema),
 44 | 			},
 45 | 			{
 46 | 				name: TOOL_NAMES.textToJson,
 47 | 				description: `Converts groupped text from ${TOOL_NAMES.groupTextByJson} tool to JSON. This tool accepts a JSON template with placeholders and groupped text from ${TOOL_NAMES.groupTextByJson} tool.`,
 48 | 				inputSchema: zodToJsonSchema(TextToJsonSchema),
 49 | 			},
 50 | 		],
 51 | 	};
 52 | 
 53 | 	return response;
 54 | });
 55 | 
 56 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
 57 | 	const { name, arguments: args } = request.params;
 58 | 	logger.info(`[MCP] Received tools/call request for tool: ${name}`);
 59 | 	logger.debug(`[MCP] Tool arguments: ${JSON.stringify(args, null, 2)}`);
 60 | 
 61 | 	if (!args) {
 62 | 		throw new Error("Arguments are required");
 63 | 	}
 64 | 
 65 | 	switch (name) {
 66 | 		case TOOL_NAMES.groupTextByJson:
 67 | 			const groupTextByJsonArgs = args as GroupTextByJsonSchemaType;
 68 | 			return groupTextByJsonTool(groupTextByJsonArgs.template);
 69 | 		case TOOL_NAMES.textToJson:
 70 | 			const textToJsonArgs = args as TextToJsonSchemaType;
 71 | 			return textToJsonTool(textToJsonArgs.template, textToJsonArgs.text);
 72 | 		default:
 73 | 			throw new Error(`Unknown tool: ${name}`);
 74 | 	}
 75 | });
 76 | 
 77 | async function runServer() {
 78 | 	try {
 79 | 		logger.info("Initializing MCP Server...");
 80 | 		const transport = new StdioServerTransport();
 81 | 
 82 | 		logger.info("Connecting to transport...");
 83 | 		await server.connect(transport);
 84 | 
 85 | 		logger.info("Custom Context MCP Server running on stdio");
 86 | 		logger.info(
 87 | 			"Server information:",
 88 | 			JSON.stringify({
 89 | 				name: MCP_SERVER_NAME,
 90 | 				tools: [TOOL_NAMES.groupTextByJson],
 91 | 			})
 92 | 		);
 93 | 
 94 | 		logger.info("MCP Server is ready to accept requests");
 95 | 
 96 | 		process.on("SIGINT", () => {
 97 | 			logger.info("Received SIGINT signal, shutting down...");
 98 | 			process.exit(0);
 99 | 		});
100 | 
101 | 		process.on("SIGTERM", () => {
102 | 			logger.info("Received SIGTERM signal, shutting down...");
103 | 			process.exit(0);
104 | 		});
105 | 
106 | 		process.on("uncaughtException", (error: Error) => {
107 | 			logger.error("Uncaught exception:", error);
108 | 		});
109 | 	} catch (error) {
110 | 		logger.error("Fatal error during server initialization:", error);
111 | 		process.exit(1);
112 | 	}
113 | }
114 | 
115 | runServer().catch((error) => {
116 | 	logger.error("Fatal error while running server:", error);
117 | 	if (error instanceof Error) {
118 | 		logger.error("Stack trace:", error.stack);
119 | 	}
120 | 	process.exit(1);
121 | });
122 | 
```