├── .gitignore
├── CHANGELOG.md
├── Dockerfile
├── LICENSE
├── MemoryViewer.html
├── README.md
├── SchemaManager.html
├── package-lock.json
├── package.json
├── smithery.yaml
├── src
├── application
│ ├── index.ts
│ ├── managers
│ │ ├── ApplicationManager.ts
│ │ ├── EdgeManager.ts
│ │ ├── GraphManager.ts
│ │ ├── ManagerFactory.ts
│ │ ├── MetadataManager.ts
│ │ ├── NodeManager.ts
│ │ ├── SearchManager.ts
│ │ ├── TransactionManager.ts
│ │ ├── base
│ │ │ └── BaseManager.ts
│ │ ├── index.ts
│ │ └── interfaces
│ │ │ ├── IEdgeManager.ts
│ │ │ ├── IManager.ts
│ │ │ ├── IManagerOperations.ts
│ │ │ ├── IMetadataManager.ts
│ │ │ ├── INodeManager.ts
│ │ │ ├── ISearchManager.ts
│ │ │ ├── ITransactionManager.ts
│ │ │ └── index.ts
│ └── operations
│ │ ├── GraphOperations.ts
│ │ ├── SearchOperations.ts
│ │ ├── TransactionOperations.ts
│ │ └── index.ts
├── config
│ ├── config.ts
│ └── index.ts
├── core
│ ├── graph
│ │ ├── Edge.ts
│ │ ├── EdgeWeightUtils.ts
│ │ ├── Graph.ts
│ │ ├── GraphValidator.ts
│ │ ├── Node.ts
│ │ └── index.ts
│ ├── index.ts
│ ├── metadata
│ │ ├── Metadata.ts
│ │ ├── MetadataProcessor.ts
│ │ └── index.ts
│ └── schema
│ │ ├── SchemaBuilder.ts
│ │ ├── SchemaLoader.ts
│ │ ├── SchemaProcessor.ts
│ │ └── index.ts
├── data
│ ├── memory.json
│ └── schemas
│ │ ├── artifact.schema.json
│ │ ├── currency.schema.json
│ │ ├── faction.schema.json
│ │ ├── inventory.schema.json
│ │ ├── location.schema.json
│ │ ├── npc.schema.json
│ │ ├── player_character.schema.json
│ │ ├── quest.schema.json
│ │ ├── skills.schema.json
│ │ ├── temporal.schema.json
│ │ └── transportation.schema.json
├── index.ts
├── infrastructure
│ ├── events
│ │ ├── EventEmitter.ts
│ │ ├── EventTypes.ts
│ │ └── index.ts
│ ├── index.ts
│ └── storage
│ │ ├── IStorage.ts
│ │ ├── JsonLineStorage.ts
│ │ └── index.ts
├── integration
│ ├── index.ts
│ └── tools
│ │ ├── DynamicSchemaToolRegistry.ts
│ │ ├── callToolHandler.ts
│ │ ├── handlers
│ │ ├── BaseToolHandler.ts
│ │ ├── DynamicToolHandler.ts
│ │ ├── GraphToolHandler.ts
│ │ ├── MetadataToolHandler.ts
│ │ ├── SearchToolHandler.ts
│ │ ├── ToolHandlerFactory.ts
│ │ └── index.ts
│ │ ├── index.ts
│ │ └── registry
│ │ ├── dynamicTools.ts
│ │ ├── index.ts
│ │ ├── staticTools.ts
│ │ └── toolsRegistry.ts
└── shared
│ ├── index.ts
│ ├── types
│ ├── index.ts
│ ├── operations.ts
│ └── tools.ts
│ └── utils
│ ├── index.ts
│ └── responseFormatter.ts
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Node.js
2 | node_modules/
3 | npm-debug.log*
4 | yarn-error.log*
5 |
6 | # TypeScript
7 | dist/
8 |
9 | # System Files (macOS)
10 | .DS_Store
11 |
12 | # IDE-specific files
13 | .idea/
14 |
15 | # Log files
16 | *.log
17 | *.log.*
18 |
19 | # Temporary files
20 | *.tmp
21 | *.temp
22 |
23 | # OS generated files
24 | Thumbs.db
25 | ehthumbs.db
26 |
27 | # dotenv
28 | .env
29 |
30 | # Ignore cache and log files created by npm
31 | npm-debug.log*
32 | yarn-debug.log*
33 | yarn-error.log*
34 | .pnpm-debug.log*
35 |
36 | # Coverage reports
37 | coverage/
38 | .nyc_output/
39 |
40 | # Ignore the memory file
41 | src/data/memory.json
42 |
43 | # Ignore schema changes
44 | scr/data/schemas/*.json
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## Changelog
2 |
3 | ### v0.2.8 (2024-12-24)
4 |
5 | #### Features
6 |
7 | - **Edge Weights:**
8 | - Introduced an optional `weight` property to the `Edge` interface to represent relationship strength in the range of 0-1.
9 | - Edge weights now default to 1 if not specified.
10 |
11 | - **Enhanced Search:**
12 | - Modified `SearchManager` to include immediate neighbor nodes in `searchNodes` and `openNodes` results.
13 |
14 | **Impact:**
15 |
16 | - **Edge Weights:**
17 | - Enables a more nuanced representation of relationships in the knowledge graph, allowing for the expression of varying degrees of connection strength or confidence.
18 | - No changes in schemas.
19 | -
20 | - **Enhanced Search:**
21 | - Provides a more comprehensive view of the relevant portion of the knowledge graph returning more contextually relevant information to the AI.
22 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
2 | # Use a Node.js image with Node.js 18
3 | FROM node:18-alpine AS builder
4 |
5 | # Set working directory
6 | WORKDIR /app
7 |
8 | # Copy package files and install dependencies
9 | COPY package.json package-lock.json ./
10 | RUN npm install --ignore-scripts
11 |
12 | # Copy source files
13 | COPY . .
14 |
15 | # Build the TypeScript project
16 | RUN npm run build
17 |
18 | # Use a minimal Node.js runtime for the final image
19 | FROM node:18-alpine
20 |
21 | # Set working directory
22 | WORKDIR /app
23 |
24 | # Copy the built files from the builder stage
25 | COPY --from=builder /app/dist /app/dist
26 | COPY --from=builder /app/package.json /app/package-lock.json ./
27 |
28 | # Install only production dependencies
29 | RUN npm ci --omit=dev
30 |
31 | # Expose necessary ports (if applicable)
32 | # EXPOSE 3000 # Example port, adjust as necessary
33 |
34 | # Start the server
35 | CMD ["node", "dist/index.js"]
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 CheMiguel23
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MemoryMesh
2 | [](./CHANGELOG.md)
3 | [](https://smithery.ai/server/memorymesh)
4 | 
5 | [](https://opensource.org/licenses/MIT)
6 | 
7 |
8 | MemoryMesh is a knowledge graph server designed for AI models, with a focus on text-based RPGs and interactive storytelling. It helps AI maintain consistent, structured memory across conversations, enabling richer and more dynamic interactions.
9 |
10 | *The project is based on the [Knowledge Graph Memory Server](https://github.com/modelcontextprotocol/servers/tree/main/src/memory) from the MCP servers repository and retains its core functionality.*
11 |
12 |
13 |
14 | ## IMPORTANT
15 | Since `v0.2.7` the default location of schemas was changed to `dist/data/schemas`.
16 | This location is not expected to change in the future, but if you are updating from a previous version, make sure to move your schema files to the new location.
17 |
18 | ## Quick Links
19 |
20 | * [Installation](#installation)
21 | * [Example](#example)
22 | * [SchemaManager Guide Discussion](https://github.com/CheMiguel23/MemoryMesh/discussions/3)
23 | * [MemoryViewer Guide Discussion](https://github.com/CheMiguel23/MemoryMesh/discussions/15)
24 |
25 | ## Overview
26 |
27 | MemoryMesh is a local knowledge graph server that empowers you to build and manage structured information for AI models. While particularly well-suited for text-based RPGs, its adaptable design makes it useful for various applications, including social network simulations, organizational planning, or any scenario involving structured data.
28 |
29 | ### Key Features
30 |
31 | * **Dynamic Schema-Based Tools:** Define your data structure with schemas, and MemoryMesh automatically generates tools for adding, updating, and deleting data.
32 | * **Intuitive Schema Design:** Create schemas that guide the AI in generating and connecting nodes, using required fields, enumerated types, and relationship definitions.
33 | * **Metadata for AI Guidance:** Use metadata to provide context and structure, helping the AI understand the meaning and relationships within your data.
34 | * **Relationship Handling:** Define relationships within your schemas to encourage the AI to create connections (edges) between related data points (nodes).
35 | * **Informative Feedback:** Provides error feedback to the AI, enabling it to learn from mistakes and improve its interactions with the knowledge graph.
36 | * **Event Support:** An event system tracks operations, providing insights into how the knowledge graph is being modified.
37 |
38 | #### Nodes
39 |
40 | Nodes represent entities or concepts within the knowledge graph. Each node has:
41 |
42 | * `name`: A unique identifier.
43 | * `nodeType`: The type of the node (e.g., `npc`, `artifact`, `location`), defined by your schemas.
44 | * `metadata`: An array of strings providing descriptive details about the node.
45 | * `weight`: (Optional) A numerical value between 0 and 1 representing the strength of the relationship, defaulting to 1.
46 |
47 | **Example Node:**
48 |
49 | ```json
50 | {
51 | "name": "Aragorn",
52 | "nodeType": "player_character",
53 | "metadata": [
54 | "Race: Human",
55 | "Class: Ranger",
56 | "Skills: Tracking, Swordsmanship",
57 | "Affiliation: Fellowship of the Ring"
58 | ]
59 | }
60 | ```
61 |
62 | #### Edges
63 |
64 | Edges represent relationships between nodes. Each edge has:
65 |
66 | * `from`: The name of the source node.
67 | * `to`: The name of the target node.
68 | * `edgeType`: The type of relationship (e.g., `owns`, `located_in`).
69 |
70 | ```json
71 | {
72 | "from": "Aragorn",
73 | "to": "Andúril",
74 | "edgeType": "owns"
75 | }
76 | ```
77 |
78 | #### Schemas
79 |
80 | Schemas are the heart of MemoryMesh. They define the structure of your data and drive the automatic generation of tools.
81 |
82 | ##### Schema File Location
83 |
84 | Place your schema files (`.schema.json`) in the `dist/data/schemas` directory of your built MemoryMesh project. MemoryMesh will automatically detect and process these files on startup.
85 |
86 | ##### Schema Structure
87 |
88 | File name: `[name].schema.json`. For example, for a schema defining an 'npc', the filename would be `add_npc.schema.json`.
89 |
90 | * `name` - Identifier for the schema and node type within the memory. **IMPORTANT**: The schema’s name *must* start with `add_` to be recognized.
91 | * `description` - Used as the description for the `add_` tool, providing context for the AI. *(The `delete` and `update` tools have a generic description)*
92 | * `properties` - Each property includes its type, description, and additional constraints.
93 | * `property`
94 | * `type` - Supported values are `string` or `array`.
95 | * `description` - Helps guide the AI on the entity’s purpose.
96 | * `required` - Boolean. If `true`, the **AI is forced** to provide this property when creating a node.
97 | * `enum` - An array of strings. If present, the **AI must choose** one of the given options.
98 | * `relationship` - Defines a connection to another node. If a property is required and has a relationship, the **AI will always create** both the node and the corresponding edge.
99 | * `edgeType` - Type of the relationship to be created.
100 | * `description` - Helps guide the AI on the relationship’s purpose.
101 | * `additionalProperties` - Boolean. If `true`, allows the AI to add extra attributes beyond those defined as required or optional.
102 |
103 | ##### Example Schema (add_npc.schema.json):
104 |
105 | ```json
106 | {
107 | "name": "add_npc",
108 | "description": "Schema for adding an NPC to the memory" ,
109 | "properties": {
110 | "name": {
111 | "type": "string",
112 | "description": "A unique identifier for the NPC",
113 | "required": true
114 | },
115 | "race": {
116 | "type": "string",
117 | "description": "The species or race of the NPC",
118 | "required": true,
119 | "enum": [
120 | "Human",
121 | "Elf",
122 | "Dwarf",
123 | "Orc",
124 | "Goblin"
125 | ]
126 | },
127 | "currentLocation": {
128 | "type": "string",
129 | "description": "The current location of the NPC",
130 | "required": true,
131 | "relationship": {
132 | "edgeType": "located_in",
133 | "description": "The current location of the NPC"
134 | }
135 | }
136 | },
137 | "additionalProperties": true
138 | }
139 | ```
140 |
141 | Based on this schema, MemoryMesh automatically creates:
142 | * add_npc: To add new NPC nodes.
143 | * update_npc: To modify existing NPC nodes.
144 | * delete_npc: To remove NPC nodes.
145 |
146 | MemoryMesh includes 11 pre-built schemas designed for text-based RPGs, providing a ready-to-use foundation for game development.
147 |
148 | ##### SchemaManager Tool
149 |
150 | MemoryMesh includes a [SchemaManager tool](https://github.com/CheMiguel23/MemoryMesh/blob/main/SchemaManager.html) to simplify schema creation and editing. It provides a visual interface, making it easy to define your data structures without writing JSON directly.
151 |
152 |
153 |
154 | ### Dynamic Tools
155 |
156 | MemoryMesh simplifies interaction with your knowledge graph through **dynamic tools**. These tools are not manually coded but are **automatically generated** directly from your **schema definitions**. This means that when you define the structure of your data using schemas, MemoryMesh intelligently creates a set of tools tailored to work with that specific data structure.
157 |
158 | **Think of it like this:** You provide a blueprint (the schema), and MemoryMesh automatically constructs the necessary tools to build, modify, and remove elements based on that blueprint.
159 |
160 | #### How does it work behind the scenes?
161 |
162 | MemoryMesh has an intelligent system that reads your schema definitions. It analyzes the structure you've defined, including the properties of your entities and their relationships. Based on this analysis, it automatically creates a set of tools for each entity type:
163 |
164 | * **`add_`:** A tool for creating new instances of an entity.
165 | * **`update_`:** A tool for modifying existing entities.
166 | * **`delete_`:** A tool for removing entities.
167 |
168 | These tools are then made available through a central hub within MemoryMesh, ensuring they can be easily accessed and used by any connected client or AI.
169 |
170 | **In essence, MemoryMesh's dynamic tool system provides a powerful and efficient way to manage your knowledge graph, freeing you to focus on the content and logic of your application rather than the underlying mechanics of data manipulation.**
171 |
172 | ### Memory file
173 |
174 | By default, data is stored in a JSON file in `dist/data/memory.json`.
175 |
176 | #### Memory Viewer
177 |
178 | The Memory Viewer is a separate tool designed to help you visualize and inspect the contents of the knowledge graph managed by MemoryMesh. It provides a user-friendly interface for exploring nodes, edges, and their properties.
179 |
180 | ##### Key Features:
181 | * Graph Visualization: View the knowledge graph as an interactive node-link diagram.
182 | * Node Inspection: Select nodes to see their nodeType, metadata, and connected edges.
183 | * Edge Exploration: Examine relationships between nodes, including edgeType and direction.
184 | * Search and Filtering: Quickly find specific nodes or filter them by type.
185 | * Table View: Allows you to easily find and inspect specific nodes and edges, or all of them at once.
186 | * Raw JSON View: Allows you to view the raw JSON data from the memory file.
187 | * Stats Panel: Provides key metrics and information about the knowledge graph: total nodes, total edges, node types, and edge types.
188 | * Search and Filter: Allows you to filter by node type or edge type and filter whether to show nodes, edges, or both.
189 |
190 | ##### Accessing the Memory Viewer
191 | The Memory Viewer is a standalone web application. [Memory Viewer discussion](https://github.com/CheMiguel23/MemoryMesh/discussions/15)
192 |
193 | ##### Using the Memory Viewer
194 | * Select Memory File: In the Memory Viewer, click the "Select Memory File" button.
195 | * Choose File: Navigate to your MemoryMesh project directory and select the `memory.json` file (located in `dist/data/memory.json` by default).
196 | * Explore: The Memory Viewer will load and display the contents of your knowledge graph.
197 |
198 | ## Memory Flow
199 |
200 | 
201 |
202 | ## Prompt
203 |
204 | For optimal results, use Claude's "Projects" feature with custom instructions. Here's an example of a prompt you can start with:
205 |
206 | ```
207 | You are a helpful AI assistant managing a knowledge graph for a text-based RPG. You have access to the following tools: add_npc, update_npc, delete_npc, add_location, update_location, delete_location, and other tools for managing the game world.
208 |
209 | When the user provides input, first process it using your available tools to update the knowledge graph. Then, respond in a way that is appropriate for a text-based RPG.
210 | ```
211 |
212 | You can also instruct the AI to perform specific actions directly in the chat.
213 |
214 | Experiment with different prompts to find what works best for your use case!
215 |
216 | ### Example
217 | 1. A [simple example](https://pastebin.com/0HvKg5FZ) with custom instructions.
218 | 2. An example for the sake of example, with visualization _(NOT part of the functionality)_
219 |
220 | > Add a couple of cities, some npcs, couple locations around the city to explore, hide an artifact or two somewhere
221 |
222 | 
223 |
224 | ## Installation
225 |
226 | ### Installing via Smithery
227 |
228 | To install MemoryMesh for Claude Desktop automatically via [Smithery](https://smithery.ai/server/memorymesh):
229 |
230 | ```bash
231 | npx -y @smithery/cli install memorymesh --client claude
232 | ```
233 |
234 | ### Prerequisites
235 |
236 | * **Node.js:** Version 18 or higher. You can download it from [nodejs.org](https://nodejs.org/).
237 | * **npm:** Usually included with Node.js.
238 | * **Claude for Desktop:** Make sure you have the latest version installed from [claude.ai/download](https://claude.ai/download).
239 |
240 | ### Installation Steps
241 |
242 | 1. **Clone the Repository:**
243 |
244 | ```bash
245 | git clone https://github.com/CheMiguel23/memorymesh.git
246 | cd memorymesh
247 | ```
248 |
249 | 2. **Install Dependencies:**
250 |
251 | ```bash
252 | npm install
253 | ```
254 |
255 | 3. **Build the Project:**
256 |
257 | ```bash
258 | npm run build
259 | ```
260 | This command compiles the TypeScript code into JavaScript in the `dist` directory and copies sample schema and data files into it as well.
261 |
262 | 4. **Verify File Copy (Optional):**
263 |
264 | * The build process should automatically copy the `data` folder to `dist`.
265 | * **Check** that `dist/data` exists and contains `.json` files. Also verify that `dist/data/schemas` exists and contains `.schema.json` files.
266 |
267 | 5. **Configure Claude Desktop:**
268 |
269 | Open your Claude Desktop configuration file:
270 |
271 | * **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
272 | * **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
273 | * Add an entry for `memorymesh` to the `mcpServers` section. You can choose **one** of the following configuration options:
274 |
275 | ```json
276 | "mcpServers": {
277 | "memorymesh": {
278 | "command": "node",
279 | "args": ["/ABSOLUTE/PATH/TO/YOUR/PROJECT/memorymesh/dist/index.js"]
280 | }
281 | }
282 | ```
283 |
284 | * Replace `/ABSOLUTE/PATH/TO/YOUR/PROJECT/` with the **actual absolute path** to your `memorymesh` project directory.
285 | * **Example (macOS):**
286 | ```json
287 | "command": "node",
288 | "args": ["/Users/yourusername/Projects/memorymesh/dist/index.js"]
289 | ```
290 | * **Example (Windows):**
291 | ```json
292 | "command": "node",
293 | "args": ["C:\\Projects\\memorymesh\\dist\\index.js"]
294 | ```
295 |
296 | 6. **Restart Claude Desktop:** Completely restart Claude Desktop for the changes to take effect.
297 |
298 | ### Verify Installation
299 |
300 | 1. Start Claude Desktop.
301 | 2. Open a new chat.
302 | 3. Look for the MCP plugin icon
in the top-right corner. If it's there, your configuration is likely correct.
303 | 4. Click the
icon. You should see "memorymesh" in the list of connected servers.
304 | 5. Click the
icon. If you see tools listed (e.g., `add_npc`, `update_npc`, etc.), your server is working and exposing tools correctly.
305 |
306 | ### Updating
307 | Before updates, make sure to back up your `dist/data` directory to avoid losing your memory data.
308 |
309 | ### Troubleshooting
310 |
311 | * **Server not appearing in Claude:**
312 | * Double-check the paths in your `claude_desktop_config.json`. Make sure they are absolute paths and correct.
313 | * Verify that the `dist` directory exists and contains the compiled JavaScript files, including `index.js`.
314 | * Check the Claude Desktop logs for errors:
315 | * **macOS:** `~/Library/Logs/Claude/mcp-server-memorymesh.log` (and `mcp.log`)
316 | * **Windows:** (Likely in a `Logs` folder under `%AppData%\Claude`)
317 |
318 | * **Tools not showing up:**
319 | * Make sure your `npm run build` command completed without errors.
320 | * Verify that your schema files are correctly placed in `dist/data/schemas` and follow the correct naming convention (`add_[entity].schema.json`).
321 | * Check your server's console output or logs for any errors during initialization.
322 |
323 | ## Advanced Configuration
324 | MemoryMesh offers several ways to customize its behavior beyond the basic setup:
325 |
326 | ### Variables
327 | You can override default settings using in `/config/config.ts`
328 | * MEMORY_FILE: Specifies the path to the JSON file used for storing the knowledge graph data. (Default: `dist/data/memory.json`)
329 | * SCHEMAS_DIR: Path to schema files directory. (Default: `dist/data/schemas/memory.json`)
330 |
331 | ## Limitations
332 |
333 | 1. **Node Deletion:** The AI may be hesitant to delete nodes from the knowledge graph. Encourage it through prompts if needed.
334 |
335 | ## Contribution
336 |
337 | Contributions, feedback, and ideas are welcome!
338 | This project is a personal exploration into integrating structured data with AI reasoning capabilities. Contributions, feedback, and ideas are welcome to push it further or inspire new projects.
339 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "memorymesh",
3 | "version": "0.2.8",
4 | "description": "An MCP server that uses a knowledge graph to store and recall structured memory for AI models",
5 | "license": "MIT",
6 | "author": "CheMiguel23",
7 | "homepage": "https://github.com/CheMiguel23/memorymesh",
8 | "bugs": "https://github.com/CheMiguel23/memorymesh/issues",
9 | "type": "module",
10 | "main": "dist/index.js",
11 | "bin": {
12 | "memorymesh": "./dist/index.js"
13 | },
14 | "files": [
15 | "dist"
16 | ],
17 | "scripts": {
18 | "build": "tsc && tsc-alias && shx chmod +x dist/*.js && copyfiles -u 1 src/data/schemas/*.json src/data/*.json dist/",
19 | "prepare": "npm run build",
20 | "watch": "tsc --watch",
21 | "start": "ts-node --esm src/index.ts",
22 | "start:claude": "ts-node --esm src/index.ts",
23 | "start:prod": "node dist/index.js"
24 | },
25 | "dependencies": {
26 | "@modelcontextprotocol/sdk": "^0.5.0"
27 | },
28 | "devDependencies": {
29 | "@types/node": "^20.11.24",
30 | "copyfiles": "^2.4.1",
31 | "shx": "^0.3.4",
32 | "ts-node": "^10.9.2",
33 | "tsc-alias": "^1.8.8",
34 | "typescript": "^5.3.3"
35 | }
36 | }
--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------
1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
2 |
3 | startCommand:
4 | type: stdio
5 | configSchema:
6 | # JSON Schema defining the configuration options for the MCP.
7 | type: object
8 | required: []
9 | properties: {}
10 | commandFunction:
11 | # A function that produces the CLI command to start the MCP on stdio.
12 | |-
13 | (config) => ({ command: 'node', args: ['dist/index.js'] })
--------------------------------------------------------------------------------
/src/application/index.ts:
--------------------------------------------------------------------------------
1 | // src/application/index.ts
2 |
3 | export {ApplicationManager} from './managers/ApplicationManager.js';
4 | export * from './managers/index.js';
5 | export * from './managers/interfaces/index.js';
6 | export * from './operations/index.js';
--------------------------------------------------------------------------------
/src/application/managers/ApplicationManager.ts:
--------------------------------------------------------------------------------
1 | // src/application/managers/ApplicationManager.ts
2 |
3 | import type {IStorage} from '@infrastructure/index.js';
4 | import type {
5 | Node,
6 | Edge,
7 | Graph,
8 | MetadataAddition,
9 | MetadataDeletion,
10 | MetadataResult,
11 | } from '@core/index.js';
12 | import type {
13 | EdgeFilter,
14 | GetEdgesResult,
15 | OpenNodesResult
16 | } from '@shared/index.js';
17 | import {
18 | GraphManager,
19 | SearchManager,
20 | TransactionManager
21 | } from "@application/index.js";
22 | import {JsonLineStorage} from '@infrastructure/index.js';
23 |
24 | /**
25 | * Main facade that coordinates between specialized managers
26 | */
27 | export class ApplicationManager {
28 | private readonly graphManager: GraphManager;
29 | private readonly searchManager: SearchManager;
30 | private readonly transactionManager: TransactionManager;
31 |
32 | constructor(storage: IStorage = new JsonLineStorage()) {
33 | this.graphManager = new GraphManager(storage);
34 | this.searchManager = new SearchManager(storage);
35 | this.transactionManager = new TransactionManager(storage);
36 | }
37 |
38 | // Graph operations delegated to GraphManager
39 | async addNodes(nodes: Node[]): Promise {
40 | return this.graphManager.addNodes(nodes);
41 | }
42 |
43 | async updateNodes(nodes: Partial[]): Promise {
44 | return this.graphManager.updateNodes(nodes);
45 | }
46 |
47 | async deleteNodes(nodeNames: string[]): Promise {
48 | return this.graphManager.deleteNodes(nodeNames);
49 | }
50 |
51 | async addEdges(edges: Edge[]): Promise {
52 | return this.graphManager.addEdges(edges);
53 | }
54 |
55 | async updateEdges(edges: Edge[]): Promise {
56 | return this.graphManager.updateEdges(edges);
57 | }
58 |
59 | async deleteEdges(edges: Edge[]): Promise {
60 | return this.graphManager.deleteEdges(edges);
61 | }
62 |
63 | async getEdges(filter?: EdgeFilter): Promise {
64 | return this.graphManager.getEdges(filter);
65 | }
66 |
67 | async addMetadata(metadata: MetadataAddition[]): Promise {
68 | return this.graphManager.addMetadata(metadata);
69 | }
70 |
71 | async deleteMetadata(deletions: MetadataDeletion[]): Promise {
72 | return this.graphManager.deleteMetadata(deletions);
73 | }
74 |
75 | // Search operations delegated to SearchManager
76 | async readGraph(): Promise {
77 | return this.searchManager.readGraph();
78 | }
79 |
80 | async searchNodes(query: string): Promise {
81 | return this.searchManager.searchNodes(query);
82 | }
83 |
84 | async openNodes(names: string[]): Promise {
85 | return this.searchManager.openNodes(names);
86 | }
87 |
88 | // Transaction operations delegated to TransactionManager
89 | async beginTransaction(): Promise {
90 | return this.transactionManager.beginTransaction();
91 | }
92 |
93 | async commit(): Promise {
94 | return this.transactionManager.commit();
95 | }
96 |
97 | async rollback(): Promise {
98 | return this.transactionManager.rollback();
99 | }
100 |
101 | async withTransaction(operation: () => Promise): Promise {
102 | return this.transactionManager.withTransaction(operation);
103 | }
104 |
105 | async addRollbackAction(action: () => Promise, description: string): Promise {
106 | return this.transactionManager.addRollbackAction(action, description);
107 | }
108 |
109 | isInTransaction(): boolean {
110 | return this.transactionManager.isInTransaction();
111 | }
112 |
113 | getCurrentGraph(): Graph {
114 | return this.transactionManager.getCurrentGraph();
115 | }
116 | }
--------------------------------------------------------------------------------
/src/application/managers/EdgeManager.ts:
--------------------------------------------------------------------------------
1 | // src/application/managers/EdgeManager.ts
2 |
3 | import {IEdgeManager} from '@application/managers/interfaces/IEdgeManager.js';
4 | import {GraphValidator, EdgeWeightUtils} from '@core/index.js';
5 | import type {Edge} from '@core/index.js';
6 | import type {EdgeUpdate, EdgeFilter} from '@shared/index.js';
7 |
8 | /**
9 | * Implements edge-related operations for the knowledge graph.
10 | * Includes adding, updating, deleting, and retrieving edges.
11 | */
12 | export class EdgeManager extends IEdgeManager {
13 | /**
14 | * Adds new edges to the knowledge graph.
15 | */
16 | async addEdges(edges: Edge[]): Promise {
17 | try {
18 | this.emit('beforeAddEdges', {edges});
19 |
20 | const graph = await this.storage.loadGraph();
21 |
22 | // Validate edge uniqueness and node existence using GraphValidator
23 | const newEdges = edges.filter(edge => {
24 | GraphValidator.validateEdgeUniqueness(graph, edge);
25 | // Ensure weights are set
26 | return EdgeWeightUtils.ensureWeight(edge);
27 | });
28 |
29 | if (newEdges.length === 0) {
30 | return [];
31 | }
32 |
33 | for (const edge of newEdges) {
34 | GraphValidator.validateNodeExists(graph, edge.from);
35 | GraphValidator.validateNodeExists(graph, edge.to);
36 | }
37 |
38 | graph.edges.push(...newEdges);
39 | await this.storage.saveGraph(graph);
40 |
41 | this.emit('afterAddEdges', {edges: newEdges});
42 | return newEdges;
43 | } catch (error) {
44 | const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
45 | throw new Error(errorMessage);
46 | }
47 | }
48 |
49 | /**
50 | * Updates existing edges in the knowledge graph.
51 | */
52 | async updateEdges(edges: EdgeUpdate[]): Promise {
53 | try {
54 | this.emit('beforeUpdateEdges', {edges});
55 |
56 | const graph = await this.storage.loadGraph();
57 | const updatedEdges: Edge[] = [];
58 |
59 | for (const updateEdge of edges) {
60 | const edgeIndex = graph.edges.findIndex(existing =>
61 | existing.from === updateEdge.from &&
62 | existing.to === updateEdge.to &&
63 | existing.edgeType === updateEdge.edgeType
64 | );
65 |
66 | if (edgeIndex === -1) {
67 | throw new Error(`Edge not found: ${updateEdge.from} -> ${updateEdge.to} (${updateEdge.edgeType})`);
68 | }
69 |
70 | // Validate node existence for updated nodes using GraphValidator
71 | if (updateEdge.newFrom) {
72 | GraphValidator.validateNodeExists(graph, updateEdge.newFrom);
73 | }
74 | if (updateEdge.newTo) {
75 | GraphValidator.validateNodeExists(graph, updateEdge.newTo);
76 | }
77 |
78 | const updatedEdge: Edge = {
79 | type: 'edge',
80 | from: updateEdge.newFrom || graph.edges[edgeIndex].from,
81 | to: updateEdge.newTo || graph.edges[edgeIndex].to,
82 | edgeType: updateEdge.newEdgeType || graph.edges[edgeIndex].edgeType,
83 | weight: updateEdge.newWeight !== undefined ? updateEdge.newWeight : graph.edges[edgeIndex].weight
84 | };
85 |
86 | // Validate the new weight if it's being updated
87 | if (updatedEdge.weight !== undefined) {
88 | EdgeWeightUtils.validateWeight(updatedEdge.weight);
89 | }
90 |
91 | graph.edges[edgeIndex] = updatedEdge;
92 | updatedEdges.push(updatedEdge);
93 | }
94 |
95 | await this.storage.saveGraph(graph);
96 |
97 | this.emit('afterUpdateEdges', {edges: updatedEdges});
98 | return updatedEdges;
99 | } catch (error) {
100 | const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
101 | throw new Error(errorMessage);
102 | }
103 | }
104 |
105 | /**
106 | * Deletes edges from the knowledge graph.
107 | */
108 | async deleteEdges(edges: Edge[]): Promise {
109 | try {
110 | this.emit('beforeDeleteEdges', {edges});
111 |
112 | const graph = await this.storage.loadGraph();
113 | const initialEdgeCount = graph.edges.length;
114 |
115 | graph.edges = graph.edges.filter(existing =>
116 | !edges.some(edge =>
117 | existing.from === edge.from &&
118 | existing.to === edge.to &&
119 | existing.edgeType === edge.edgeType
120 | )
121 | );
122 |
123 | const deletedCount = initialEdgeCount - graph.edges.length;
124 |
125 | await this.storage.saveGraph(graph);
126 |
127 | this.emit('afterDeleteEdges', {deletedCount});
128 | } catch (error) {
129 | const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
130 | throw new Error(errorMessage);
131 | }
132 | }
133 |
134 | /**
135 | * Retrieves edges from the knowledge graph based on filter criteria.
136 | */
137 | async getEdges(filter?: EdgeFilter): Promise {
138 | try {
139 | const graph = await this.storage.loadGraph();
140 |
141 | if (!filter || Object.keys(filter).length === 0) {
142 | return graph.edges;
143 | }
144 |
145 | return graph.edges.filter(edge => {
146 | if (filter.from && edge.from !== filter.from) return false;
147 | if (filter.to && edge.to !== filter.to) return false;
148 | return !(filter.edgeType && edge.edgeType !== filter.edgeType);
149 | });
150 | } catch (error) {
151 | const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
152 | throw new Error(errorMessage);
153 | }
154 | }
155 | }
--------------------------------------------------------------------------------
/src/application/managers/GraphManager.ts:
--------------------------------------------------------------------------------
1 | // src/application/managers/GraphManager.ts
2 |
3 | import {BaseManager, GraphOperations} from '@application/index.js';
4 | import type {
5 | MetadataAddition,
6 | MetadataDeletion,
7 | MetadataResult,
8 | Node,
9 | Edge
10 | } from '@core/index.js';
11 | import type {
12 | EdgeFilter,
13 | GetEdgesResult,
14 | } from '@shared/index.js';
15 | import type {IStorage} from '@infrastructure/index.js';
16 |
17 | /**
18 | * Handles graph-specific operations (nodes, edges, metadata)
19 | */
20 | export class GraphManager extends BaseManager {
21 | private readonly graphOperations: GraphOperations;
22 |
23 | constructor(storage?: IStorage) {
24 | super(storage);
25 | const {nodeManager, edgeManager, metadataManager} = this.createManagers();
26 | this.graphOperations = new GraphOperations(nodeManager, edgeManager, metadataManager);
27 | }
28 |
29 | // Node operations
30 | async addNodes(nodes: Node[]): Promise {
31 | return this.graphOperations.addNodes(nodes);
32 | }
33 |
34 | async updateNodes(nodes: Partial[]): Promise {
35 | return this.graphOperations.updateNodes(nodes);
36 | }
37 |
38 | async deleteNodes(nodeNames: string[]): Promise {
39 | return this.graphOperations.deleteNodes(nodeNames);
40 | }
41 |
42 | // Edge operations
43 | async addEdges(edges: Edge[]): Promise {
44 | return this.graphOperations.addEdges(edges);
45 | }
46 |
47 | async updateEdges(edges: Edge[]): Promise {
48 | return this.graphOperations.updateEdges(edges);
49 | }
50 |
51 | async deleteEdges(edges: Edge[]): Promise {
52 | return this.graphOperations.deleteEdges(edges);
53 | }
54 |
55 | async getEdges(filter?: EdgeFilter): Promise {
56 | return this.graphOperations.getEdges(filter);
57 | }
58 |
59 | // Metadata operations
60 | async addMetadata(metadata: MetadataAddition[]): Promise {
61 | return this.graphOperations.addMetadata(metadata);
62 | }
63 |
64 | async deleteMetadata(deletions: MetadataDeletion[]): Promise {
65 | return this.graphOperations.deleteMetadata(deletions);
66 | }
67 | }
--------------------------------------------------------------------------------
/src/application/managers/ManagerFactory.ts:
--------------------------------------------------------------------------------
1 | // src/core/managers/ManagerFactory.ts
2 |
3 | import {
4 | NodeManager,
5 | EdgeManager,
6 | MetadataManager,
7 | SearchManager,
8 | TransactionManager
9 | } from '@application/index.js';
10 | import type {IStorage} from '@infrastructure/index.js';
11 | import type {
12 | INodeManager,
13 | IEdgeManager,
14 | IMetadataManager,
15 | ISearchManager,
16 | ITransactionManager
17 | } from '@application/index.js';
18 |
19 | /**
20 | * Factory class responsible for creating instances of various manager classes
21 | * used in the knowledge graph. Ensures consistent initialization and configuration
22 | * of all manager instances.
23 | */
24 | export class ManagerFactory {
25 | private static instances: {
26 | nodeManager?: NodeManager;
27 | edgeManager?: EdgeManager;
28 | metadataManager?: MetadataManager;
29 | searchManager?: SearchManager;
30 | transactionManager?: TransactionManager;
31 | } = {};
32 |
33 | /**
34 | * Creates or returns an existing instance of NodeManager
35 | */
36 | static getNodeManager(storage: IStorage): INodeManager {
37 | if (!this.instances.nodeManager) {
38 | this.instances.nodeManager = new NodeManager(storage);
39 | }
40 | return this.instances.nodeManager;
41 | }
42 |
43 | /**
44 | * Creates or returns an existing instance of EdgeManager
45 | */
46 | static getEdgeManager(storage: IStorage): IEdgeManager {
47 | if (!this.instances.edgeManager) {
48 | this.instances.edgeManager = new EdgeManager(storage);
49 | }
50 | return this.instances.edgeManager;
51 | }
52 |
53 | /**
54 | * Creates or returns an existing instance of MetadataManager
55 | */
56 | static getMetadataManager(storage: IStorage): IMetadataManager {
57 | if (!this.instances.metadataManager) {
58 | this.instances.metadataManager = new MetadataManager(storage);
59 | }
60 | return this.instances.metadataManager;
61 | }
62 |
63 | /**
64 | * Creates or returns an existing instance of SearchManager
65 | */
66 | static getSearchManager(storage: IStorage): ISearchManager {
67 | if (!this.instances.searchManager) {
68 | this.instances.searchManager = new SearchManager(storage);
69 | }
70 | return this.instances.searchManager;
71 | }
72 |
73 | /**
74 | * Creates or returns an existing instance of TransactionManager
75 | */
76 | static getTransactionManager(storage: IStorage): ITransactionManager {
77 | if (!this.instances.transactionManager) {
78 | this.instances.transactionManager = new TransactionManager(storage);
79 | }
80 | return this.instances.transactionManager;
81 | }
82 |
83 | /**
84 | * Creates all manager instances at once
85 | */
86 | static getAllManagers(storage: IStorage) {
87 | return {
88 | nodeManager: this.getNodeManager(storage),
89 | edgeManager: this.getEdgeManager(storage),
90 | metadataManager: this.getMetadataManager(storage),
91 | searchManager: this.getSearchManager(storage),
92 | transactionManager: this.getTransactionManager(storage)
93 | };
94 | }
95 |
96 | /**
97 | * Clears all cached manager instances
98 | */
99 | static clearInstances(): void {
100 | this.instances = {};
101 | }
102 | }
--------------------------------------------------------------------------------
/src/application/managers/MetadataManager.ts:
--------------------------------------------------------------------------------
1 | // src/core/managers/implementations/MetadataManager.ts
2 |
3 | import {IMetadataManager} from './interfaces/IMetadataManager.js';
4 | import {IManager} from './interfaces/IManager.js';
5 | import {GraphValidator} from '@core/index.js';
6 | import type {Metadata, MetadataAddition, MetadataResult, MetadataDeletion} from '@core/index.js';
7 |
8 | /**
9 | * Implements metadata-related operations for the knowledge graph.
10 | * Includes adding, deleting, and retrieving metadata associated with nodes.
11 | */
12 | export class MetadataManager extends IManager implements IMetadataManager {
13 | /**
14 | * Adds metadata to existing nodes.
15 | */
16 | async addMetadata(metadata: MetadataAddition[]): Promise {
17 | try {
18 | this.emit('beforeAddMetadata', {metadata});
19 |
20 | const graph = await this.storage.loadGraph();
21 | const results: MetadataResult[] = [];
22 |
23 | for (const item of metadata) {
24 | GraphValidator.validateNodeExists(graph, item.nodeName);
25 | const node = graph.nodes.find(e => e.name === item.nodeName);
26 |
27 | if (!Array.isArray(node!.metadata)) {
28 | node!.metadata = [];
29 | }
30 |
31 | const newMetadata = item.contents.filter(content =>
32 | !node!.metadata.includes(content)
33 | );
34 |
35 | node!.metadata.push(...newMetadata);
36 | results.push({
37 | nodeName: item.nodeName,
38 | addedMetadata: newMetadata
39 | });
40 | }
41 |
42 | await this.storage.saveGraph(graph);
43 |
44 | this.emit('afterAddMetadata', {results});
45 | return results;
46 | } catch (error) {
47 | const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
48 | throw new Error(errorMessage);
49 | }
50 | }
51 |
52 | /**
53 | * Deletes metadata from nodes.
54 | */
55 | async deleteMetadata(deletions: MetadataDeletion[]): Promise {
56 | try {
57 | this.emit('beforeDeleteMetadata', {deletions});
58 |
59 | const graph = await this.storage.loadGraph();
60 | let deletedCount = 0;
61 |
62 | for (const deletion of deletions) {
63 | GraphValidator.validateNodeExists(graph, deletion.nodeName);
64 | const node = graph.nodes.find(e => e.name === deletion.nodeName);
65 |
66 | if (node) {
67 | const initialMetadataCount = node.metadata.length;
68 | node.metadata = node.metadata.filter(o =>
69 | !deletion.metadata.includes(o)
70 | );
71 | deletedCount += initialMetadataCount - node.metadata.length;
72 | }
73 | }
74 |
75 | await this.storage.saveGraph(graph);
76 |
77 | this.emit('afterDeleteMetadata', {deletedCount});
78 | } catch (error) {
79 | const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
80 | throw new Error(errorMessage);
81 | }
82 | }
83 |
84 | /**
85 | * Retrieves metadata for a specific node.
86 | */
87 | async getMetadata(nodeName: string): Promise {
88 | try {
89 | const graph = await this.storage.loadGraph();
90 | GraphValidator.validateNodeExists(graph, nodeName);
91 | const node = graph.nodes.find(e => e.name === nodeName);
92 |
93 | return node!.metadata || [];
94 | } catch (error) {
95 | const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
96 | throw new Error(errorMessage);
97 | }
98 | }
99 | }
--------------------------------------------------------------------------------
/src/application/managers/NodeManager.ts:
--------------------------------------------------------------------------------
1 | // src/core/managers/implementations/NodeManager.ts
2 |
3 | import {IManager} from './interfaces/IManager.js';
4 | import {INodeManager} from './interfaces/INodeManager.js';
5 | import {GraphValidator} from '@core/index.js';
6 | import type {Node} from '@core/index.js';
7 |
8 | /**
9 | * Implements node-related operations for the knowledge graph.
10 | * Includes adding, updating, deleting, and retrieving nodes.
11 | */
12 | export class NodeManager extends IManager implements INodeManager {
13 | /**
14 | * Adds new nodes to the knowledge graph.
15 | */
16 | async addNodes(nodes: Node[]): Promise {
17 | try {
18 | this.emit('beforeAddNodes', {nodes});
19 |
20 | const graph = await this.storage.loadGraph();
21 | const newNodes: Node[] = [];
22 |
23 | for (const node of nodes) {
24 | GraphValidator.validateNodeProperties(node);
25 | GraphValidator.validateNodeDoesNotExist(graph, node.name);
26 | newNodes.push(node);
27 | }
28 |
29 | graph.nodes.push(...newNodes);
30 | await this.storage.saveGraph(graph);
31 |
32 | this.emit('afterAddNodes', {nodes: newNodes});
33 | return newNodes;
34 | } catch (error) {
35 | const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
36 | throw new Error(errorMessage);
37 | }
38 | }
39 |
40 | /**
41 | * Updates existing nodes in the knowledge graph.
42 | */
43 | async updateNodes(nodes: Partial[]): Promise {
44 | try {
45 | this.emit('beforeUpdateNodes', {nodes});
46 |
47 | const graph = await this.storage.loadGraph();
48 | const updatedNodes: Node[] = [];
49 |
50 | for (const updateNode of nodes) {
51 | GraphValidator.validateNodeNameProperty(updateNode);
52 | const nodeIndex = graph.nodes.findIndex(n => n.name === updateNode.name);
53 |
54 | if (nodeIndex === -1) {
55 | throw new Error(`Node not found: ${updateNode.name}`);
56 | }
57 |
58 | graph.nodes[nodeIndex] = {
59 | ...graph.nodes[nodeIndex],
60 | ...updateNode
61 | };
62 | updatedNodes.push(graph.nodes[nodeIndex]);
63 | }
64 |
65 | await this.storage.saveGraph(graph);
66 |
67 | this.emit('afterUpdateNodes', {nodes: updatedNodes});
68 | return updatedNodes;
69 | } catch (error) {
70 | const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
71 | throw new Error(errorMessage);
72 | }
73 | }
74 |
75 | /**
76 | * Deletes nodes and their associated edges from the knowledge graph.
77 | */
78 | async deleteNodes(nodeNames: string[]): Promise {
79 | try {
80 | GraphValidator.validateNodeNamesArray(nodeNames);
81 | this.emit('beforeDeleteNodes', {nodeNames});
82 |
83 | const graph = await this.storage.loadGraph();
84 | const initialNodeCount = graph.nodes.length;
85 |
86 | graph.nodes = graph.nodes.filter(node => !nodeNames.includes(node.name));
87 | graph.edges = graph.edges.filter(edge =>
88 | !nodeNames.includes(edge.from) && !nodeNames.includes(edge.to)
89 | );
90 |
91 | const deletedCount = initialNodeCount - graph.nodes.length;
92 |
93 | await this.storage.saveGraph(graph);
94 |
95 | this.emit('afterDeleteNodes', {deletedCount});
96 | } catch (error) {
97 | const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
98 | throw new Error(errorMessage);
99 | }
100 | }
101 |
102 | /**
103 | * Retrieves specific nodes from the knowledge graph by their names.
104 | */
105 | async getNodes(nodeNames: string[]): Promise {
106 | try {
107 | const graph = await this.storage.loadGraph();
108 | return graph.nodes.filter(node => nodeNames.includes(node.name));
109 | } catch (error) {
110 | const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
111 | throw new Error(errorMessage);
112 | }
113 | }
114 | }
--------------------------------------------------------------------------------
/src/application/managers/SearchManager.ts:
--------------------------------------------------------------------------------
1 | // src/application/managers/SearchManager.ts
2 |
3 | import {ISearchManager} from './interfaces/ISearchManager.js';
4 | import {IManager} from './interfaces/IManager.js';
5 | import type {Graph} from '@core/index.js';
6 |
7 | /**
8 | * Implements search-related operations for the knowledge graph.
9 | * Provides functionality for searching nodes and retrieving graph data.
10 | */
11 | export class SearchManager extends IManager implements ISearchManager {
12 | /**
13 | * Searches for nodes in the knowledge graph based on a query.
14 | * Includes both matching nodes and their immediate neighbors.
15 | */
16 | async searchNodes(query: string): Promise {
17 | try {
18 | this.emit('beforeSearch', {query});
19 |
20 | const graph = await this.storage.loadGraph();
21 |
22 | // Find directly matching nodes
23 | const matchingNodes = graph.nodes.filter(node =>
24 | node.name.toLowerCase().includes(query.toLowerCase()) ||
25 | node.nodeType.toLowerCase().includes(query.toLowerCase()) ||
26 | node.metadata.some(meta =>
27 | meta.toLowerCase().includes(query.toLowerCase())
28 | )
29 | );
30 |
31 | // Get names of matching nodes for efficient lookup
32 | const matchingNodeNames = new Set(matchingNodes.map(node => node.name));
33 |
34 | // Find all edges connected to matching nodes
35 | const connectedEdges = graph.edges.filter(edge =>
36 | matchingNodeNames.has(edge.from) || matchingNodeNames.has(edge.to)
37 | );
38 |
39 | // Get names of all neighbor nodes from the edges
40 | const neighborNodeNames = new Set();
41 | connectedEdges.forEach(edge => {
42 | if (matchingNodeNames.has(edge.from)) {
43 | neighborNodeNames.add(edge.to);
44 | }
45 | if (matchingNodeNames.has(edge.to)) {
46 | neighborNodeNames.add(edge.from);
47 | }
48 | });
49 |
50 | // Get all neighbor nodes
51 | const neighborNodes = graph.nodes.filter(node =>
52 | !matchingNodeNames.has(node.name) && neighborNodeNames.has(node.name)
53 | );
54 |
55 | // Combine matching nodes and their neighbors
56 | const resultNodes = [...matchingNodes, ...neighborNodes];
57 |
58 | const result: Graph = {
59 | nodes: resultNodes,
60 | edges: connectedEdges
61 | };
62 |
63 | this.emit('afterSearch', result);
64 | return result;
65 | } catch (error) {
66 | const message = error instanceof Error ? error.message : 'Unknown error occurred';
67 | throw new Error(`Search operation failed: ${message}`);
68 | }
69 | }
70 |
71 | /**
72 | * Retrieves specific nodes and their immediate neighbors from the knowledge graph.
73 | */
74 | async openNodes(names: string[]): Promise {
75 | try {
76 | this.emit('beforeOpenNodes', {names});
77 |
78 | const graph = await this.storage.loadGraph();
79 |
80 | // Get the requested nodes
81 | const requestedNodes = graph.nodes.filter(node =>
82 | names.includes(node.name)
83 | );
84 |
85 | // Get names of requested nodes for efficient lookup
86 | const requestedNodeNames = new Set(requestedNodes.map(node => node.name));
87 |
88 | // Find all edges connected to requested nodes
89 | const connectedEdges = graph.edges.filter(edge =>
90 | requestedNodeNames.has(edge.from) || requestedNodeNames.has(edge.to)
91 | );
92 |
93 | // Get names of all neighbor nodes from the edges
94 | const neighborNodeNames = new Set();
95 | connectedEdges.forEach(edge => {
96 | if (requestedNodeNames.has(edge.from)) {
97 | neighborNodeNames.add(edge.to);
98 | }
99 | if (requestedNodeNames.has(edge.to)) {
100 | neighborNodeNames.add(edge.from);
101 | }
102 | });
103 |
104 | // Get all neighbor nodes
105 | const neighborNodes = graph.nodes.filter(node =>
106 | !requestedNodeNames.has(node.name) && neighborNodeNames.has(node.name)
107 | );
108 |
109 | // Combine requested nodes and their neighbors
110 | const resultNodes = [...requestedNodes, ...neighborNodes];
111 |
112 | const result: Graph = {
113 | nodes: resultNodes,
114 | edges: connectedEdges
115 | };
116 |
117 | this.emit('afterOpenNodes', result);
118 | return result;
119 | } catch (error) {
120 | const message = error instanceof Error ? error.message : 'Unknown error occurred';
121 | throw new Error(`Failed to open nodes: ${message}`);
122 | }
123 | }
124 |
125 | /**
126 | * Reads and returns the entire knowledge graph.
127 | */
128 | async readGraph(): Promise {
129 | try {
130 | this.emit('beforeReadGraph', {});
131 | const graph = await this.storage.loadGraph();
132 | this.emit('afterReadGraph', graph);
133 | return graph;
134 | } catch (error) {
135 | const message = error instanceof Error ? error.message : 'Unknown error occurred';
136 | throw new Error(`Failed to read graph: ${message}`);
137 | }
138 | }
139 |
140 | /**
141 | * Initializes the search manager.
142 | */
143 | async initialize(): Promise {
144 | try {
145 | await super.initialize();
146 | // Add any search-specific initialization here
147 | } catch (error) {
148 | const message = error instanceof Error ? error.message : 'Unknown error occurred';
149 | throw new Error(`Failed to initialize SearchManager: ${message}`);
150 | }
151 | }
152 | }
--------------------------------------------------------------------------------
/src/application/managers/TransactionManager.ts:
--------------------------------------------------------------------------------
1 | // src/core/managers/implementations/TransactionManager.ts
2 |
3 | import {ITransactionManager, RollbackAction} from './interfaces/ITransactionManager.js';
4 | import { IManager } from './interfaces/IManager.js';
5 | import type {Graph} from '@core/index.js';
6 | import type {IStorage} from '@infrastructure/index.js';
7 |
8 | /**
9 | * Implements transaction-related operations for the knowledge graph.
10 | * Handles transaction lifecycle, rollback actions, and maintaining transaction state.
11 | */
12 | export class TransactionManager extends IManager implements ITransactionManager {
13 | private graph: Graph;
14 | private rollbackActions: RollbackAction[];
15 | private inTransaction: boolean;
16 |
17 | constructor(storage: IStorage) {
18 | super(storage);
19 | this.graph = {nodes: [], edges: []};
20 | this.rollbackActions = [];
21 | this.inTransaction = false;
22 | }
23 |
24 | /**
25 | * Begins a new transaction.
26 | * @throws Error if a transaction is already in progress
27 | */
28 | async beginTransaction(): Promise {
29 | if (this.inTransaction) {
30 | throw new Error('Transaction already in progress');
31 | }
32 |
33 | this.emit('beforeBeginTransaction', {});
34 |
35 | try {
36 | // Load current state
37 | this.graph = await this.storage.loadGraph();
38 | this.rollbackActions = [];
39 | this.inTransaction = true;
40 |
41 | this.emit('afterBeginTransaction', {});
42 | } catch (error) {
43 | const message = error instanceof Error ? error.message : 'Unknown error occurred';
44 | throw new Error(`Failed to begin transaction: ${message}`);
45 | }
46 | }
47 |
48 | /**
49 | * Adds a rollback action to be executed if the transaction is rolled back.
50 | * @throws Error if no transaction is in progress
51 | */
52 | async addRollbackAction(action: () => Promise, description: string): Promise {
53 | if (!this.inTransaction) {
54 | throw new Error('No transaction in progress');
55 | }
56 |
57 | this.rollbackActions.push({action, description});
58 | }
59 |
60 | /**
61 | * Commits the current transaction.
62 | * @throws Error if no transaction is in progress
63 | */
64 | async commit(): Promise {
65 | if (!this.inTransaction) {
66 | throw new Error('No transaction to commit');
67 | }
68 |
69 | this.emit('beforeCommit', {});
70 |
71 | try {
72 | // Clear the transaction state
73 | this.rollbackActions = [];
74 | this.inTransaction = false;
75 |
76 | this.emit('afterCommit', {});
77 | } catch (error) {
78 | const message = error instanceof Error ? error.message : 'Unknown error occurred';
79 | throw new Error(`Failed to commit transaction: ${message}`);
80 | }
81 | }
82 |
83 | /**
84 | * Rolls back the current transaction, executing all rollback actions in reverse order.
85 | * @throws Error if no transaction is in progress
86 | */
87 | async rollback(): Promise {
88 | if (!this.inTransaction) {
89 | throw new Error('No transaction to rollback');
90 | }
91 |
92 | this.emit('beforeRollback', {actions: this.rollbackActions});
93 |
94 | try {
95 | // Execute rollback actions in reverse order
96 | for (const {action, description} of this.rollbackActions.reverse()) {
97 | try {
98 | await action();
99 | } catch (error) {
100 | console.error(`Error during rollback action (${description}):`, error);
101 | // Continue with other rollbacks even if one fails
102 | }
103 | }
104 |
105 | // Clear the transaction state
106 | this.rollbackActions = [];
107 | this.inTransaction = false;
108 |
109 | this.emit('afterRollback', {});
110 | } catch (error) {
111 | const message = error instanceof Error ? error.message : 'Unknown error occurred';
112 | throw new Error(`Failed to rollback transaction: ${message}`);
113 | }
114 | }
115 |
116 | /**
117 | * Gets the current graph state within the transaction.
118 | */
119 | getCurrentGraph(): Graph {
120 | return this.graph;
121 | }
122 |
123 | /**
124 | * Checks if a transaction is currently in progress.
125 | */
126 | isInTransaction(): boolean {
127 | return this.inTransaction;
128 | }
129 |
130 | /**
131 | * Executes an operation within a transaction, handling commit and rollback automatically.
132 | */
133 | async withTransaction(operation: () => Promise): Promise {
134 | await this.beginTransaction();
135 | try {
136 | const result = await operation();
137 | await this.commit();
138 | return result;
139 | } catch (error) {
140 | await this.rollback();
141 | throw error;
142 | }
143 | }
144 | }
--------------------------------------------------------------------------------
/src/application/managers/base/BaseManager.ts:
--------------------------------------------------------------------------------
1 | // src/application/managers/base/BaseManager.ts
2 |
3 | import type {IStorage} from '@infrastructure/index.js';
4 | import {JsonLineStorage} from '@infrastructure/index.js';
5 | import {ManagerFactory} from '@application/index.js';
6 |
7 | /**
8 | * Base class that handles initialization and common functionality
9 | */
10 | export abstract class BaseManager {
11 | protected readonly storage: IStorage;
12 |
13 | constructor(storage: IStorage = new JsonLineStorage()) {
14 | this.storage = storage;
15 | }
16 |
17 | protected createManagers() {
18 | return {
19 | nodeManager: ManagerFactory.getNodeManager(this.storage),
20 | edgeManager: ManagerFactory.getEdgeManager(this.storage),
21 | metadataManager: ManagerFactory.getMetadataManager(this.storage),
22 | searchManager: ManagerFactory.getSearchManager(this.storage),
23 | transactionManager: ManagerFactory.getTransactionManager(this.storage)
24 | };
25 | }
26 | }
--------------------------------------------------------------------------------
/src/application/managers/index.ts:
--------------------------------------------------------------------------------
1 | export {BaseManager} from './base/BaseManager.js';
2 | export {EdgeManager} from './EdgeManager.js';
3 | export {GraphManager} from './GraphManager.js';
4 | export {MetadataManager} from './MetadataManager.js';
5 | export {NodeManager} from './NodeManager.js';
6 | export {SearchManager} from './SearchManager.js';
7 | export {TransactionManager} from './TransactionManager.js';
8 | export {ManagerFactory} from './ManagerFactory.js';
--------------------------------------------------------------------------------
/src/application/managers/interfaces/IEdgeManager.ts:
--------------------------------------------------------------------------------
1 | // src/application/managers/interfaces/IEdgeManager.ts
2 |
3 | import {IManager} from './IManager.js';
4 | import type {EdgeUpdate, EdgeFilter} from '@shared/index.js';
5 | import type {Edge} from '@core/index.js'
6 |
7 | /**
8 | * Interface for edge-related operations in the knowledge graph.
9 | * Defines the contract for managing edges, including adding, updating, deleting, and retrieving edges.
10 | */
11 | export abstract class IEdgeManager extends IManager {
12 | /**
13 | * Adds new edges to the knowledge graph.
14 | */
15 | abstract addEdges(edges: Edge[]): Promise;
16 |
17 | /**
18 | * Updates existing edges in the knowledge graph.
19 | */
20 | abstract updateEdges(edges: EdgeUpdate[]): Promise;
21 |
22 | /**
23 | * Deletes edges from the knowledge graph.
24 | */
25 | abstract deleteEdges(edges: Edge[]): Promise;
26 |
27 | /**
28 | * Retrieves edges from the knowledge graph based on filter criteria.
29 | */
30 | abstract getEdges(filter?: EdgeFilter): Promise;
31 | }
--------------------------------------------------------------------------------
/src/application/managers/interfaces/IManager.ts:
--------------------------------------------------------------------------------
1 | // src/application/managers/interfaces/IManager.ts
2 |
3 | import {EventEmitter, IStorage} from '@infrastructure/index.js';
4 |
5 | /**
6 | * Abstract base class for all manager interfaces.
7 | * Provides event emission capabilities and implements common initialization.
8 | */
9 | export abstract class IManager extends EventEmitter {
10 | /**
11 | * The storage instance used by the manager.
12 | */
13 | protected storage: IStorage;
14 |
15 | /**
16 | * Creates an instance of IManager.
17 | * @param storage - The storage mechanism to use for persisting the knowledge graph.
18 | * @throws {Error} If attempting to instantiate the abstract class directly.
19 | */
20 | constructor(storage: IStorage) {
21 | super();
22 | if (new.target === IManager) {
23 | throw new Error('IManager is an abstract class');
24 | }
25 | this.storage = storage;
26 | }
27 |
28 | /**
29 | * Initializes the manager by emitting the 'initialized' event.
30 | * Common implementation for all manager classes.
31 | */
32 | public async initialize(): Promise {
33 | this.emit('initialized', {manager: this.constructor.name});
34 | }
35 | }
--------------------------------------------------------------------------------
/src/application/managers/interfaces/IManagerOperations.ts:
--------------------------------------------------------------------------------
1 | // src/application/managers/interfaces/IManagerOperations.ts
2 |
3 | import type {
4 | Node,
5 | Edge,
6 | Metadata,
7 | Graph,
8 | MetadataAddition,
9 | MetadataResult,
10 | MetadataDeletion
11 | } from '@core/index.js';
12 | import type {
13 | EdgeUpdate,
14 | EdgeFilter
15 | } from '@shared/index.js';
16 |
17 | /**
18 | * Base interface for all manager operations
19 | */
20 | export interface IManagerOperations {
21 | initialize(): Promise;
22 | }
23 |
24 | /**
25 | * Node manager specific operations
26 | */
27 | export interface INodeOperations extends IManagerOperations {
28 | addNodes(nodes: Node[]): Promise;
29 |
30 | updateNodes(nodes: Partial[]): Promise;
31 |
32 | deleteNodes(nodeNames: string[]): Promise;
33 |
34 | getNodes(nodeNames: string[]): Promise;
35 | }
36 |
37 | /**
38 | * Edge manager specific operations
39 | */
40 | export interface IEdgeOperations extends IManagerOperations {
41 | addEdges(edges: Edge[]): Promise;
42 |
43 | updateEdges(edges: EdgeUpdate[]): Promise;
44 |
45 | deleteEdges(edges: Edge[]): Promise;
46 |
47 | getEdges(filter?: EdgeFilter): Promise;
48 | }
49 |
50 | /**
51 | * Metadata manager specific operations
52 | */
53 | export interface IMetadataOperations extends IManagerOperations {
54 | addMetadata(metadata: MetadataAddition[]): Promise;
55 |
56 | deleteMetadata(deletions: MetadataDeletion[]): Promise;
57 |
58 | getMetadata(nodeName: string): Promise;
59 | }
60 |
61 | /**
62 | * Search manager specific operations
63 | */
64 | export interface ISearchOperations extends IManagerOperations {
65 | searchNodes(query: string): Promise;
66 |
67 | openNodes(names: string[]): Promise;
68 |
69 | readGraph(): Promise;
70 | }
--------------------------------------------------------------------------------
/src/application/managers/interfaces/IMetadataManager.ts:
--------------------------------------------------------------------------------
1 | // src/application/managers/interfaces/IMetadataManager.ts
2 |
3 | import {IManager} from './IManager.js';
4 | import type {Metadata, MetadataAddition, MetadataResult, MetadataDeletion} from '@core/index.js';
5 |
6 | /**
7 | * Interface for metadata-related operations in the knowledge graph.
8 | * Defines the contract for managing metadata, including adding, deleting, and retrieving metadata for nodes.
9 | */
10 | export interface IMetadataManager extends IManager {
11 | /**
12 | * Adds metadata to existing nodes.
13 | */
14 | addMetadata(metadata: MetadataAddition[]): Promise;
15 |
16 | /**
17 | * Deletes metadata from nodes.
18 | */
19 | deleteMetadata(deletions: MetadataDeletion[]): Promise;
20 |
21 | /**
22 | * Retrieves metadata for a specific node.
23 | */
24 | getMetadata(nodeName: string): Promise;
25 | }
--------------------------------------------------------------------------------
/src/application/managers/interfaces/INodeManager.ts:
--------------------------------------------------------------------------------
1 | // src/application/managers/interfaces/INodeManager.ts
2 |
3 | import {IManager} from './IManager.js';
4 | import type {Node} from '@core/index.js';
5 |
6 | /**
7 | * Interface for node-related operations in the knowledge graph.
8 | * Defines the contract for managing nodes, including adding, updating, deleting, and retrieving nodes.
9 | */
10 | export interface INodeManager extends IManager {
11 | /**
12 | * Adds new nodes to the knowledge graph.
13 | */
14 | addNodes(nodes: Node[]): Promise;
15 |
16 | /**
17 | * Updates existing nodes in the knowledge graph.
18 | */
19 | updateNodes(nodes: Partial[]): Promise;
20 |
21 | /**
22 | * Deletes nodes from the knowledge graph.
23 | */
24 | deleteNodes(nodeNames: string[]): Promise;
25 |
26 | /**
27 | * Retrieves specific nodes from the knowledge graph by their names.
28 | */
29 | getNodes(nodeNames: string[]): Promise;
30 | }
--------------------------------------------------------------------------------
/src/application/managers/interfaces/ISearchManager.ts:
--------------------------------------------------------------------------------
1 | // src/application/managers/interfaces/ISearchManager.ts
2 |
3 | import {IManager} from './IManager.js';
4 | import type {Graph} from '@core/index.js';
5 |
6 | /**
7 | * Interface for search-related operations in the knowledge graph.
8 | * Defines the contract for searching nodes based on queries and retrieving specific nodes.
9 | */
10 | export interface ISearchManager extends IManager {
11 | /**
12 | * Searches for nodes in the knowledge graph based on a query.
13 | */
14 | searchNodes(query: string): Promise;
15 |
16 | /**
17 | * Retrieves specific nodes from the knowledge graph by their names.
18 | */
19 | openNodes(names: string[]): Promise;
20 |
21 | /**
22 | * Reads and returns the entire knowledge graph.
23 | */
24 | readGraph(): Promise;
25 | }
--------------------------------------------------------------------------------
/src/application/managers/interfaces/ITransactionManager.ts:
--------------------------------------------------------------------------------
1 | // src/application/managers/interfaces/ITransactionManager.ts
2 |
3 | import type {Graph} from '@core/index.js';
4 | import {IManager} from './IManager.js';
5 |
6 | export interface RollbackAction {
7 | action: () => Promise;
8 | description: string;
9 | }
10 |
11 | export abstract class ITransactionManager extends IManager {
12 | abstract beginTransaction(): Promise;
13 |
14 | abstract addRollbackAction(action: () => Promise, description: string): Promise;
15 |
16 | abstract commit(): Promise;
17 |
18 | abstract rollback(): Promise;
19 |
20 | abstract getCurrentGraph(): Graph;
21 |
22 | abstract isInTransaction(): boolean;
23 | }
24 |
--------------------------------------------------------------------------------
/src/application/managers/interfaces/index.ts:
--------------------------------------------------------------------------------
1 | // src/application/managers/interfaces/index.ts
2 |
3 | export {IManager} from './IManager.js';
4 | export {INodeManager} from './INodeManager.js';
5 | export {IEdgeManager} from './IEdgeManager.js';
6 | export {IMetadataManager} from './IMetadataManager.js';
7 | export {ISearchManager} from './ISearchManager.js';
8 | export {ITransactionManager, RollbackAction} from './ITransactionManager.js';
9 | export {
10 | IManagerOperations,
11 | INodeOperations,
12 | IEdgeOperations,
13 | IMetadataOperations,
14 | ISearchOperations
15 | } from './IManagerOperations.js';
--------------------------------------------------------------------------------
/src/application/operations/GraphOperations.ts:
--------------------------------------------------------------------------------
1 | // src/core/operations/GraphOperations.ts
2 |
3 | import {EventEmitter} from '@infrastructure/index.js';
4 | import type {
5 | INodeManager,
6 | IEdgeManager,
7 | IMetadataManager
8 | } from '@application/index.js';
9 | import type {
10 | Node,
11 | Edge,
12 | MetadataAddition,
13 | MetadataDeletion,
14 | MetadataResult
15 | } from '@core/index.js';
16 | import type {
17 | EdgeFilter,
18 | GetEdgesResult
19 | } from '@shared/index.js';
20 |
21 | export class GraphOperations extends EventEmitter {
22 | constructor(
23 | private nodeManager: INodeManager,
24 | private edgeManager: IEdgeManager,
25 | private metadataManager: IMetadataManager
26 | ) {
27 | super();
28 | }
29 |
30 | async addNodes(nodes: Node[]): Promise {
31 | this.emit('beforeAddNodes', {nodes});
32 | const result = await this.nodeManager.addNodes(nodes);
33 | this.emit('afterAddNodes', {nodes: result});
34 | return result;
35 | }
36 |
37 | async updateNodes(nodes: Partial[]): Promise {
38 | this.emit('beforeUpdateNodes', {nodes});
39 | const result = await this.nodeManager.updateNodes(nodes);
40 | this.emit('afterUpdateNodes', {nodes: result});
41 | return result;
42 | }
43 |
44 | async deleteNodes(nodeNames: string[]): Promise {
45 | this.emit('beforeDeleteNodes', {nodeNames});
46 | await this.nodeManager.deleteNodes(nodeNames);
47 | this.emit('afterDeleteNodes', {nodeNames});
48 | }
49 |
50 | async addEdges(edges: Edge[]): Promise {
51 | this.emit('beforeAddEdges', {edges});
52 | const result = await this.edgeManager.addEdges(edges);
53 | this.emit('afterAddEdges', {edges: result});
54 | return result;
55 | }
56 |
57 | async updateEdges(edges: Edge[]): Promise {
58 | this.emit('beforeUpdateEdges', {edges});
59 | const result = await this.edgeManager.updateEdges(edges);
60 | this.emit('afterUpdateEdges', {edges: result});
61 | return result;
62 | }
63 |
64 | async deleteEdges(edges: Edge[]): Promise {
65 | this.emit('beforeDeleteEdges', {edges});
66 | await this.edgeManager.deleteEdges(edges);
67 | this.emit('afterDeleteEdges', {edges});
68 | }
69 |
70 | async getEdges(filter?: EdgeFilter): Promise {
71 | const edges = await this.edgeManager.getEdges(filter);
72 | return {edges};
73 | }
74 |
75 | async addMetadata(metadata: MetadataAddition[]): Promise {
76 | this.emit('beforeAddMetadata', {metadata});
77 | const result = await this.metadataManager.addMetadata(metadata);
78 | this.emit('afterAddMetadata', {results: result});
79 | return result;
80 | }
81 |
82 | async deleteMetadata(deletions: MetadataDeletion[]): Promise {
83 | this.emit('beforeDeleteMetadata', {deletions});
84 | await this.metadataManager.deleteMetadata(deletions);
85 | this.emit('afterDeleteMetadata', {deletions});
86 | }
87 | }
--------------------------------------------------------------------------------
/src/application/operations/SearchOperations.ts:
--------------------------------------------------------------------------------
1 | // src/core/operations/SearchOperations.ts
2 |
3 | import {EventEmitter} from '@infrastructure/index.js';
4 | import type {ISearchManager} from '@application/index.js';
5 | import type {OpenNodesResult} from '@shared/index.js';
6 | import type {Graph} from '@core/index.js';
7 |
8 | export class SearchOperations extends EventEmitter {
9 | constructor(private searchManager: ISearchManager) {
10 | super();
11 | }
12 |
13 | async searchNodes(query: string): Promise {
14 | this.emit('beforeSearch', {query});
15 | const result = await this.searchManager.searchNodes(query);
16 | this.emit('afterSearch', result);
17 | return result;
18 | }
19 |
20 | async openNodes(names: string[]): Promise {
21 | this.emit('beforeOpenNodes', {names});
22 | const result = await this.searchManager.openNodes(names);
23 | this.emit('afterOpenNodes', result);
24 | return result;
25 | }
26 |
27 | async readGraph(): Promise {
28 | this.emit('beforeReadGraph', {});
29 | const result = await this.searchManager.readGraph();
30 | this.emit('afterReadGraph', result);
31 | return result;
32 | }
33 | }
--------------------------------------------------------------------------------
/src/application/operations/TransactionOperations.ts:
--------------------------------------------------------------------------------
1 | // src/core/operations/TransactionOperations.ts
2 |
3 | import {EventEmitter} from '@infrastructure/index.js';
4 | import type {ITransactionManager} from '@application/index.js';
5 | import type {Graph} from '@core/index.js';
6 |
7 | export class TransactionOperations extends EventEmitter {
8 | constructor(private transactionManager: ITransactionManager) {
9 | super();
10 | }
11 |
12 | async beginTransaction(): Promise {
13 | this.emit('beforeBeginTransaction', {});
14 | await this.transactionManager.beginTransaction();
15 | this.emit('afterBeginTransaction', {});
16 | }
17 |
18 | async commit(): Promise {
19 | this.emit('beforeCommit', {});
20 | await this.transactionManager.commit();
21 | this.emit('afterCommit', {});
22 | }
23 |
24 | async rollback(): Promise {
25 | this.emit('beforeRollback', {});
26 | await this.transactionManager.rollback();
27 | this.emit('afterRollback', {});
28 | }
29 |
30 | async withTransaction(operation: () => Promise): Promise {
31 | await this.beginTransaction();
32 | try {
33 | const result = await operation();
34 | await this.commit();
35 | return result;
36 | } catch (error) {
37 | await this.rollback();
38 | throw error;
39 | }
40 | }
41 |
42 | async addRollbackAction(action: () => Promise, description: string): Promise {
43 | await this.transactionManager.addRollbackAction(action, description);
44 | }
45 |
46 | isInTransaction(): boolean {
47 | return this.transactionManager.isInTransaction();
48 | }
49 |
50 | getCurrentGraph(): Graph {
51 | return this.transactionManager.getCurrentGraph();
52 | }
53 | }
--------------------------------------------------------------------------------
/src/application/operations/index.ts:
--------------------------------------------------------------------------------
1 | // src/application/operations/index.ts
2 |
3 | export {GraphOperations} from './GraphOperations.js';
4 | export {SearchOperations} from './SearchOperations.js';
5 | export {TransactionOperations} from './TransactionOperations.js';
--------------------------------------------------------------------------------
/src/config/config.ts:
--------------------------------------------------------------------------------
1 | // src/config/config.ts
2 |
3 | import path from 'path';
4 | import {fileURLToPath} from 'url';
5 |
6 | const __dirname = path.dirname(fileURLToPath(import.meta.url));
7 |
8 | interface ServerConfig {
9 | NAME: string;
10 | VERSION: string;
11 | }
12 |
13 | interface PathsConfig {
14 | SCHEMAS_DIR: string;
15 | MEMORY_FILE: string;
16 | }
17 |
18 | interface SchemaConfig {
19 | SUPPORTED_VERSIONS: string[];
20 | }
21 |
22 | interface Config {
23 | SERVER: ServerConfig;
24 | PATHS: PathsConfig;
25 | SCHEMA: SchemaConfig;
26 | }
27 |
28 | /**
29 | * Centralized configuration for MemoryMesh.
30 | */
31 | export const CONFIG: Config = {
32 | SERVER: {
33 | NAME: 'memorymesh',
34 | VERSION: '0.2.8',
35 | },
36 |
37 | PATHS: {
38 | /** Path to schema files directory. */
39 | SCHEMAS_DIR: path.join(__dirname, '..', 'data', 'schemas'),
40 | /** Path to the memory JSON file. */
41 | MEMORY_FILE: path.join(__dirname, '..', 'data', 'memory.json'),
42 | },
43 |
44 | SCHEMA: {
45 | /** Supported schema versions (not yet implemented). */
46 | SUPPORTED_VERSIONS: ['0.1', '0.2'], // TODO: Add schema versioning
47 | },
48 | };
--------------------------------------------------------------------------------
/src/config/index.ts:
--------------------------------------------------------------------------------
1 | // src/config/index.ts
2 |
3 | export {CONFIG} from './config.js';
--------------------------------------------------------------------------------
/src/core/graph/Edge.ts:
--------------------------------------------------------------------------------
1 | // src/core/graph/Edge.ts
2 |
3 | /**
4 | * Represents an edge connecting two nodes in the knowledge graph
5 | */
6 | export interface Edge {
7 | type: 'edge';
8 | from: string;
9 | to: string;
10 | edgeType: string;
11 | weight?: number; // Optional weight property (0-1 range)
12 | }
--------------------------------------------------------------------------------
/src/core/graph/EdgeWeightUtils.ts:
--------------------------------------------------------------------------------
1 | // src/core/graph/EdgeWeightUtils.ts
2 |
3 | import type {Edge} from './Edge.js';
4 |
5 | /**
6 | * Utility functions for working with edge weights in the knowledge graph
7 | */
8 | export class EdgeWeightUtils {
9 | /**
10 | * Validates that a weight is within the valid range (0-1)
11 | */
12 | static validateWeight(weight: number): void {
13 | if (weight < 0 || weight > 1) {
14 | throw new Error('Edge weight must be between 0 and 1');
15 | }
16 | }
17 |
18 | /**
19 | * Sets a default weight for an edge if none is provided
20 | */
21 | static ensureWeight(edge: Edge): Edge {
22 | if (edge.weight === undefined) {
23 | return {
24 | ...edge,
25 | weight: 1 // Default to maximum weight
26 | };
27 | }
28 | return edge;
29 | }
30 |
31 | /**
32 | * Updates the weight of an edge based on new evidence
33 | * Uses a simple averaging approach
34 | */
35 | static updateWeight(currentWeight: number, newEvidence: number): number {
36 | this.validateWeight(newEvidence);
37 | return (currentWeight + newEvidence) / 2;
38 | }
39 |
40 | /**
41 | * Combines multiple edge weights (e.g., for parallel edges)
42 | * Uses the maximum weight by default
43 | */
44 | static combineWeights(weights: number[]): number {
45 | if (weights.length === 0) return 1;
46 | return Math.max(...weights);
47 | }
48 | }
--------------------------------------------------------------------------------
/src/core/graph/Graph.ts:
--------------------------------------------------------------------------------
1 | // src/core/graph/Graph.ts
2 |
3 | import type {Node} from './Node.js';
4 | import type {Edge} from './Edge.js';
5 |
6 | /**
7 | * Represents the complete knowledge graph structure
8 | */
9 | export interface Graph {
10 | nodes: Node[];
11 | edges: Edge[];
12 | }
--------------------------------------------------------------------------------
/src/core/graph/GraphValidator.ts:
--------------------------------------------------------------------------------
1 | // src/core/graph/GraphValidator.ts
2 |
3 | import type {Graph} from './Graph.js';
4 | import type {Node} from './Node.js';
5 | import type {Edge} from './Edge.js';
6 | import {EdgeWeightUtils} from './EdgeWeightUtils.js';
7 |
8 | /**
9 | * Provides validation methods for graph operations
10 | */
11 | export class GraphValidator {
12 | /**
13 | * Validates that a node with the given name exists in the graph.
14 | */
15 | static validateNodeExists(graph: Graph, nodeName: string): void {
16 | if (!graph.nodes.some(node => node.name === nodeName)) {
17 | throw new Error(`Node not found: ${nodeName}`);
18 | }
19 | }
20 |
21 | /**
22 | * Validates that a node with the given name does not exist in the graph.
23 | */
24 | static validateNodeDoesNotExist(graph: Graph, nodeName: string): void {
25 | if (graph.nodes.some(node => node.name === nodeName)) {
26 | throw new Error(`Node already exists: ${nodeName}. Consider updating existing node.`);
27 | }
28 | }
29 |
30 | /**
31 | * Validates that an edge is unique in the graph.
32 | */
33 | static validateEdgeUniqueness(graph: Graph, edge: Edge): void {
34 | if (graph.edges.some(existing =>
35 | existing.from === edge.from &&
36 | existing.to === edge.to &&
37 | existing.edgeType === edge.edgeType
38 | )) {
39 | throw new Error(`Edge already exists: ${edge.from} -> ${edge.to} (${edge.edgeType})`);
40 | }
41 | }
42 |
43 | /**
44 | * Validates that a node has required properties.
45 | */
46 | static validateNodeProperties(node: Node): void {
47 | if (!node.name) {
48 | throw new Error("Node must have a 'name' property");
49 | }
50 | if (!node.nodeType) {
51 | throw new Error("Node must have a 'nodeType' property");
52 | }
53 | if (!Array.isArray(node.metadata)) {
54 | throw new Error("Node must have a 'metadata' array");
55 | }
56 | }
57 |
58 | /**
59 | * Validates that a partial node update has a name property.
60 | */
61 | static validateNodeNameProperty(node: Partial): void {
62 | if (!node.name) {
63 | throw new Error("Node must have a 'name' property for updating");
64 | }
65 | }
66 |
67 | /**
68 | * Validates that the provided value is a valid array of node names.
69 | */
70 | static validateNodeNamesArray(nodeNames: unknown): asserts nodeNames is string[] {
71 | if (!Array.isArray(nodeNames)) {
72 | throw new Error("nodeNames must be an array");
73 | }
74 | if (nodeNames.some(name => typeof name !== 'string')) {
75 | throw new Error("All node names must be strings");
76 | }
77 | }
78 |
79 | /**
80 | * Validates edge properties.
81 | */
82 | static validateEdgeProperties(edge: Edge): void {
83 | if (!edge.from) {
84 | throw new Error("Edge must have a 'from' property");
85 | }
86 | if (!edge.to) {
87 | throw new Error("Edge must have a 'to' property");
88 | }
89 | if (!edge.edgeType) {
90 | throw new Error("Edge must have an 'edgeType' property");
91 | }
92 | if (edge.weight !== undefined) {
93 | EdgeWeightUtils.validateWeight(edge.weight);
94 | }
95 | }
96 |
97 | /**
98 | * Validates that all referenced nodes in edges exist.
99 | */
100 | static validateEdgeReferences(graph: Graph, edges: Edge[]): void {
101 | for (const edge of edges) {
102 | this.validateNodeExists(graph, edge.from);
103 | this.validateNodeExists(graph, edge.to);
104 | }
105 | }
106 |
107 | /**
108 | * Validates the entire graph structure.
109 | */
110 | static validateGraphStructure(graph: Graph): void {
111 | if (!Array.isArray(graph.nodes)) {
112 | throw new Error("Graph must have a 'nodes' array");
113 | }
114 | if (!Array.isArray(graph.edges)) {
115 | throw new Error("Graph must have an 'edges' array");
116 | }
117 |
118 | // Validate all nodes
119 | graph.nodes.forEach(node => this.validateNodeProperties(node));
120 |
121 | // Validate all edges
122 | graph.edges.forEach(edge => {
123 | this.validateEdgeProperties(edge);
124 | this.validateNodeExists(graph, edge.from);
125 | this.validateNodeExists(graph, edge.to);
126 | });
127 | }
128 | }
129 |
130 | // Export convenience functions
131 | export const {
132 | validateNodeExists,
133 | validateNodeDoesNotExist,
134 | validateEdgeUniqueness,
135 | validateNodeProperties,
136 | validateNodeNameProperty,
137 | validateNodeNamesArray,
138 | validateEdgeProperties,
139 | validateEdgeReferences,
140 | validateGraphStructure
141 | } = GraphValidator;
--------------------------------------------------------------------------------
/src/core/graph/Node.ts:
--------------------------------------------------------------------------------
1 | // src/core/graph/Node.ts
2 |
3 | import type {Metadata} from '@core/index.js';
4 |
5 | /**
6 | * Represents a node in the knowledge graph
7 | */
8 | export interface Node {
9 | type: 'node';
10 | name: string;
11 | nodeType: string;
12 | metadata: Metadata;
13 | }
--------------------------------------------------------------------------------
/src/core/graph/index.ts:
--------------------------------------------------------------------------------
1 | // src/core/graph/index.ts
2 |
3 | export type {Node} from './Node.js';
4 | export type {Edge} from './Edge.js';
5 | export type {Graph} from './Graph.js';
6 | export {GraphValidator} from './GraphValidator.js';
7 | export { EdgeWeightUtils } from './EdgeWeightUtils.js';
--------------------------------------------------------------------------------
/src/core/index.ts:
--------------------------------------------------------------------------------
1 | // src/core/index.ts
2 |
3 | export * from './graph/index.js';
4 | export * from './metadata/index.js';
5 | export * from './schema/index.js';
--------------------------------------------------------------------------------
/src/core/metadata/Metadata.ts:
--------------------------------------------------------------------------------
1 | // src/core/metadata/Metadata.ts
2 |
3 | /**
4 | * Represents metadata information associated with a node
5 | * Each string in the array represents a metadata entry in the format "key: value"
6 | */
7 | export type Metadata = string[];
8 |
9 | export interface MetadataEntry {
10 | key: string;
11 | value: string;
12 | }
13 |
14 | export interface MetadataAddition {
15 | nodeName: string;
16 | contents: string[];
17 | }
18 |
19 | export interface MetadataDeletion {
20 | nodeName: string;
21 | metadata: string[];
22 | }
23 |
24 | export interface MetadataResult {
25 | nodeName: string;
26 | addedMetadata: string[];
27 | }
--------------------------------------------------------------------------------
/src/core/metadata/MetadataProcessor.ts:
--------------------------------------------------------------------------------
1 | // src/core/metadata/MetadataProcessor.ts
2 |
3 | import {MetadataEntry, Metadata} from './Metadata.js';
4 |
5 | export class MetadataProcessor {
6 | /**
7 | * Converts a raw metadata string to a structured entry
8 | */
9 | static parseMetadataEntry(entry: string): MetadataEntry {
10 | const colonIndex = entry.indexOf(':');
11 | if (colonIndex === -1) {
12 | throw new Error(`Invalid metadata format: ${entry}`);
13 | }
14 |
15 | return {
16 | key: entry.substring(0, colonIndex).trim(),
17 | value: entry.substring(colonIndex + 1).trim()
18 | };
19 | }
20 |
21 | /**
22 | * Formats a metadata entry into a string
23 | */
24 | static formatMetadataEntry(key: string, value: string | string[] | unknown): string {
25 | if (Array.isArray(value)) {
26 | return `${key}: ${value.join(', ')}`;
27 | }
28 | return `${key}: ${String(value)}`;
29 | }
30 |
31 | /**
32 | * Processes and validates metadata entries
33 | */
34 | static validateMetadata(metadata: Metadata): boolean {
35 | try {
36 | metadata.forEach(entry => this.parseMetadataEntry(entry));
37 | return true;
38 | } catch (error) {
39 | return false;
40 | }
41 | }
42 |
43 | /**
44 | * Merges multiple metadata arrays, removing duplicates
45 | */
46 | static mergeMetadata(...metadataArrays: Metadata[]): Metadata {
47 | const uniqueEntries = new Set();
48 |
49 | metadataArrays.forEach(metadata => {
50 | metadata.forEach(entry => uniqueEntries.add(entry));
51 | });
52 |
53 | return Array.from(uniqueEntries);
54 | }
55 |
56 | /**
57 | * Filters metadata entries by key
58 | */
59 | static filterByKey(metadata: Metadata, key: string): Metadata {
60 | return metadata.filter(entry => {
61 | const parsed = this.parseMetadataEntry(entry);
62 | return parsed.key === key;
63 | });
64 | }
65 |
66 | /**
67 | * Extracts value for a specific metadata key
68 | */
69 | static getValue(metadata: Metadata, key: string): string | null {
70 | const entries = this.filterByKey(metadata, key);
71 | if (entries.length === 0) return null;
72 |
73 | return this.parseMetadataEntry(entries[0]).value;
74 | }
75 |
76 | /**
77 | * Creates a metadata entry map for efficient lookup
78 | */
79 | static createMetadataMap(metadata: Metadata): Map {
80 | const map = new Map();
81 |
82 | metadata.forEach(entry => {
83 | const {key, value} = this.parseMetadataEntry(entry);
84 | map.set(key, value);
85 | });
86 |
87 | return map;
88 | }
89 | }
--------------------------------------------------------------------------------
/src/core/metadata/index.ts:
--------------------------------------------------------------------------------
1 | // src/core/metadata/index.ts
2 |
3 | export type {
4 | Metadata,
5 | MetadataEntry,
6 | MetadataAddition,
7 | MetadataDeletion,
8 | MetadataResult
9 | } from './Metadata.js';
10 | export {MetadataProcessor} from './MetadataProcessor.js';
--------------------------------------------------------------------------------
/src/core/schema/SchemaBuilder.ts:
--------------------------------------------------------------------------------
1 | // src/schema/loader/schemaBuilder.ts
2 |
3 | /**
4 | * Schema property configuration
5 | */
6 | export interface SchemaPropertyConfig {
7 | type: string;
8 | description: string;
9 | enum?: string[];
10 | items?: Partial;
11 | properties?: Record;
12 | required?: string[];
13 | }
14 |
15 | /**
16 | * Relationship configuration
17 | */
18 | export interface RelationshipConfig {
19 | edgeType: string;
20 | nodeType?: string;
21 | description?: string;
22 | }
23 |
24 | /**
25 | * Metadata configuration
26 | */
27 | export interface MetadataConfig {
28 | requiredFields: string[];
29 | optionalFields: string[];
30 | excludeFields: string[];
31 | }
32 |
33 | /**
34 | * Schema configuration
35 | */
36 | export interface SchemaConfig {
37 | name: string;
38 | description: string;
39 | inputSchema: {
40 | type: string;
41 | properties: Record;
44 | required: string[];
45 | additionalProperties: boolean | SchemaPropertyConfig;
46 | }>;
47 | required: string[];
48 | };
49 | relationships: Record;
50 | metadataConfig: MetadataConfig;
51 | }
52 |
53 | /**
54 | * Facilitates the construction and manipulation of schemas for nodes in the knowledge graph.
55 | */
56 | export class SchemaBuilder {
57 | private schema: Partial;
58 | private relationships: Map;
59 | private metadataConfig: MetadataConfig;
60 |
61 | /**
62 | * Creates an instance of SchemaBuilder.
63 | */
64 | constructor(name: string, description: string) {
65 | this.schema = {
66 | name,
67 | description,
68 | inputSchema: {
69 | type: "object",
70 | properties: {
71 | [name.replace('add_', '')]: {
72 | type: "object",
73 | properties: {},
74 | required: [],
75 | additionalProperties: {
76 | type: "string",
77 | description: "Any additional properties"
78 | }
79 | }
80 | },
81 | required: [name.replace('add_', '')]
82 | }
83 | };
84 |
85 | this.relationships = new Map();
86 | this.metadataConfig = {
87 | requiredFields: [],
88 | optionalFields: [],
89 | excludeFields: []
90 | };
91 | }
92 |
93 | /**
94 | * Adds a string property to the schema with an optional enum.
95 | */
96 | addStringProperty(
97 | name: string,
98 | description: string,
99 | required: boolean = false,
100 | enumValues: string[] | null = null
101 | ): SchemaBuilder {
102 | const property: SchemaPropertyConfig = {
103 | type: "string",
104 | description
105 | };
106 |
107 | if (enumValues) {
108 | property.enum = enumValues;
109 | }
110 |
111 | const schemaName = this.schema.name!.replace('add_', '');
112 | if (this.schema.inputSchema?.properties[schemaName]) {
113 | this.schema.inputSchema.properties[schemaName].properties[name] = property;
114 |
115 | if (required) {
116 | this.schema.inputSchema.properties[schemaName].required.push(name);
117 | this.metadataConfig.requiredFields.push(name);
118 | } else {
119 | this.metadataConfig.optionalFields.push(name);
120 | }
121 | }
122 |
123 | return this;
124 | }
125 |
126 | /**
127 | * Adds an array property to the schema with optional enum values for items.
128 | */
129 | addArrayProperty(
130 | name: string,
131 | description: string,
132 | required: boolean = false,
133 | enumValues: string[] | null = null
134 | ): SchemaBuilder {
135 | const property: SchemaPropertyConfig = {
136 | type: "array",
137 | description,
138 | items: {
139 | type: "string",
140 | description: `Item in ${name} array`,
141 | ...(enumValues && {enum: enumValues})
142 | }
143 | };
144 |
145 | const schemaName = this.schema.name!.replace('add_', '');
146 | if (this.schema.inputSchema?.properties[schemaName]) {
147 | this.schema.inputSchema.properties[schemaName].properties[name] = property;
148 |
149 | if (required) {
150 | this.schema.inputSchema.properties[schemaName].required.push(name);
151 | this.metadataConfig.requiredFields.push(name);
152 | } else {
153 | this.metadataConfig.optionalFields.push(name);
154 | }
155 | }
156 |
157 | return this;
158 | }
159 |
160 | /**
161 | * Adds a relationship definition to the schema.
162 | */
163 | addRelationship(
164 | propertyName: string,
165 | edgeType: string,
166 | description: string,
167 | nodeType: string | null = null
168 | ): SchemaBuilder {
169 | this.relationships.set(propertyName, {
170 | edgeType,
171 | ...(nodeType && {nodeType}),
172 | description
173 | });
174 | this.metadataConfig.excludeFields.push(propertyName);
175 | return this.addArrayProperty(propertyName, description);
176 | }
177 |
178 | /**
179 | * Sets whether additional properties are allowed in the schema.
180 | */
181 | allowAdditionalProperties(allowed: boolean): SchemaBuilder {
182 | const schemaName = this.schema.name!.replace('add_', '');
183 | if (this.schema.inputSchema?.properties[schemaName]) {
184 | if (allowed) {
185 | this.schema.inputSchema.properties[schemaName].additionalProperties = {
186 | type: "string",
187 | description: "Additional property value"
188 | };
189 | } else {
190 | this.schema.inputSchema.properties[schemaName].additionalProperties = false;
191 | }
192 | }
193 | return this;
194 | }
195 |
196 | /**
197 | * Creates an update schema based on the current schema.
198 | */
199 | createUpdateSchema(excludeFields: Set = new Set()): SchemaConfig {
200 | const schemaName = this.schema.name!.replace('add_', 'update_');
201 | const updateSchemaBuilder = new SchemaBuilder(
202 | schemaName,
203 | `Update an existing ${schemaName.replace('update_', '')} in the knowledge graph`
204 | );
205 |
206 | const baseProperties = this.schema.inputSchema!.properties[this.schema.name!.replace('add_', '')].properties;
207 |
208 | // Copy properties except excluded ones
209 | Object.entries(baseProperties).forEach(([propName, propValue]) => {
210 | if (!excludeFields.has(propName)) {
211 | if (propValue.type === 'array') {
212 | updateSchemaBuilder.addArrayProperty(
213 | propName,
214 | propValue.description,
215 | false,
216 | propValue.items?.enum
217 | );
218 | } else {
219 | updateSchemaBuilder.addStringProperty(
220 | propName,
221 | propValue.description,
222 | false,
223 | propValue.enum
224 | );
225 | }
226 | }
227 | });
228 |
229 | // Copy relationships
230 | this.relationships.forEach((config, propName) => {
231 | if (!excludeFields.has(propName)) {
232 | updateSchemaBuilder.addRelationship(
233 | propName,
234 | config.edgeType,
235 | config.description || 'Relationship property',
236 | config.nodeType || null
237 | );
238 | }
239 | });
240 |
241 | // Add metadata array
242 | updateSchemaBuilder.addArrayProperty(
243 | 'metadata',
244 | 'An array of metadata contents to replace the existing metadata'
245 | );
246 |
247 | return updateSchemaBuilder.build();
248 | }
249 |
250 | /**
251 | * Builds and returns the final schema object.
252 | */
253 | build(): SchemaConfig {
254 | return {
255 | ...this.schema as SchemaConfig,
256 | relationships: Object.fromEntries(this.relationships),
257 | metadataConfig: this.metadataConfig
258 | };
259 | }
260 | }
--------------------------------------------------------------------------------
/src/core/schema/SchemaLoader.ts:
--------------------------------------------------------------------------------
1 | // src/schema/loader/schemaLoader.ts
2 |
3 | import {promises as fs} from 'fs';
4 | import {SchemaBuilder} from './SchemaBuilder.js';
5 | import path from 'path';
6 | import {CONFIG} from '@config/index.js';
7 |
8 | interface RawSchemaProperty {
9 | type: string;
10 | description: string;
11 | required?: boolean;
12 | enum?: string[];
13 | relationship?: {
14 | edgeType: string;
15 | description: string;
16 | nodeType?: string;
17 | };
18 | }
19 |
20 | interface RawSchema {
21 | name: string;
22 | description: string;
23 | properties: Record;
24 | additionalProperties?: boolean;
25 | }
26 |
27 | /**
28 | * Responsible for loading and converting schema definitions from JSON files into SchemaBuilder instances.
29 | */
30 | export class SchemaLoader {
31 | /**
32 | * Loads a specific schema by name.
33 | */
34 | static async loadSchema(schemaName: string): Promise {
35 | const SCHEMAS_DIR = CONFIG.PATHS.SCHEMAS_DIR;
36 | const schemaPath = path.join(SCHEMAS_DIR, `${schemaName}.schema.json`);
37 |
38 | try {
39 | const schemaContent = await fs.readFile(schemaPath, 'utf-8');
40 | const schema = JSON.parse(schemaContent) as RawSchema;
41 | this.validateSchema(schema);
42 | return this.convertToSchemaBuilder(schema);
43 | } catch (error) {
44 | if (error instanceof Error) {
45 | throw new Error(`Failed to load schema ${schemaName}: ${error.message}`);
46 | }
47 | throw new Error(`Failed to load schema ${schemaName}`);
48 | }
49 | }
50 |
51 | /**
52 | * Converts a JSON schema object into a SchemaBuilder instance.
53 | */
54 | static convertToSchemaBuilder(schema: RawSchema): SchemaBuilder {
55 | const builder = new SchemaBuilder(schema.name, schema.description);
56 |
57 | Object.entries(schema.properties).forEach(([propName, propConfig]) => {
58 | if (propConfig.type === 'array') {
59 | builder.addArrayProperty(
60 | propName,
61 | propConfig.description,
62 | propConfig.required,
63 | propConfig.enum
64 | );
65 | } else {
66 | builder.addStringProperty(
67 | propName,
68 | propConfig.description,
69 | propConfig.required,
70 | propConfig.enum
71 | );
72 | }
73 |
74 | // Add relationship if defined
75 | if (propConfig.relationship) {
76 | builder.addRelationship(
77 | propName,
78 | propConfig.relationship.edgeType,
79 | propConfig.relationship.description,
80 | propConfig.relationship.nodeType
81 | );
82 | }
83 | });
84 |
85 | if (schema.additionalProperties !== undefined) {
86 | builder.allowAdditionalProperties(schema.additionalProperties);
87 | }
88 |
89 | return builder;
90 | }
91 |
92 | /**
93 | * Loads all schema files from the schemas directory.
94 | */
95 | static async loadAllSchemas(): Promise> {
96 | const SCHEMAS_DIR = CONFIG.PATHS.SCHEMAS_DIR;
97 |
98 | try {
99 | const files = await fs.readdir(SCHEMAS_DIR);
100 | const schemaFiles = files.filter(file => file.endsWith('.schema.json'));
101 |
102 | const schemas: Record = {};
103 | for (const file of schemaFiles) {
104 | const schemaName = path.basename(file, '.schema.json');
105 | schemas[schemaName] = await this.loadSchema(schemaName);
106 | }
107 |
108 | return schemas;
109 | } catch (error) {
110 | if (error instanceof Error) {
111 | throw new Error(`Failed to load schemas: ${error.message}`);
112 | }
113 | throw new Error('Failed to load schemas');
114 | }
115 | }
116 |
117 | /**
118 | * Validates a schema definition.
119 | * @throws {Error} If the schema is invalid
120 | */
121 | private static validateSchema(schema: RawSchema): void {
122 | if (!schema.name || !schema.description || !schema.properties) {
123 | throw new Error('Schema must have name, description, and properties');
124 | }
125 |
126 | Object.entries(schema.properties).forEach(([propName, propConfig]) => {
127 | if (!propConfig.type || !propConfig.description) {
128 | throw new Error(`Property ${propName} must have type and description`);
129 | }
130 |
131 | if (propConfig.relationship && !propConfig.relationship.edgeType) {
132 | throw new Error(`Relationship property ${propName} must have edgeType`);
133 | }
134 | });
135 | }
136 | }
--------------------------------------------------------------------------------
/src/core/schema/SchemaProcessor.ts:
--------------------------------------------------------------------------------
1 | // src/schema/loader/schemaProcessor.ts
2 |
3 | import {formatToolResponse, formatToolError, ToolResponse} from '@shared/index.js';
4 | import type {Node, Edge, Graph, SchemaConfig} from '@core/index.js';
5 | import type {ApplicationManager} from '@application/index.js';
6 |
7 | interface NodeData {
8 | name: string;
9 |
10 | [key: string]: any;
11 | }
12 |
13 | export interface ProcessedNodeResult {
14 | nodes: Node[];
15 | edges: Edge[];
16 | }
17 |
18 | export interface SchemaUpdateResult {
19 | metadata: string[];
20 | edgeChanges: {
21 | remove: Edge[];
22 | add: Edge[];
23 | };
24 | }
25 |
26 | /**
27 | * Formats a field value into a metadata string.
28 | */
29 | function formatMetadataEntry(field: string, value: string | string[] | unknown): string {
30 | if (Array.isArray(value)) {
31 | return `${field}: ${value.join(', ')}`;
32 | }
33 | return `${field}: ${String(value)}`;
34 | }
35 |
36 | /**
37 | * Creates a node based on schema definition and input data.
38 | */
39 | export async function createSchemaNode(
40 | data: NodeData,
41 | schema: SchemaConfig,
42 | nodeType: string
43 | ): Promise {
44 | try {
45 | const {metadataConfig, relationships} = schema;
46 | const metadata: string[] = [];
47 | const nodes: Node[] = [];
48 | const edges: Edge[] = [];
49 |
50 | // Create excluded fields set
51 | const excludedFields = new Set([
52 | 'name',
53 | ...metadataConfig.requiredFields,
54 | ...metadataConfig.optionalFields,
55 | ...(metadataConfig.excludeFields || []),
56 | ]);
57 |
58 | if (relationships) {
59 | Object.keys(relationships).forEach(field => excludedFields.add(field));
60 | }
61 |
62 | // Process required fields
63 | for (const field of metadataConfig.requiredFields) {
64 | if (data[field] === undefined) {
65 | throw new Error(`Required field "${field}" is missing`);
66 | }
67 | if (!relationships || !relationships[field]) {
68 | metadata.push(formatMetadataEntry(field, data[field]));
69 | }
70 | }
71 |
72 | // Process optional fields
73 | for (const field of metadataConfig.optionalFields) {
74 | if (data[field] !== undefined && (!relationships || !relationships[field])) {
75 | metadata.push(formatMetadataEntry(field, data[field]));
76 | }
77 | }
78 |
79 | // Process relationships
80 | if (relationships) {
81 | for (const [field, config] of Object.entries(relationships)) {
82 | if (data[field]) {
83 | const value = data[field];
84 | if (Array.isArray(value)) {
85 | for (const target of value) {
86 | edges.push({
87 | type: 'edge',
88 | from: data.name,
89 | to: target,
90 | edgeType: config.edgeType
91 | });
92 | }
93 | } else {
94 | edges.push({
95 | type: 'edge',
96 | from: data.name,
97 | to: value as string,
98 | edgeType: config.edgeType
99 | });
100 | }
101 | metadata.push(formatMetadataEntry(field, value));
102 | }
103 | }
104 | }
105 |
106 | // Process additional fields
107 | for (const [key, value] of Object.entries(data)) {
108 | if (!excludedFields.has(key) && value !== undefined) {
109 | metadata.push(formatMetadataEntry(key, value));
110 | }
111 | }
112 |
113 | // Create the main node
114 | const node: Node = {
115 | type: 'node',
116 | name: data.name,
117 | nodeType,
118 | metadata
119 | };
120 | nodes.push(node);
121 |
122 | return {nodes, edges};
123 | } catch (error) {
124 | throw error;
125 | }
126 | }
127 |
128 | export async function updateSchemaNode(
129 | updates: NodeData,
130 | currentNode: Node,
131 | schema: SchemaConfig,
132 | currentGraph: Graph
133 | ): Promise {
134 | const {metadataConfig, relationships} = schema;
135 | const metadata = new Map();
136 | const edgeChanges = {
137 | remove: [] as Edge[],
138 | add: [] as Edge[]
139 | };
140 |
141 | // Create a set of all schema-defined fields
142 | const schemaFields = new Set([
143 | ...metadataConfig.requiredFields,
144 | ...metadataConfig.optionalFields,
145 | ...(metadataConfig.excludeFields || []),
146 | 'name',
147 | 'metadata'
148 | ]);
149 |
150 | // Add relationship fields to schema fields
151 | if (relationships) {
152 | Object.keys(relationships).forEach(field => schemaFields.add(field));
153 | }
154 |
155 | // Process existing metadata into the Map
156 | currentNode.metadata.forEach(meta => {
157 | const colonIndex = meta.indexOf(':');
158 | if (colonIndex !== -1) {
159 | const key = meta.substring(0, colonIndex).trim().toLowerCase();
160 | const value = meta.substring(colonIndex + 1).trim();
161 | metadata.set(key, value);
162 | }
163 | });
164 |
165 | const updateMetadataEntry = (key: string, value: unknown) => {
166 | const formattedValue = Array.isArray(value) ? value.join(', ') : String(value);
167 | metadata.set(key.toLowerCase(), formattedValue);
168 | };
169 |
170 | // Process standard metadata fields
171 | const allSchemaFields = [...metadataConfig.requiredFields, ...metadataConfig.optionalFields];
172 | for (const field of allSchemaFields) {
173 | if (updates[field] !== undefined && (!relationships || !relationships[field])) {
174 | updateMetadataEntry(field, updates[field]);
175 | }
176 | }
177 |
178 | // Process relationships if they exist in the schema
179 | if (relationships) {
180 | for (const [field, config] of Object.entries(relationships)) {
181 | // Only process relationship if it's being updated
182 | if (updates[field] !== undefined) {
183 | // Get all existing edges for this relationship type from this node
184 | const existingEdges = currentGraph.edges.filter(edge =>
185 | edge.from === currentNode.name &&
186 | edge.edgeType === config.edgeType
187 | );
188 |
189 | // Only mark edges for removal if they're part of this relationship type
190 | edgeChanges.remove.push(...existingEdges);
191 |
192 | // Add new edges
193 | const value = updates[field];
194 | if (Array.isArray(value)) {
195 | value.forEach((target: string) => {
196 | edgeChanges.add.push({
197 | type: 'edge',
198 | from: currentNode.name,
199 | to: target,
200 | edgeType: config.edgeType
201 | });
202 | });
203 | } else if (value) {
204 | edgeChanges.add.push({
205 | type: 'edge',
206 | from: currentNode.name,
207 | to: value as string,
208 | edgeType: config.edgeType
209 | });
210 | }
211 |
212 | updateMetadataEntry(field, value);
213 | }
214 | }
215 | }
216 |
217 | // Process additional fields not defined in schema
218 | for (const [key, value] of Object.entries(updates)) {
219 | if (!schemaFields.has(key) && value !== undefined) {
220 | updateMetadataEntry(key, value);
221 | }
222 | }
223 |
224 | const updatedMetadata = Array.from(metadata).map(([key, value]) => {
225 | const capitalizedKey = key.charAt(0).toUpperCase() + key.slice(1);
226 | return `${capitalizedKey}: ${value}`;
227 | });
228 |
229 | return {
230 | metadata: updatedMetadata,
231 | edgeChanges
232 | };
233 | }
234 |
235 | /**
236 | * Handles the complete update process for a schema-based entity.
237 | */
238 | export async function handleSchemaUpdate(
239 | updates: NodeData,
240 | schema: SchemaConfig,
241 | nodeType: string,
242 | applicationManager: ApplicationManager
243 | ): Promise {
244 | try {
245 | // Start a transaction to ensure atomic updates
246 | await applicationManager.beginTransaction();
247 |
248 | // Get the complete current state
249 | const fullGraph = await applicationManager.readGraph();
250 | const node = fullGraph.nodes.find((n: Node) => n.nodeType === nodeType && n.name === updates.name);
251 |
252 | if (!node) {
253 | await applicationManager.rollback();
254 | return formatToolError({
255 | operation: 'updateSchema',
256 | error: `${nodeType} "${updates.name}" not found`,
257 | context: {updates, nodeType},
258 | suggestions: ["Verify the node exists", "Check node type matches"]
259 | });
260 | }
261 |
262 | try {
263 | // Process updates
264 | const {metadata, edgeChanges} = await updateSchemaNode(
265 | updates,
266 | node,
267 | schema,
268 | fullGraph
269 | );
270 |
271 | // Update the node first
272 | const updatedNode: Node = {
273 | ...node,
274 | metadata
275 | };
276 | await applicationManager.updateNodes([updatedNode]);
277 |
278 | // Then handle edges if there are any changes
279 | if (edgeChanges.remove.length > 0) {
280 | await applicationManager.deleteEdges(edgeChanges.remove);
281 | }
282 |
283 | if (edgeChanges.add.length > 0) {
284 | await applicationManager.addEdges(edgeChanges.add);
285 | }
286 |
287 | // If everything succeeded, commit the transaction
288 | await applicationManager.commit();
289 |
290 | return formatToolResponse({
291 | data: {
292 | updatedNode,
293 | edgeChanges
294 | },
295 | actionTaken: `Updated ${nodeType}: ${updatedNode.name}`
296 | });
297 |
298 | } catch (error) {
299 | // If anything fails, rollback all changes
300 | await applicationManager.rollback();
301 | throw error;
302 | }
303 |
304 | } catch (error) {
305 | if (applicationManager.isInTransaction()) {
306 | await applicationManager.rollback();
307 | }
308 |
309 | return formatToolError({
310 | operation: 'updateSchema',
311 | error: error instanceof Error ? error.message : 'Unknown error occurred',
312 | context: {updates, schema, nodeType},
313 | suggestions: [
314 | "Check all required fields are provided",
315 | "Verify relationship targets exist"
316 | ],
317 | recoverySteps: [
318 | "Review schema requirements",
319 | "Ensure node exists before updating"
320 | ]
321 | });
322 | }
323 | }
324 |
325 | export async function handleSchemaDelete(
326 | nodeName: string,
327 | nodeType: string,
328 | applicationManager: ApplicationManager
329 | ): Promise {
330 | try {
331 | const graph = await applicationManager.readGraph();
332 | const node = graph.nodes.find((n: Node) => n.name === nodeName && n.nodeType === nodeType);
333 |
334 | if (!node) {
335 | return formatToolError({
336 | operation: 'deleteSchema',
337 | error: `${nodeType} "${nodeName}" not found`,
338 | context: {nodeName, nodeType},
339 | suggestions: ["Verify node name and type"]
340 | });
341 | }
342 |
343 | await applicationManager.deleteNodes([nodeName]);
344 |
345 | return formatToolResponse({
346 | actionTaken: `Deleted ${nodeType}: ${nodeName}`
347 | });
348 | } catch (error) {
349 | return formatToolError({
350 | operation: 'deleteSchema',
351 | error: error instanceof Error ? error.message : 'Unknown error occurred',
352 | context: {nodeName, nodeType},
353 | suggestions: [
354 | "Check node exists",
355 | "Verify delete permissions"
356 | ],
357 | recoverySteps: [
358 | "Ensure no dependent nodes exist",
359 | "Try retrieving node first"
360 | ]
361 | });
362 | }
363 | }
--------------------------------------------------------------------------------
/src/core/schema/index.ts:
--------------------------------------------------------------------------------
1 | // src/core/schema/index.ts
2 |
3 | export { SchemaBuilder } from './SchemaBuilder.js';
4 | export { SchemaLoader } from './SchemaLoader.js';
5 | export {
6 | createSchemaNode,
7 | updateSchemaNode,
8 | handleSchemaUpdate,
9 | handleSchemaDelete
10 | } from './SchemaProcessor.js';
11 |
12 | export type {
13 | SchemaPropertyConfig,
14 | RelationshipConfig,
15 | MetadataConfig,
16 | SchemaConfig
17 | } from './SchemaBuilder.js';
18 |
19 | export type {
20 | ProcessedNodeResult,
21 | SchemaUpdateResult
22 | } from './SchemaProcessor.js';
--------------------------------------------------------------------------------
/src/data/memory.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CheMiguel23/MemoryMesh/db5a103b261e1fc6dd66e7ef39e4abd37ea1d510/src/data/memory.json
--------------------------------------------------------------------------------
/src/data/schemas/artifact.schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "add_artifact",
3 | "description": "Add a new artifact or unique item to the knowledge graph",
4 | "properties": {
5 | "name": {
6 | "type": "string",
7 | "description": "The artifact's name",
8 | "required": true
9 | },
10 | "description": {
11 | "type": "string",
12 | "description": "A detailed description of the artifact",
13 | "required": true
14 | },
15 | "type": {
16 | "type": "string",
17 | "description": "The artifact's type",
18 | "required": true
19 | },
20 | "rarity": {
21 | "type": "string",
22 | "description": "The rarity of the artifact",
23 | "required": true
24 | },
25 | "effects": {
26 | "type": "array",
27 | "description": "The artifact's effects or abilities",
28 | "required": true
29 | },
30 | "origin": {
31 | "type": "string",
32 | "description": "The artifact's origin or history",
33 | "required": false
34 | },
35 | "value": {
36 | "type": "string",
37 | "description": "The monetary or intrinsic value of the artifact",
38 | "required": false
39 | },
40 | "relatedCharacters": {
41 | "type": "array",
42 | "description": "Characters associated with the artifact",
43 | "required": false,
44 | "relationship": {
45 | "edgeType": "owned_by",
46 | "description": "Artifact owners"
47 | }
48 | },
49 | "relatedQuests": {
50 | "type": "array",
51 | "description": "Quests involving the artifact",
52 | "required": false,
53 | "relationship": {
54 | "edgeType": "associated_with",
55 | "description": "Artifact-related quests"
56 | }
57 | },
58 | "relatedLocations": {
59 | "type": "array",
60 | "description": "Locations associated with the artifact",
61 | "required": false,
62 | "relationship": {
63 | "edgeType": "found_at",
64 | "description": "Artifact locations"
65 | }
66 | }
67 | },
68 | "additionalProperties": true
69 | }
--------------------------------------------------------------------------------
/src/data/schemas/currency.schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "add_currency",
3 | "description": "Represents a type of currency in the game world.",
4 | "properties": {
5 | "name": {
6 | "type": "string",
7 | "description": "The name of the currency.",
8 | "required": true
9 | },
10 | "description": {
11 | "type": "string",
12 | "description": "A brief description of the currency.",
13 | "required": false
14 | },
15 | "owner": {
16 | "type": "string",
17 | "description": "The entity or character that owns this currency.",
18 | "required": true,
19 | "relationship": {
20 | "edgeType": "owned_by",
21 | "description": "The relationship between the currency and its owner."
22 | }
23 | },
24 | "quantity": {
25 | "type": "string",
26 | "description": "The amount of this currency owned.",
27 | "required": true
28 | }
29 | },
30 | "additionalProperties": true
31 | }
--------------------------------------------------------------------------------
/src/data/schemas/faction.schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "add_faction",
3 | "description": "A faction or organization operating within the game world.",
4 | "properties": {
5 | "name": {
6 | "type": "string",
7 | "description": "The name of the faction or organization.",
8 | "required": true
9 | },
10 | "type": {
11 | "type": "string",
12 | "description": "The type of the faction.",
13 | "required": true
14 | },
15 | "description": {
16 | "type": "string",
17 | "description": "A detailed description of the faction.",
18 | "required": true
19 | },
20 | "goals": {
21 | "type": "array",
22 | "description": "The main objectives or goals of the faction.",
23 | "required": false
24 | },
25 | "leader": {
26 | "type": "string",
27 | "description": "The leader of the faction.",
28 | "required": false,
29 | "relationship": {
30 | "edgeType": "led_by",
31 | "description": "The entity leading this faction."
32 | }
33 | }
34 | },
35 | "additionalProperties": true
36 | }
--------------------------------------------------------------------------------
/src/data/schemas/inventory.schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "add_inventory",
3 | "description": "A collection of items or equipment belonging to a character, entity, or location.",
4 | "properties": {
5 | "name": {
6 | "type": "string",
7 | "description": "[Entity]_inventory.",
8 | "required": true
9 | },
10 | "owner": {
11 | "type": "string",
12 | "description": "The owner of this inventory.",
13 | "required": true,
14 | "relationship": {
15 | "edgeType": "owned_by",
16 | "description": "The entity that owns this inventory."
17 | }
18 | },
19 | "items": {
20 | "type": "array",
21 | "description": "List of items in the inventory.",
22 | "required": true
23 | }
24 | },
25 | "additionalProperties": true
26 | }
--------------------------------------------------------------------------------
/src/data/schemas/location.schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "add_location",
3 | "description": "Add a new location to the knowledge graph",
4 | "properties": {
5 | "name": {
6 | "type": "string",
7 | "description": "Location's name",
8 | "required": true
9 | },
10 | "type": {
11 | "type": "string",
12 | "description": "Type of location",
13 | "required": true
14 | },
15 | "description": {
16 | "type": "string",
17 | "description": "Detailed description of the location",
18 | "required": true
19 | },
20 | "status": {
21 | "type": "string",
22 | "description": "Current state of the location",
23 | "required": true
24 | },
25 | "parentLocation": {
26 | "type": "string",
27 | "description": "The parent location this location belongs to",
28 | "required": false
29 | },
30 | "atmosphere": {
31 | "type": "string",
32 | "description": "The general feel/mood of the location",
33 | "required": false
34 | },
35 | "accessibility": {
36 | "type": "string",
37 | "description": "How characters can enter/exit the location",
38 | "required": false
39 | },
40 | "size": {
41 | "type": "string",
42 | "description": "The size or scale of the location",
43 | "required": false
44 | },
45 | "dangerLevel": {
46 | "type": "string",
47 | "description": "The level of danger present in the location",
48 | "required": false
49 | },
50 | "notableFeatures": {
51 | "type": "array",
52 | "description": "Distinct characteristics of the location",
53 | "required": false
54 | },
55 | "subLocations": {
56 | "type": "array",
57 | "description": "Locations contained within this location",
58 | "required": false,
59 | "relationship": {
60 | "edgeType": "contains",
61 | "description": "Locations contained within this location"
62 | }
63 | },
64 | "relatedCharacters": {
65 | "type": "array",
66 | "description": "Characters present in or associated with this location",
67 | "required": false,
68 | "relationship": {
69 | "edgeType": "present_in",
70 | "description": "Characters associated with the location"
71 | }
72 | },
73 | "relatedQuests": {
74 | "type": "array",
75 | "description": "Quests associated with this location",
76 | "required": false,
77 | "relationship": {
78 | "edgeType": "takes_place_in",
79 | "description": "Quests associated with the location"
80 | }
81 | },
82 | "relatedArtifacts": {
83 | "type": "array",
84 | "description": "Artifacts found or stored at this location",
85 | "required": false,
86 | "relationship": {
87 | "edgeType": "located_at",
88 | "description": "Artifacts associated with the location"
89 | }
90 | }
91 | },
92 | "additionalProperties": true
93 | }
--------------------------------------------------------------------------------
/src/data/schemas/npc.schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "add_npc",
3 | "description": "Add a new Non-Player Character (NPC) to the knowledge graph",
4 | "properties": {
5 | "name": {
6 | "type": "string",
7 | "description": "NPC's name",
8 | "required": true
9 | },
10 | "role": {
11 | "type": "string",
12 | "description": "NPC's role or occupation",
13 | "required": true
14 | },
15 | "status": {
16 | "type": "string",
17 | "description": "NPC's current status",
18 | "required": true
19 | },
20 | "currentLocation": {
21 | "type": "string",
22 | "description": "The current location of the NPC",
23 | "required": true,
24 | "relationship": {
25 | "edgeType": "located_in",
26 | "description": "The current location of the NPC"
27 | }
28 | },
29 | "description": {
30 | "type": "string",
31 | "description": "A detailed description of the NPC",
32 | "required": true
33 | },
34 | "gender": {
35 | "type": "string",
36 | "description": "NPC's gender",
37 | "required": false
38 | },
39 | "race": {
40 | "type": "string",
41 | "description": "NPC's race",
42 | "required": false
43 | },
44 | "background": {
45 | "type": "string",
46 | "description": "The background story of the NPC",
47 | "required": false
48 | },
49 | "secret": {
50 | "type": "string",
51 | "description": "A hidden detail about the NPC",
52 | "required": false
53 | },
54 | "origin": {
55 | "type": "string",
56 | "description": "The origin or home location of the NPC",
57 | "required": false,
58 | "relationship": {
59 | "edgeType": "originates_from",
60 | "description": "The origin location of the NPC"
61 | }
62 | },
63 | "traits": {
64 | "type": "array",
65 | "description": "Unique traits or characteristics of the NPC",
66 | "required": false
67 | },
68 | "abilities": {
69 | "type": "array",
70 | "description": "Specific skills or powers the NPC possesses",
71 | "required": false
72 | },
73 | "importance": {
74 | "type": "string",
75 | "description": "The importance of the NPC in the story or world",
76 | "required": false
77 | },
78 | "reputation": {
79 | "type": "string",
80 | "description": "How the NPC is perceived by others",
81 | "required": false
82 | },
83 | "money": {
84 | "type": "string",
85 | "description": "Currency or wealth the NPC holds",
86 | "required": false
87 | },
88 | "alignment": {
89 | "type": "string",
90 | "description": "The ethical or moral alignment of the NPC",
91 | "required": false
92 | },
93 | "motivation": {
94 | "type": "string",
95 | "description": "The driving purpose or goals of the NPC",
96 | "required": false
97 | }
98 | },
99 | "additionalProperties": true
100 | }
--------------------------------------------------------------------------------
/src/data/schemas/player_character.schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "add_player_character",
3 | "description": "Add a new Player Character to the knowledge graph",
4 | "properties": {
5 | "name": {
6 | "type": "string",
7 | "description": "Player character's name",
8 | "required": true
9 | },
10 | "age": {
11 | "type": "string",
12 | "description": "Player character's age",
13 | "required": true
14 | },
15 | "gender": {
16 | "type": "string",
17 | "description": "Player character's gender",
18 | "required": true
19 | },
20 | "occupation": {
21 | "type": "string",
22 | "description": "Player character's occupation",
23 | "required": true
24 | },
25 | "status": {
26 | "type": "string",
27 | "description": "Player character's current status",
28 | "required": true
29 | },
30 | "race": {
31 | "type": "string",
32 | "description": "Player character's race",
33 | "required": false
34 | },
35 | "description": {
36 | "type": "string",
37 | "description": "A detailed description of the player character",
38 | "required": false
39 | },
40 | "background": {
41 | "type": "string",
42 | "description": "The background story of the player character",
43 | "required": false
44 | },
45 | "equipment": {
46 | "type": "array",
47 | "description": "List of equipment items associated with the player character",
48 | "required": false
49 | }
50 | },
51 | "additionalProperties": true
52 | }
--------------------------------------------------------------------------------
/src/data/schemas/quest.schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "add_quest",
3 | "description": "Add a new Quest to the knowledge graph",
4 | "properties": {
5 | "name": {
6 | "type": "string",
7 | "description": "Quest's name",
8 | "required": true
9 | },
10 | "description": {
11 | "type": "string",
12 | "description": "Detailed description of the quest",
13 | "required": true
14 | },
15 | "status": {
16 | "type": "string",
17 | "description": "Current status of the quest",
18 | "required": true,
19 | "enum": [
20 | "Active",
21 | "Completed",
22 | "Failed"
23 | ]
24 | },
25 | "objectives": {
26 | "type": "array",
27 | "description": "List of objectives to complete the quest",
28 | "required": true,
29 | "items": {
30 | "type": "string"
31 | }
32 | },
33 | "rewards": {
34 | "type": "array",
35 | "description": "List of rewards for completing the quest",
36 | "required": true,
37 | "items": {
38 | "type": "string"
39 | }
40 | },
41 | "relatedCharacters": {
42 | "type": "array",
43 | "description": "List of player characters assigned to the quest",
44 | "required": false,
45 | "items": {
46 | "type": "string"
47 | }
48 | },
49 | "relatedNPCs": {
50 | "type": "array",
51 | "description": "List of NPCs involved in the quest",
52 | "required": false,
53 | "items": {
54 | "type": "string"
55 | }
56 | },
57 | "relatedLocations": {
58 | "type": "array",
59 | "description": "List of locations associated with the quest",
60 | "required": false,
61 | "items": {
62 | "type": "string"
63 | }
64 | }
65 | },
66 | "additionalProperties": true
67 | }
68 |
--------------------------------------------------------------------------------
/src/data/schemas/skills.schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "add_skills",
3 | "description": "Defines list of skills or abilities a character can possess.",
4 | "properties": {
5 | "name": {
6 | "type": "string",
7 | "description": "[Entity]_abilities",
8 | "required": true
9 | },
10 | "owner": {
11 | "type": "string",
12 | "description": "The entity or character that owns these skills.",
13 | "required": true,
14 | "relationship": {
15 | "edgeType": "possesses",
16 | "description": "The relationship between the skill and its owner."
17 | }
18 | }
19 | },
20 | "additionalProperties": true
21 | }
22 |
--------------------------------------------------------------------------------
/src/data/schemas/temporal.schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "add_temporal",
3 | "description": "Represents a specific point in time and its associated environmental conditions.",
4 | "properties": {
5 | "time": {
6 | "type": "string",
7 | "description": "The specific time.",
8 | "required": false
9 | },
10 | "day": {
11 | "type": "string",
12 | "description": "The current day.",
13 | "required": false
14 | },
15 | "year": {
16 | "type": "string",
17 | "description": "The current year or point in the timeline.",
18 | "required": false
19 | },
20 | "weather": {
21 | "type": "string",
22 | "description": "The current weather or environmental conditions.",
23 | "required": false
24 | }
25 | },
26 | "additionalProperties": true
27 | }
28 |
--------------------------------------------------------------------------------
/src/data/schemas/transportation.schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "add_transportation",
3 | "description": "Represents a transportation owned or used by a character or entity.",
4 | "properties": {
5 | "name": {
6 | "type": "string",
7 | "description": "The name of the vehicle.",
8 | "required": true
9 | },
10 | "description": {
11 | "type": "string",
12 | "description": "A brief description of the vehicle.",
13 | "required": true
14 | },
15 | "owner": {
16 | "type": "string",
17 | "description": "The entity or character that owns this vehicle.",
18 | "required": true,
19 | "relationship": {
20 | "edgeType": "owned_by",
21 | "description": "The relationship between the vehicle and its owner."
22 | }
23 | },
24 | "type": {
25 | "type": "string",
26 | "description": "The type or class of the vehicle (e.g., car, spaceship, boat, horse).",
27 | "required": true
28 | }
29 | },
30 | "additionalProperties": true
31 | }
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import {Server} from "@modelcontextprotocol/sdk/server/index.js";
3 | import {StdioServerTransport} from "@modelcontextprotocol/sdk/server/stdio.js";
4 | import {
5 | CallToolRequestSchema,
6 | ListToolsRequestSchema,
7 | } from "@modelcontextprotocol/sdk/types.js";
8 | import {ApplicationManager} from '@application/managers/ApplicationManager.js';
9 | import {handleCallToolRequest} from '@integration/tools/callToolHandler.js';
10 | import {toolsRegistry} from '@integration/tools/registry/toolsRegistry.js';
11 | import {CONFIG} from './config/config.js';
12 | import {formatToolError} from "@shared/utils/responseFormatter.js";
13 |
14 | const knowledgeGraphManager = new ApplicationManager();
15 |
16 | const server = new Server({
17 | name: CONFIG.SERVER.NAME,
18 | version: CONFIG.SERVER.VERSION,
19 | }, {
20 | capabilities: {
21 | tools: {},
22 | },
23 | });
24 |
25 | async function main(): Promise {
26 | try {
27 | await toolsRegistry.initialize(knowledgeGraphManager);
28 |
29 | server.setRequestHandler(ListToolsRequestSchema, async () => {
30 | return {
31 | tools: toolsRegistry.getAllTools().map(tool => ({
32 | name: tool.name,
33 | description: tool.description,
34 | inputSchema: tool.inputSchema
35 | }))
36 | };
37 | });
38 |
39 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
40 | try {
41 | if (!request.params.arguments) {
42 | throw new Error("Tool arguments are required");
43 | }
44 |
45 | const toolRequest = {
46 | params: {
47 | name: request.params.name,
48 | arguments: request.params.arguments
49 | }
50 | };
51 |
52 | const result = await handleCallToolRequest(toolRequest, knowledgeGraphManager);
53 |
54 | return {
55 | toolResult: result.toolResult
56 | };
57 | } catch (error) {
58 | console.error("Error in handleCallToolRequest:", error);
59 | const formattedError = formatToolError({
60 | operation: "callTool",
61 | error: error instanceof Error ? error.message : 'Unknown error occurred',
62 | context: {request},
63 | suggestions: ["Examine the tool input parameters for correctness.", "Verify that the requested operation is supported."],
64 | recoverySteps: ["Adjust the input parameters based on the schema definition."]
65 | });
66 | return {
67 | toolResult: formattedError.toolResult
68 | };
69 | }
70 | });
71 |
72 | server.onerror = (error: Error) => {
73 | console.error("[MCP Server Error]", error);
74 | };
75 |
76 | process.on('SIGINT', async () => {
77 | await server.close();
78 | process.exit(0);
79 | });
80 |
81 | const transport = new StdioServerTransport();
82 | await server.connect(transport);
83 | console.error("Knowledge Graph MCP Server running on stdio");
84 | } catch (error) {
85 | console.error("Fatal error during server startup:", error);
86 | process.exit(1);
87 | }
88 | }
89 |
90 | main().catch((error) => {
91 | console.error("Fatal error in main():", error);
92 | process.exit(1);
93 | });
--------------------------------------------------------------------------------
/src/infrastructure/events/EventEmitter.ts:
--------------------------------------------------------------------------------
1 | // src/core/events/EventEmitter.ts
2 |
3 | import type {EventListener} from './EventTypes.ts';
4 |
5 | /**
6 | * A simple event emitter implementation for managing event listeners and emitting events.
7 | */
8 | export class EventEmitter {
9 | /**
10 | * Maps event names to their respective sets of listener functions.
11 | */
12 | private eventListeners: Map>;
13 |
14 | constructor() {
15 | this.eventListeners = new Map();
16 |
17 | // Bind methods to ensure correct 'this' context
18 | this.on = this.on.bind(this);
19 | this.off = this.off.bind(this);
20 | this.once = this.once.bind(this);
21 | this.emit = this.emit.bind(this);
22 | this.removeAllListeners = this.removeAllListeners.bind(this);
23 | }
24 |
25 | /**
26 | * Adds an event listener for the specified event.
27 | */
28 | public on(eventName: string, listener: EventListener): () => void {
29 | if (typeof listener !== 'function') {
30 | throw new TypeError('Listener must be a function');
31 | }
32 |
33 | if (!this.eventListeners.has(eventName)) {
34 | this.eventListeners.set(eventName, new Set());
35 | }
36 |
37 | const listeners = this.eventListeners.get(eventName);
38 | if (listeners) {
39 | listeners.add(listener);
40 | }
41 |
42 | // Return unsubscribe function
43 | return () => this.off(eventName, listener);
44 | }
45 |
46 | /**
47 | * Removes an event listener for the specified event.
48 | */
49 | public off(eventName: string, listener: EventListener): void {
50 | if (typeof listener !== 'function') {
51 | throw new TypeError('Listener must be a function');
52 | }
53 |
54 | const listeners = this.eventListeners.get(eventName);
55 | if (listeners) {
56 | listeners.delete(listener);
57 |
58 | // Clean up empty listener sets
59 | if (listeners.size === 0) {
60 | this.eventListeners.delete(eventName);
61 | }
62 | }
63 | }
64 |
65 | /**
66 | * Adds a one-time event listener that removes itself after being called.
67 | */
68 | public once(eventName: string, listener: EventListener): () => void {
69 | if (typeof listener !== 'function') {
70 | throw new TypeError('Listener must be a function');
71 | }
72 |
73 | const onceWrapper: EventListener = (data: any) => {
74 | this.off(eventName, onceWrapper);
75 | listener(data);
76 | };
77 |
78 | return this.on(eventName, onceWrapper);
79 | }
80 |
81 | /**
82 | * Emits an event with the specified data to all registered listeners.
83 | */
84 | public emit(eventName: string, data?: any): boolean {
85 |
86 | const listeners = this.eventListeners.get(eventName);
87 | if (!listeners) {
88 | return false;
89 | }
90 |
91 | const errors: Error[] = [];
92 | listeners.forEach(listener => {
93 | try {
94 | listener(data);
95 | } catch (error) {
96 | errors.push(error instanceof Error ? error : new Error(String(error)));
97 | }
98 | });
99 |
100 | if (errors.length > 0) {
101 | throw new Error(`Multiple errors occurred while emitting "${eventName}" event: ${errors.map(e => e.message).join(', ')}`);
102 | }
103 |
104 | return true;
105 | }
106 |
107 | /**
108 | * Removes all listeners for a specific event or all events.
109 | */
110 | public removeAllListeners(eventName?: string): void {
111 | if (eventName === undefined) {
112 | this.eventListeners.clear();
113 | } else {
114 | this.eventListeners.delete(eventName);
115 | }
116 | }
117 |
118 | /**
119 | * Gets the number of listeners for a specific event.
120 | */
121 | public listenerCount(eventName: string): number {
122 |
123 | const listeners = this.eventListeners.get(eventName);
124 | return listeners ? listeners.size : 0;
125 | }
126 |
127 | /**
128 | * Gets all registered event names.
129 | */
130 | public eventNames(): string[] {
131 | return Array.from(this.eventListeners.keys());
132 | }
133 |
134 | /**
135 | * Gets all listeners for a specific event.
136 | */
137 | public getListeners(eventName: string): EventListener[] {
138 |
139 | const listeners = this.eventListeners.get(eventName);
140 | return listeners ? Array.from(listeners) : [];
141 | }
142 | }
--------------------------------------------------------------------------------
/src/infrastructure/events/EventTypes.ts:
--------------------------------------------------------------------------------
1 | // src/types/events.ts
2 |
3 | import {
4 | Edge,
5 | Graph,
6 | MetadataAddition,
7 | MetadataDeletion,
8 | MetadataResult
9 | } from "@core/index.js";
10 |
11 | /**
12 | * Event handler function type
13 | */
14 | export type EventListener = (data?: any) => void;
15 |
16 | /**
17 | * Map of event names to their listeners
18 | */
19 | export interface EventMap {
20 | [eventName: string]: Set;
21 | }
22 |
23 | /**
24 | * Defines actions that can be rolled back during a transaction
25 | */
26 | export interface RollbackAction {
27 | action: () => Promise;
28 | description: string;
29 | }
30 |
31 | /**
32 | * Event emitter events
33 | */
34 | export interface EmitterEvents {
35 | // Manager Initialization
36 | initialized: { manager: string };
37 |
38 | // Node Events
39 | beforeAddNodes: { nodes: Node[] };
40 | afterAddNodes: { nodes: Node[] };
41 | beforeUpdateNodes: { nodes: Node[] };
42 | afterUpdateNodes: { nodes: Node[] };
43 | beforeDeleteNodes: { nodeNames: string[] };
44 | afterDeleteNodes: { deletedCount: number };
45 |
46 | // Edge Events
47 | beforeAddEdges: { edges: Edge[] };
48 | afterAddEdges: { edges: Edge[] };
49 | beforeUpdateEdges: { edges: Edge[] };
50 | afterUpdateEdges: { edges: Edge[] };
51 | beforeDeleteEdges: { edges: Edge[] };
52 | afterDeleteEdges: { deletedCount: number };
53 |
54 | // Metadata Events
55 | beforeAddMetadata: { metadata: MetadataAddition[] };
56 | afterAddMetadata: { results: MetadataResult[] };
57 | beforeDeleteMetadata: { deletions: MetadataDeletion[] };
58 | afterDeleteMetadata: { deletedCount: number };
59 |
60 | // Search Events
61 | beforeSearch: { query: string };
62 | afterSearch: Graph;
63 | beforeOpenNodes: { names: string[] };
64 | afterOpenNodes: Graph;
65 |
66 | // Transaction Events
67 | beforeBeginTransaction: {};
68 | afterBeginTransaction: {};
69 | beforeCommit: {};
70 | afterCommit: {};
71 | beforeRollback: { actions: RollbackAction[] };
72 | afterRollback: {};
73 |
74 | // Error Event
75 | error: { operation: string; error: Error };
76 | }
--------------------------------------------------------------------------------
/src/infrastructure/events/index.ts:
--------------------------------------------------------------------------------
1 | // src/infrastructure/events/index.ts
2 |
3 | export {EventEmitter} from './EventEmitter.js';
4 | export type {
5 | EventListener,
6 | EventMap,
7 | RollbackAction,
8 | EmitterEvents
9 | } from './EventTypes.js';
--------------------------------------------------------------------------------
/src/infrastructure/index.ts:
--------------------------------------------------------------------------------
1 | // src/infrastructure/index.ts
2 |
3 | export * from './events/index.js';
4 | export * from './storage/index.js';
--------------------------------------------------------------------------------
/src/infrastructure/storage/IStorage.ts:
--------------------------------------------------------------------------------
1 | // src/types/storage.ts
2 |
3 | import type {Edge, Graph} from '@core/index.js';
4 |
5 | /**
6 | * Edge indexing structure
7 | */
8 | export interface EdgeIndex {
9 | byFrom: Map>;
10 | byTo: Map>;
11 | byType: Map>;
12 | }
13 |
14 | /**
15 | * Storage interface for graph operations
16 | */
17 | export interface IStorage {
18 | loadGraph(): Promise;
19 |
20 | saveGraph(graph: Graph): Promise;
21 |
22 | loadEdgesByIds(edgeIds: string[]): Promise;
23 | }
--------------------------------------------------------------------------------
/src/infrastructure/storage/JsonLineStorage.ts:
--------------------------------------------------------------------------------
1 | // src/core/storage/JsonLineStorage.ts
2 |
3 | import {promises as fs} from 'fs';
4 | import path from 'path';
5 | import {CONFIG} from '@config/config.js';
6 | import type {IStorage} from './IStorage.js';
7 | import type {Edge, Graph} from '@core/index.js';
8 |
9 | /**
10 | * Handles persistent storage of the knowledge graph using a JSON Lines file format.
11 | */
12 | export class JsonLineStorage implements IStorage {
13 | private edgeIndex: {
14 | byFrom: Map>;
15 | byTo: Map>;
16 | byType: Map>;
17 | };
18 | private initialized: boolean;
19 |
20 | constructor() {
21 | this.edgeIndex = {
22 | byFrom: new Map(),
23 | byTo: new Map(),
24 | byType: new Map()
25 | };
26 | this.initialized = false;
27 | }
28 |
29 | /**
30 | * Ensures the storage file and directory exist
31 | */
32 | private async ensureStorageExists(): Promise {
33 | if (this.initialized) {
34 | return;
35 | }
36 |
37 | const MEMORY_FILE_PATH = CONFIG.PATHS.MEMORY_FILE;
38 | const dir = path.dirname(MEMORY_FILE_PATH);
39 |
40 | try {
41 | // Check if directory exists, create if it doesn't
42 | try {
43 | await fs.access(dir);
44 | } catch {
45 | await fs.mkdir(dir, {recursive: true});
46 | }
47 |
48 | // Check if file exists, create if it doesn't
49 | try {
50 | await fs.access(MEMORY_FILE_PATH);
51 | } catch {
52 | await fs.writeFile(MEMORY_FILE_PATH, '');
53 | }
54 |
55 | this.initialized = true;
56 | } catch (error) {
57 | console.error('Error initializing storage:', error);
58 | throw new Error('Failed to initialize storage');
59 | }
60 | }
61 |
62 | /**
63 | * Loads the entire knowledge graph from storage and builds the edge indices.
64 | */
65 | async loadGraph(): Promise {
66 | await this.ensureStorageExists();
67 |
68 | try {
69 | const MEMORY_FILE_PATH = CONFIG.PATHS.MEMORY_FILE;
70 | const data = await fs.readFile(MEMORY_FILE_PATH, "utf-8");
71 | const lines = data.split("\n").filter(line => line.trim() !== "");
72 |
73 | // Clear existing indices before rebuilding
74 | this.clearIndices();
75 |
76 | const graph: Graph = {nodes: [], edges: []};
77 |
78 | for (const line of lines) {
79 | try {
80 | const item = JSON.parse(line);
81 | if (item.type === "node") {
82 | graph.nodes.push(item);
83 | } else if (item.type === "edge") {
84 | graph.edges.push(item);
85 | }
86 | } catch (parseError) {
87 | console.error('Error parsing line:', line, parseError);
88 | }
89 | }
90 |
91 | return graph;
92 | } catch (error) {
93 | if (error instanceof Error && 'code' in error && error.code === "ENOENT") {
94 | return {nodes: [], edges: []};
95 | }
96 | throw error;
97 | }
98 | }
99 |
100 | /**
101 | * Saves the entire knowledge graph to storage.
102 | */
103 | async saveGraph(graph: Graph): Promise {
104 | await this.ensureStorageExists();
105 |
106 | const MEMORY_FILE_PATH = CONFIG.PATHS.MEMORY_FILE;
107 |
108 | const processedEdges = graph.edges.map(edge => ({
109 | ...edge,
110 | type: 'edge'
111 | }));
112 |
113 | const lines = [
114 | ...graph.nodes.map(node => JSON.stringify({...node, type: 'node'})),
115 | ...processedEdges.map(edge => JSON.stringify(edge))
116 | ];
117 |
118 | await fs.writeFile(MEMORY_FILE_PATH, lines.join("\n") + (lines.length > 0 ? "\n" : ""));
119 | }
120 |
121 | /**
122 | * Loads specific edges by their IDs from storage.
123 | */
124 | async loadEdgesByIds(edgeIds: string[]): Promise {
125 | const graph = await this.loadGraph();
126 | const edgeMap = new Map(
127 | graph.edges.map(edge => [this.generateEdgeId(edge), edge])
128 | );
129 |
130 | return edgeIds
131 | .map(id => edgeMap.get(id))
132 | .filter((edge): edge is Edge => edge !== undefined);
133 | }
134 |
135 | /**
136 | * Indexes a single edge by adding it to all relevant indices.
137 | */
138 | private indexEdge(edge: Edge): void {
139 | const edgeId = this.generateEdgeId(edge);
140 |
141 | // Index by 'from' node
142 | if (!this.edgeIndex.byFrom.has(edge.from)) {
143 | this.edgeIndex.byFrom.set(edge.from, new Set());
144 | }
145 | this.edgeIndex.byFrom.get(edge.from)?.add(edgeId);
146 |
147 | // Index by 'to' node
148 | if (!this.edgeIndex.byTo.has(edge.to)) {
149 | this.edgeIndex.byTo.set(edge.to, new Set());
150 | }
151 | this.edgeIndex.byTo.get(edge.to)?.add(edgeId);
152 |
153 | // Index by edge type
154 | if (!this.edgeIndex.byType.has(edge.edgeType)) {
155 | this.edgeIndex.byType.set(edge.edgeType, new Set());
156 | }
157 | this.edgeIndex.byType.get(edge.edgeType)?.add(edgeId);
158 | }
159 |
160 | /**
161 | * Generates a unique ID for an edge based on its properties.
162 | */
163 | private generateEdgeId(edge: Edge): string {
164 | return `${edge.from}|${edge.to}|${edge.edgeType}`;
165 | }
166 |
167 | /**
168 | * Clears all edge indices.
169 | */
170 | private clearIndices(): void {
171 | this.edgeIndex.byFrom.clear();
172 | this.edgeIndex.byTo.clear();
173 | this.edgeIndex.byType.clear();
174 | }
175 | }
--------------------------------------------------------------------------------
/src/infrastructure/storage/index.ts:
--------------------------------------------------------------------------------
1 | // src/infrastructure/storage/index.ts
2 |
3 | export {JsonLineStorage} from './JsonLineStorage.js';
4 | export type {
5 | IStorage,
6 | EdgeIndex
7 | } from './IStorage.js';
--------------------------------------------------------------------------------
/src/integration/index.ts:
--------------------------------------------------------------------------------
1 | // src/integration/index.ts
2 |
3 | export * from './tools/index.js';
--------------------------------------------------------------------------------
/src/integration/tools/DynamicSchemaToolRegistry.ts:
--------------------------------------------------------------------------------
1 | // src/tools/DynamicSchemaToolRegistry.ts
2 |
3 | import {promises as fs} from 'fs';
4 | import path from 'path';
5 | import {
6 | SchemaLoader,
7 | createSchemaNode,
8 | handleSchemaUpdate,
9 | handleSchemaDelete
10 | } from '@core/index.js';
11 | import {CONFIG} from '@config/index.js';
12 | import {formatToolResponse, formatToolError} from '@shared/index.js';
13 | import type {ApplicationManager} from '@application/index.js';
14 | import type {Tool, ToolResponse} from '@shared/index.js';
15 | import type {SchemaBuilder} from '@core/index.js';
16 |
17 | /**
18 | * Interface defining the public contract for dynamic schema tool registry
19 | */
20 | export interface IDynamicSchemaToolRegistry {
21 | getTools(): Tool[];
22 |
23 | handleToolCall(toolName: string, args: Record, knowledgeGraphManager: ApplicationManager): Promise;
24 | }
25 |
26 | /**
27 | * Manages dynamic tools generated from schema definitions
28 | */
29 | class DynamicSchemaToolRegistry implements IDynamicSchemaToolRegistry {
30 | private schemas: Map;
31 | private toolsCache: Map;
32 | private static instance: DynamicSchemaToolRegistry;
33 |
34 | private constructor() {
35 | this.schemas = new Map();
36 | this.toolsCache = new Map();
37 | }
38 |
39 | /**
40 | * Gets the singleton instance
41 | */
42 | public static getInstance(): DynamicSchemaToolRegistry {
43 | if (!DynamicSchemaToolRegistry.instance) {
44 | DynamicSchemaToolRegistry.instance = new DynamicSchemaToolRegistry();
45 | }
46 | return DynamicSchemaToolRegistry.instance;
47 | }
48 |
49 | /**
50 | * Initializes the registry by loading schemas and generating tools
51 | */
52 | public async initialize(): Promise {
53 | try {
54 | const SCHEMAS_DIR = CONFIG.PATHS.SCHEMAS_DIR;
55 | const schemaFiles = await fs.readdir(SCHEMAS_DIR);
56 |
57 | // Process schema files
58 | for (const file of schemaFiles) {
59 | if (file.endsWith('.schema.json')) {
60 | const schemaName = path.basename(file, '.schema.json');
61 | const schema = await SchemaLoader.loadSchema(schemaName);
62 | this.schemas.set(schemaName, schema);
63 | }
64 | }
65 |
66 | // Generate tools for each schema
67 | for (const [schemaName, schema] of this.schemas.entries()) {
68 | const tools = await this.generateToolsForSchema(schemaName, schema);
69 | tools.forEach(tool => this.toolsCache.set(tool.name, tool));
70 | }
71 |
72 | console.error(`[DynamicSchemaTools] Initialized ${this.schemas.size} schemas and ${this.toolsCache.size} tools`);
73 | } catch (error) {
74 | console.error('[DynamicSchemaTools] Initialization error:', error);
75 | throw error;
76 | }
77 | }
78 |
79 | /**
80 | * Retrieves all generated tools
81 | */
82 | public getTools(): Tool[] {
83 | return Array.from(this.toolsCache.values());
84 | }
85 |
86 | /**
87 | * Generates tools for a given schema
88 | */
89 | private async generateToolsForSchema(schemaName: string, schema: SchemaBuilder): Promise {
90 | const tools: Tool[] = [];
91 | const baseSchema = schema.build();
92 |
93 | // Add tool
94 | tools.push(baseSchema as unknown as Tool);
95 |
96 | // Update tool
97 | const updateSchema = schema.createUpdateSchema();
98 | tools.push(updateSchema as unknown as Tool);
99 |
100 | // Delete tool
101 | const deleteSchema: Tool = {
102 | name: `delete_${schemaName}`,
103 | description: `Delete
104 | an existing
105 | ${schemaName}
106 | from
107 | the
108 | knowledge
109 | graph`,
110 | inputSchema: {
111 | type: "object",
112 | properties: {
113 | [`delete_${schemaName}`]: {
114 | type: "object",
115 | description: `Delete parameters for ${schemaName}`,
116 | properties: {
117 | name: {
118 | type: "string",
119 | description: `The name of the ${schemaName} to delete`
120 | }
121 | },
122 | required: ["name"]
123 | }
124 | },
125 | required: [`delete_${schemaName}`]
126 | }
127 | };
128 |
129 | tools.push(deleteSchema);
130 | return tools;
131 | }
132 |
133 | /**
134 | * Handles tool calls for dynamically generated schema-based tools
135 | */
136 | public async handleToolCall(
137 | toolName: string,
138 | args: Record,
139 | knowledgeGraphManager: ApplicationManager
140 | ): Promise {
141 | const match = toolName.match(/^(add|update|delete)_(.+)$/);
142 | if (!match) {
143 | return formatToolError({
144 | operation: toolName,
145 | error: `Invalid tool name format: ${toolName}`,
146 | suggestions: ["Tool name must follow pattern: 'add|update|delete_'"]
147 | });
148 | }
149 |
150 | const [, operation, schemaName] = match;
151 | const schemaBuilder = this.schemas.get(schemaName);
152 |
153 | if (!schemaBuilder) {
154 | return formatToolError({
155 | operation: toolName,
156 | error: `Schema not found: ${schemaName}`,
157 | context: {availableSchemas: Array.from(this.schemas.keys())},
158 | suggestions: ["Verify schema name exists"]
159 | });
160 | }
161 |
162 | try {
163 | const schema = schemaBuilder.build();
164 |
165 | switch (operation) {
166 | case 'add': {
167 | const nodeData = args[schemaName];
168 | const existingNodes = await knowledgeGraphManager.openNodes([nodeData.name]);
169 |
170 | if (existingNodes.nodes.length > 0) {
171 | throw new Error(`Node already exists: ${nodeData.name}`);
172 | }
173 |
174 | const {nodes, edges} = await createSchemaNode(nodeData, schema, schemaName);
175 |
176 | await knowledgeGraphManager.beginTransaction();
177 | try {
178 | await knowledgeGraphManager.addNodes(nodes);
179 | if (edges.length > 0) {
180 | await knowledgeGraphManager.addEdges(edges);
181 | }
182 | await knowledgeGraphManager.commit();
183 |
184 | return formatToolResponse({
185 | data: {nodes, edges},
186 | actionTaken: `Created ${schemaName}: ${nodeData.name}`
187 | });
188 | } catch (error) {
189 | await knowledgeGraphManager.rollback();
190 | throw error;
191 | }
192 | }
193 |
194 | case 'update': {
195 | return handleSchemaUpdate(
196 | args[`update_${schemaName}`],
197 | schema,
198 | schemaName,
199 | knowledgeGraphManager
200 | );
201 | }
202 |
203 | case 'delete': {
204 | const {name} = args[`delete_${schemaName}`];
205 | if (!name) {
206 | return formatToolError({
207 | operation: toolName,
208 | error: `Name is required to delete a ${schemaName}`,
209 | suggestions: ["Provide the 'name' parameter"]
210 | });
211 | }
212 | return handleSchemaDelete(name, schemaName, knowledgeGraphManager);
213 | }
214 |
215 | default:
216 | return formatToolError({
217 | operation: toolName,
218 | error: `Unknown operation: ${operation}`,
219 | suggestions: ["Use 'add', 'update', or 'delete'"]
220 | });
221 | }
222 | } catch (error) {
223 | return formatToolError({
224 | operation: toolName,
225 | error: error instanceof Error ? error.message : 'Unknown error occurred',
226 | context: {args},
227 | suggestions: [
228 | "Check input parameters against schema",
229 | "Verify entity existence for updates/deletes"
230 | ],
231 | recoverySteps: [
232 | "Review schema requirements",
233 | "Ensure all required fields are provided"
234 | ]
235 | });
236 | }
237 | }
238 | }
239 |
240 | // Create and export singleton instance
241 | export const dynamicSchemaTools = DynamicSchemaToolRegistry.getInstance();
242 |
243 | /**
244 | * Initializes the dynamic tools registry
245 | */
246 | export async function initializeDynamicTools(): Promise {
247 | await dynamicSchemaTools.initialize();
248 | return dynamicSchemaTools;
249 | }
--------------------------------------------------------------------------------
/src/integration/tools/callToolHandler.ts:
--------------------------------------------------------------------------------
1 | // src/tools/callToolHandler.ts
2 |
3 | import {toolsRegistry, ToolHandlerFactory} from '@integration/index.js';
4 | import {formatToolError} from '@shared/index.js';
5 | import type {ApplicationManager} from '@application/index.js';
6 | import type {ToolResponse} from '@shared/index.js';
7 |
8 | interface ToolCallRequest {
9 | params: {
10 | name: string;
11 | arguments: Record;
12 | };
13 | }
14 |
15 | /**
16 | * Handles incoming tool call requests by routing them to the appropriate handler
17 | */
18 | export async function handleCallToolRequest(
19 | request: ToolCallRequest,
20 | knowledgeGraphManager: ApplicationManager
21 | ): Promise {
22 | try {
23 | const {name, arguments: args} = request.params;
24 |
25 | if (!args) {
26 | return formatToolError({
27 | operation: name,
28 | error: "Tool arguments are required",
29 | suggestions: ["Provide required arguments for the tool"]
30 | });
31 | }
32 |
33 | // Ensure tools registry is initialized
34 | if (!toolsRegistry.hasTool(name)) {
35 | return formatToolError({
36 | operation: name,
37 | error: `Tool not found: ${name}`,
38 | context: {
39 | availableTools: toolsRegistry.getAllTools().map(t => t.name)
40 | },
41 | suggestions: ["Verify tool name is correct"],
42 | recoverySteps: ["Check available tools list"]
43 | });
44 | }
45 |
46 | // Initialize handlers if needed
47 | if (!ToolHandlerFactory.isInitialized()) {
48 | ToolHandlerFactory.initialize(knowledgeGraphManager);
49 | }
50 |
51 | // Get appropriate handler and process the request
52 | const handler = ToolHandlerFactory.getHandler(name);
53 | return await handler.handleTool(name, args);
54 |
55 | } catch (error) {
56 | return formatToolError({
57 | operation: "callTool",
58 | error: error instanceof Error ? error.message : 'Unknown error occurred',
59 | context: {request},
60 | suggestions: [
61 | "Verify tool name and arguments",
62 | "Check tool handler initialization"
63 | ],
64 | recoverySteps: [
65 | "Review tool documentation",
66 | "Ensure all required arguments are provided"
67 | ]
68 | });
69 | }
70 | }
--------------------------------------------------------------------------------
/src/integration/tools/handlers/BaseToolHandler.ts:
--------------------------------------------------------------------------------
1 | // src/tools/handlers/BaseToolHandler.ts
2 |
3 | import {formatToolError} from '@shared/index.js';
4 | import type {ApplicationManager} from '@application/index.js';
5 | import type {ToolResponse} from '@shared/index.js';
6 |
7 | export abstract class BaseToolHandler {
8 | constructor(protected knowledgeGraphManager: ApplicationManager) {
9 | }
10 |
11 | abstract handleTool(name: string, args: Record): Promise;
12 |
13 | protected validateArguments(args: Record): void {
14 | if (!args) {
15 | throw new Error("Tool arguments are required");
16 | }
17 | }
18 |
19 | protected handleError(name: string, error: unknown): ToolResponse {
20 | console.error(`Error in ${name}:`, error);
21 | return formatToolError({
22 | operation: name,
23 | error: error instanceof Error ? error.message : 'Unknown error occurred',
24 | context: {toolName: name},
25 | suggestions: ["Examine the tool input parameters for correctness.", "Verify that the requested operation is supported."],
26 | recoverySteps: ["Adjust the input parameters based on the schema definition."]
27 | });
28 | }
29 | }
--------------------------------------------------------------------------------
/src/integration/tools/handlers/DynamicToolHandler.ts:
--------------------------------------------------------------------------------
1 | // src/tools/handlers/DynamicToolHandler.ts
2 |
3 | import {BaseToolHandler} from './BaseToolHandler.js';
4 | import {dynamicToolManager} from '@integration/index.js';
5 | import {formatToolResponse, formatToolError} from '@shared/index.js';
6 | import type {ToolResponse} from '@shared/index.js';
7 |
8 | export class DynamicToolHandler extends BaseToolHandler {
9 | async handleTool(name: string, args: Record): Promise {
10 | try {
11 | this.validateArguments(args);
12 |
13 | const toolResult = await dynamicToolManager.handleToolCall(name, args, this.knowledgeGraphManager);
14 |
15 | if (toolResult?.toolResult?.isError !== undefined) {
16 | return toolResult;
17 | }
18 |
19 | return formatToolResponse({
20 | data: toolResult,
21 | actionTaken: `Executed dynamic tool: ${name}`
22 | });
23 | } catch (error) {
24 | return formatToolError({
25 | operation: name,
26 | error: error instanceof Error ? error.message : 'Unknown error occurred',
27 | context: {toolName: name, args},
28 | suggestions: [
29 | "Examine the tool input parameters for correctness",
30 | "Verify that the requested operation is supported"
31 | ],
32 | recoverySteps: [
33 | "Adjust the input parameters based on the schema definition"
34 | ]
35 | });
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/src/integration/tools/handlers/GraphToolHandler.ts:
--------------------------------------------------------------------------------
1 | // src/tools/handlers/GraphToolHandler.ts
2 |
3 | import {BaseToolHandler} from './BaseToolHandler.js';
4 | import {formatToolResponse, formatToolError} from '@shared/index.js';
5 | import type {ToolResponse} from '@shared/index.js';
6 |
7 | export class GraphToolHandler extends BaseToolHandler {
8 | async handleTool(name: string, args: Record): Promise {
9 | try {
10 | this.validateArguments(args);
11 |
12 | switch (name) {
13 | case "add_nodes":
14 | const addedNodes = await this.knowledgeGraphManager.addNodes(args.nodes);
15 | return formatToolResponse({
16 | data: {nodes: addedNodes},
17 | actionTaken: "Added nodes to knowledge graph"
18 | });
19 |
20 | case "update_nodes":
21 | const updatedNodes = await this.knowledgeGraphManager.updateNodes(args.nodes);
22 | return formatToolResponse({
23 | data: {nodes: updatedNodes},
24 | actionTaken: "Updated nodes in knowledge graph"
25 | });
26 |
27 | case "add_edges":
28 | const addedEdges = await this.knowledgeGraphManager.addEdges(args.edges);
29 | return formatToolResponse({
30 | data: {edges: addedEdges},
31 | actionTaken: "Added edges to knowledge graph"
32 | });
33 |
34 | case "update_edges":
35 | const updatedEdges = await this.knowledgeGraphManager.updateEdges(args.edges);
36 | return formatToolResponse({
37 | data: {edges: updatedEdges},
38 | actionTaken: "Updated edges in knowledge graph"
39 | });
40 |
41 | case "delete_nodes":
42 | await this.knowledgeGraphManager.deleteNodes(args.nodeNames);
43 | return formatToolResponse({
44 | actionTaken: `Deleted nodes: ${args.nodeNames.join(', ')}`
45 | });
46 |
47 | case "delete_edges":
48 | await this.knowledgeGraphManager.deleteEdges(args.edges);
49 | return formatToolResponse({
50 | actionTaken: "Deleted edges from knowledge graph"
51 | });
52 |
53 | default:
54 | throw new Error(`Unknown graph operation: ${name}`);
55 | }
56 | } catch (error) {
57 | return formatToolError({
58 | operation: name,
59 | error: error instanceof Error ? error.message : 'Unknown error occurred',
60 | context: {args},
61 | suggestions: [
62 | "Verify node/edge existence",
63 | "Check input parameters format"
64 | ],
65 | recoverySteps: [
66 | "Review the error details and adjust inputs",
67 | "Ensure referenced nodes exist before creating edges"
68 | ]
69 | });
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/src/integration/tools/handlers/MetadataToolHandler.ts:
--------------------------------------------------------------------------------
1 | // src/tools/handlers/MetadataToolHandler.ts
2 |
3 | import {BaseToolHandler} from './BaseToolHandler.js';
4 | import {formatToolResponse, formatToolError} from '@shared/index.js';
5 | import type {ToolResponse} from '@shared/index.js';
6 |
7 | export class MetadataToolHandler extends BaseToolHandler {
8 | async handleTool(name: string, args: Record): Promise {
9 | try {
10 | this.validateArguments(args);
11 |
12 | switch (name) {
13 | case "add_metadata":
14 | const addResult = await this.knowledgeGraphManager.addMetadata(args.metadata);
15 | return formatToolResponse({
16 | data: {metadata: addResult},
17 | actionTaken: "Added metadata to nodes"
18 | });
19 |
20 | case "delete_metadata":
21 | await this.knowledgeGraphManager.deleteMetadata(args.deletions);
22 | return formatToolResponse({
23 | actionTaken: "Deleted metadata from nodes"
24 | });
25 |
26 | default:
27 | throw new Error(`Unknown metadata operation: ${name}`);
28 | }
29 | } catch (error) {
30 | return formatToolError({
31 | operation: name,
32 | error: error instanceof Error ? error.message : 'Unknown error occurred',
33 | context: {args},
34 | suggestions: [
35 | "Verify node existence",
36 | "Check metadata format"
37 | ],
38 | recoverySteps: [
39 | "Ensure nodes exist before adding metadata",
40 | "Verify metadata content format"
41 | ]
42 | });
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/src/integration/tools/handlers/SearchToolHandler.ts:
--------------------------------------------------------------------------------
1 | // src/tools/handlers/SearchToolHandler.ts
2 |
3 | import {BaseToolHandler} from './BaseToolHandler.js';
4 | import {formatToolResponse, formatToolError} from '@shared/index.js';
5 | import type {ToolResponse} from '@shared/index.js';
6 |
7 | export class SearchToolHandler extends BaseToolHandler {
8 | async handleTool(name: string, args: Record): Promise {
9 | try {
10 | this.validateArguments(args);
11 |
12 | switch (name) {
13 | case "read_graph":
14 | const graph = await this.knowledgeGraphManager.readGraph();
15 | return formatToolResponse({
16 | data: graph,
17 | actionTaken: "Read complete knowledge graph"
18 | });
19 |
20 | case "search_nodes":
21 | const searchResults = await this.knowledgeGraphManager.searchNodes(args.query);
22 | return formatToolResponse({
23 | data: searchResults,
24 | actionTaken: `Searched nodes with query: ${args.query}`
25 | });
26 |
27 | case "open_nodes":
28 | const nodes = await this.knowledgeGraphManager.openNodes(args.names);
29 | return formatToolResponse({
30 | data: nodes,
31 | actionTaken: `Retrieved nodes: ${args.names.join(', ')}`
32 | });
33 |
34 | default:
35 | throw new Error(`Unknown search operation: ${name}`);
36 | }
37 | } catch (error) {
38 | return formatToolError({
39 | operation: name,
40 | error: error instanceof Error ? error.message : 'Unknown error occurred',
41 | context: {args},
42 | suggestions: [
43 | "Check node names exist",
44 | "Verify search query format"
45 | ],
46 | recoverySteps: [
47 | "Try with different node names",
48 | "Adjust search query parameters"
49 | ]
50 | });
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/src/integration/tools/handlers/ToolHandlerFactory.ts:
--------------------------------------------------------------------------------
1 | // src/tools/handlers/ToolHandlerFactory.ts
2 |
3 | import {GraphToolHandler} from './GraphToolHandler.js';
4 | import {SearchToolHandler} from './SearchToolHandler.js';
5 | import {MetadataToolHandler} from './MetadataToolHandler.js';
6 | import {DynamicToolHandler} from './DynamicToolHandler.js';
7 | import {toolsRegistry} from '@integration/index.js';
8 | import type {ApplicationManager} from '@application/index.js';
9 | import type {BaseToolHandler} from './BaseToolHandler.js';
10 |
11 | export class ToolHandlerFactory {
12 | private static graphHandler: GraphToolHandler;
13 | private static searchHandler: SearchToolHandler;
14 | private static metadataHandler: MetadataToolHandler;
15 | private static dynamicHandler: DynamicToolHandler;
16 | private static initialized = false;
17 |
18 | /**
19 | * Initializes all tool handlers
20 | */
21 | static initialize(knowledgeGraphManager: ApplicationManager): void {
22 | if (this.initialized) {
23 | return;
24 | }
25 |
26 | this.graphHandler = new GraphToolHandler(knowledgeGraphManager);
27 | this.searchHandler = new SearchToolHandler(knowledgeGraphManager);
28 | this.metadataHandler = new MetadataToolHandler(knowledgeGraphManager);
29 | this.dynamicHandler = new DynamicToolHandler(knowledgeGraphManager);
30 |
31 | this.initialized = true;
32 | }
33 |
34 | /**
35 | * Gets the appropriate handler for a given tool name
36 | */
37 | static getHandler(toolName: string): BaseToolHandler {
38 | if (!this.initialized) {
39 | throw new Error('ToolHandlerFactory not initialized');
40 | }
41 |
42 | // First check static tools
43 | if (toolName.match(/^(add|update|delete)_(nodes|edges)$/)) {
44 | return this.graphHandler;
45 | }
46 | if (toolName.match(/^(read_graph|search_nodes|open_nodes)$/)) {
47 | return this.searchHandler;
48 | }
49 | if (toolName.match(/^(add|delete)_metadata$/)) {
50 | return this.metadataHandler;
51 | }
52 |
53 | // Then check dynamic tools
54 | if (toolsRegistry.hasTool(toolName) && toolName.match(/^(add|update|delete)_/)) {
55 | return this.dynamicHandler;
56 | }
57 |
58 | throw new Error(`No handler found for tool: ${toolName}`);
59 | }
60 |
61 | /**
62 | * Checks if factory is initialized
63 | */
64 | static isInitialized(): boolean {
65 | return this.initialized;
66 | }
67 | }
--------------------------------------------------------------------------------
/src/integration/tools/handlers/index.ts:
--------------------------------------------------------------------------------
1 | // src/integration/tools/handlers/index.ts
2 |
3 | export {BaseToolHandler} from './BaseToolHandler.js';
4 | export {GraphToolHandler} from './GraphToolHandler.js';
5 | export {SearchToolHandler} from './SearchToolHandler.js';
6 | export {MetadataToolHandler} from './MetadataToolHandler.js';
7 | export {DynamicToolHandler} from './DynamicToolHandler.js';
8 | export {ToolHandlerFactory} from './ToolHandlerFactory.js';
--------------------------------------------------------------------------------
/src/integration/tools/index.ts:
--------------------------------------------------------------------------------
1 | // src/integration/tools/index.ts
2 |
3 | export * from './registry/index.js';
4 | export * from './handlers/index.js';
5 | export { handleCallToolRequest } from './callToolHandler.js';
--------------------------------------------------------------------------------
/src/integration/tools/registry/dynamicTools.ts:
--------------------------------------------------------------------------------
1 | // src/tools/registry/dynamicTools.ts
2 |
3 | import {formatToolError} from '@shared/index.js';
4 | import {initializeDynamicTools, type IDynamicSchemaToolRegistry} from '@integration/tools/DynamicSchemaToolRegistry.js';
5 | import type {Tool, ToolResponse} from '@shared/index.js';
6 | import type {ApplicationManager} from '@application/index.js';
7 |
8 | /**
9 | * Manages dynamically generated tools based on schemas
10 | */
11 | export class DynamicToolManager {
12 | private static instance: DynamicToolManager;
13 | private registry: IDynamicSchemaToolRegistry | null = null;
14 | private initialized = false;
15 |
16 | private constructor() {
17 | }
18 |
19 | /**
20 | * Gets the singleton instance of DynamicToolManager
21 | */
22 | static getInstance(): DynamicToolManager {
23 | if (!DynamicToolManager.instance) {
24 | DynamicToolManager.instance = new DynamicToolManager();
25 | }
26 | return DynamicToolManager.instance;
27 | }
28 |
29 | /**
30 | * Initializes the dynamic tool registry
31 | */
32 | async initialize(): Promise {
33 | if (this.initialized) {
34 | return;
35 | }
36 |
37 | try {
38 | this.registry = await initializeDynamicTools();
39 | this.initialized = true;
40 | } catch (error) {
41 | console.error('Failed to initialize dynamic tools:', error);
42 | throw new Error('Dynamic tools initialization failed');
43 | }
44 | }
45 |
46 | /**
47 | * Gets all dynamically generated tools
48 | */
49 | getTools(): Tool[] {
50 | if (!this.initialized || !this.registry) {
51 | throw new Error('Dynamic tools not initialized');
52 | }
53 | return this.registry.getTools();
54 | }
55 |
56 | /**
57 | * Handles a call to a dynamic tool
58 | */
59 | async handleToolCall(
60 | toolName: string,
61 | args: Record,
62 | knowledgeGraphManager: ApplicationManager
63 | ): Promise {
64 | if (!this.initialized || !this.registry) {
65 | return formatToolError({
66 | operation: toolName,
67 | error: 'Dynamic tools not initialized',
68 | suggestions: ["Initialize dynamic tools before use"],
69 | recoverySteps: ["Reinitialize DynamicToolManager"]
70 | });
71 | }
72 |
73 | try {
74 | return await this.registry.handleToolCall(toolName, args, knowledgeGraphManager);
75 | } catch (error) {
76 | return formatToolError({
77 | operation: toolName,
78 | error: error instanceof Error ? error.message : 'Unknown error occurred',
79 | context: {
80 | toolName,
81 | argumentKeys: Object.keys(args)
82 | },
83 | suggestions: [
84 | "Verify tool name matches schema",
85 | "Check argument format"
86 | ],
87 | recoverySteps: [
88 | "Review tool schema requirements",
89 | "Ensure tool is properly registered"
90 | ]
91 | });
92 | }
93 | }
94 |
95 | /**
96 | * Checks if a tool name corresponds to a dynamic tool
97 | */
98 | isDynamicTool(toolName: string): boolean {
99 | if (!this.initialized || !this.registry) {
100 | return false;
101 | }
102 | return this.getTools().some(tool => tool.name === toolName);
103 | }
104 | }
105 |
106 | /**
107 | * Singleton instance of the DynamicToolManager
108 | */
109 | export const dynamicToolManager = DynamicToolManager.getInstance();
110 |
111 | /**
112 | * Re-export types that might be needed by consumers
113 | */
114 | export type {
115 | Tool,
116 | ToolResponse,
117 | IDynamicSchemaToolRegistry
118 | };
--------------------------------------------------------------------------------
/src/integration/tools/registry/index.ts:
--------------------------------------------------------------------------------
1 | // src/integration/tools/registry/index.ts
2 |
3 | export {toolsRegistry} from './toolsRegistry.js';
4 | export {dynamicToolManager} from './dynamicTools.js';
5 | export {allStaticTools, graphTools, searchTools, metadataTools} from './staticTools.js';
--------------------------------------------------------------------------------
/src/integration/tools/registry/staticTools.ts:
--------------------------------------------------------------------------------
1 | // src/tools/registry/staticTools.ts
2 |
3 | import type {Tool} from '@shared/index.js';
4 |
5 | /**
6 | * Graph manipulation tools for managing nodes and edges
7 | */
8 | export const graphTools: Tool[] = [
9 | {
10 | name: "add_nodes",
11 | description: "Add multiple new nodes in the knowledge graph",
12 | inputSchema: {
13 | type: "object",
14 | properties: {
15 | nodes: {
16 | type: "array",
17 | description: "Array of nodes to add",
18 | items: {
19 | type: "object",
20 | description: "Node to add",
21 | properties: {
22 | name: {type: "string", description: "The name of the node"},
23 | nodeType: {type: "string", description: "The type of the node"},
24 | metadata: {
25 | type: "array",
26 | items: {type: "string", description: "Metadata item"},
27 | description: "An array of metadata contents associated with the node"
28 | },
29 | },
30 | required: ["name", "nodeType", "metadata"],
31 | },
32 | },
33 | },
34 | required: ["nodes"],
35 | },
36 | },
37 |
38 | {
39 | name: "update_nodes",
40 | description: "Update existing nodes in the knowledge graph",
41 | inputSchema: {
42 | type: "object",
43 | properties: {
44 | nodes: {
45 | type: "array",
46 | description: "Array of nodes to update",
47 | items: {
48 | type: "object",
49 | description: "Node to update",
50 | properties: {
51 | name: {type: "string", description: "The name of the node to update"},
52 | nodeType: {type: "string", description: "The new type of the node"},
53 | metadata: {
54 | type: "array",
55 | items: {type: "string", description: "Metadata item"},
56 | description: "An array of new metadata contents for the node"
57 | },
58 | },
59 | required: ["name"],
60 | },
61 | },
62 | },
63 | required: ["nodes"],
64 | },
65 | },
66 |
67 | {
68 | name: "add_edges",
69 | description: "Add multiple new edges between nodes in the knowledge graph. Edges should be in active voice",
70 | inputSchema: {
71 | type: "object",
72 | properties: {
73 | edges: {
74 | type: "array",
75 | description: "Array of edges to add",
76 | items: {
77 | type: "object",
78 | description: "Edge to add",
79 | properties: {
80 | from: {type: "string", description: "The name of the node where the edge starts"},
81 | to: {type: "string", description: "The name of the node where the edge ends"},
82 | edgeType: {type: "string", description: "The type of the edge"},
83 | weight: {
84 | type: "number",
85 | description: "Optional edge weight (0-1 range). Defaults to 1 if not specified",
86 | minimum: 0,
87 | maximum: 1
88 | }
89 | },
90 | required: ["from", "to", "edgeType"],
91 | },
92 | },
93 | },
94 | required: ["edges"],
95 | },
96 | },
97 |
98 | {
99 | name: "update_edges",
100 | description: "Update existing edges in the knowledge graph",
101 | inputSchema: {
102 | type: "object",
103 | properties: {
104 | edges: {
105 | type: "array",
106 | description: "Array of edges to update",
107 | items: {
108 | type: "object",
109 | description: "Edge to update",
110 | properties: {
111 | from: {type: "string", description: "Current source node name"},
112 | to: {type: "string", description: "Current target node name"},
113 | edgeType: {type: "string", description: "Current edge type"},
114 | newFrom: {type: "string", description: "New source node name"},
115 | newTo: {type: "string", description: "New target node name"},
116 | newEdgeType: {type: "string", description: "New edge type"},
117 | newWeight: {
118 | type: "number",
119 | description: "Optional new edge weight (0-1 range)",
120 | minimum: 0,
121 | maximum: 1
122 | }
123 | },
124 | required: ["from", "to", "edgeType"],
125 | },
126 | },
127 | },
128 | required: ["edges"],
129 | },
130 | },
131 |
132 | {
133 | name: "delete_nodes",
134 | description: "Delete multiple nodes and their associated edges from the knowledge graph",
135 | inputSchema: {
136 | type: "object",
137 | properties: {
138 | nodeNames: {
139 | type: "array",
140 | items: {type: "string", description: "Node name to delete"},
141 | description: "An array of node names to delete"
142 | },
143 | },
144 | required: ["nodeNames"],
145 | },
146 | },
147 |
148 | {
149 | name: "delete_edges",
150 | description: "Delete multiple edges from the knowledge graph",
151 | inputSchema: {
152 | type: "object",
153 | properties: {
154 | edges: {
155 | type: "array",
156 | description: "Array of edges to delete",
157 | items: {
158 | type: "object",
159 | description: "Edge to delete",
160 | properties: {
161 | from: {type: "string", description: "The name of the node where the edge starts"},
162 | to: {type: "string", description: "The name of the node where the edge ends"},
163 | edgeType: {type: "string", description: "The type of the edge"},
164 | },
165 | required: ["from", "to", "edgeType"],
166 | },
167 | },
168 | },
169 | required: ["edges"],
170 | },
171 | },
172 | ];
173 |
174 | /**
175 | * Search-related tools for querying the knowledge graph
176 | */
177 | export const searchTools: Tool[] = [
178 | {
179 | name: "read_graph",
180 | description: "Read the entire knowledge graph",
181 | inputSchema: {
182 | type: "object",
183 | properties: {},
184 | },
185 | },
186 |
187 | {
188 | name: "search_nodes",
189 | description: "Search for nodes in the knowledge graph based on a query",
190 | inputSchema: {
191 | type: "object",
192 | properties: {
193 | query: {
194 | type: "string",
195 | description: "The search query to match against node names, types, and metadata content"
196 | },
197 | },
198 | required: ["query"],
199 | },
200 | },
201 |
202 | {
203 | name: "open_nodes",
204 | description: "Open specific nodes in the knowledge graph by their names",
205 | inputSchema: {
206 | type: "object",
207 | properties: {
208 | names: {
209 | type: "array",
210 | items: {type: "string", description: "Node name to open"},
211 | description: "An array of node names to retrieve",
212 | },
213 | },
214 | required: ["names"],
215 | },
216 | }
217 | ];
218 |
219 | /**
220 | * Metadata-related tools for managing node metadata
221 | */
222 | export const metadataTools: Tool[] = [
223 | {
224 | name: "add_metadata",
225 | description: "Add new metadata to existing nodes in the knowledge graph",
226 | inputSchema: {
227 | type: "object",
228 | properties: {
229 | metadata: {
230 | type: "array",
231 | description: "Array of metadata to add",
232 | items: {
233 | type: "object",
234 | description: "Metadata to add",
235 | properties: {
236 | nodeName: {type: "string", description: "The name of the node to add the metadata to"},
237 | contents: {
238 | type: "array",
239 | items: {type: "string", description: "Metadata content item"},
240 | description: "An array of metadata contents to add"
241 | },
242 | },
243 | required: ["nodeName", "contents"],
244 | },
245 | },
246 | },
247 | required: ["metadata"],
248 | },
249 | },
250 |
251 | {
252 | name: "delete_metadata",
253 | description: "Delete specific metadata from nodes in the knowledge graph",
254 | inputSchema: {
255 | type: "object",
256 | properties: {
257 | deletions: {
258 | type: "array",
259 | description: "Array of metadata deletions",
260 | items: {
261 | type: "object",
262 | description: "Metadata deletion",
263 | properties: {
264 | nodeName: {type: "string", description: "The name of the node containing the metadata"},
265 | metadata: {
266 | type: "array",
267 | items: {type: "string", description: "Metadata item to delete"},
268 | description: "An array of metadata to delete"
269 | },
270 | },
271 | required: ["nodeName", "metadata"],
272 | },
273 | },
274 | },
275 | required: ["deletions"],
276 | },
277 | }
278 | ];
279 |
280 | /**
281 | * Combined array of all static tools
282 | */
283 | export const allStaticTools: Tool[] = [
284 | ...graphTools,
285 | ...searchTools,
286 | ...metadataTools
287 | ];
--------------------------------------------------------------------------------
/src/integration/tools/registry/toolsRegistry.ts:
--------------------------------------------------------------------------------
1 | // src/tools/registry/toolsRegistry.ts
2 |
3 | import {allStaticTools} from './staticTools.js';
4 | import {dynamicToolManager} from './dynamicTools.js';
5 | import {formatToolError} from '@shared/index.js';
6 | import type {Tool, ToolResponse} from '@shared/index.js';
7 | import type {ApplicationManager} from '@application/index.js';
8 |
9 | /**
10 | * Central registry for all tools (both static and dynamic)
11 | */
12 | export class ToolsRegistry {
13 | private static instance: ToolsRegistry;
14 | private initialized = false;
15 | private tools: Map = new Map();
16 | private knowledgeGraphManager: ApplicationManager | null = null;
17 |
18 | private constructor() {
19 | }
20 |
21 | /**
22 | * Gets the singleton instance of ToolsRegistry
23 | */
24 | static getInstance(): ToolsRegistry {
25 | if (!ToolsRegistry.instance) {
26 | ToolsRegistry.instance = new ToolsRegistry();
27 | }
28 | return ToolsRegistry.instance;
29 | }
30 |
31 | /**
32 | * Initializes the registry with both static and dynamic tools
33 | */
34 | async initialize(knowledgeGraphManager: ApplicationManager): Promise {
35 | if (this.initialized) {
36 | return;
37 | }
38 |
39 | try {
40 | this.knowledgeGraphManager = knowledgeGraphManager;
41 |
42 | // Register static tools
43 | allStaticTools.forEach(tool => {
44 | this.tools.set(tool.name, tool);
45 | });
46 |
47 | // Initialize and register dynamic tools
48 | await dynamicToolManager.initialize();
49 | dynamicToolManager.getTools().forEach(tool => {
50 | this.tools.set(tool.name, tool);
51 | });
52 |
53 | this.initialized = true;
54 | console.error(`[ToolsRegistry] Initialized with ${this.tools.size} tools`);
55 | } catch (error) {
56 | console.error('[ToolsRegistry] Initialization error:', error);
57 | throw error;
58 | }
59 | }
60 |
61 | /**
62 | * Gets a specific tool by name
63 | */
64 | getTool(name: string): Tool | undefined {
65 | this.ensureInitialized();
66 | return this.tools.get(name);
67 | }
68 |
69 | /**
70 | * Gets all registered tools
71 | */
72 | getAllTools(): Tool[] {
73 | this.ensureInitialized();
74 | return Array.from(this.tools.values());
75 | }
76 |
77 | /**
78 | * Handles a tool call by delegating to the appropriate handler
79 | */
80 | async handleToolCall(toolName: string, args: Record): Promise {
81 | this.ensureInitialized();
82 |
83 | if (!this.knowledgeGraphManager) {
84 | return formatToolError({
85 | operation: toolName,
86 | error: 'KnowledgeGraphManager not initialized',
87 | suggestions: ["Initialize registry with KnowledgeGraphManager"],
88 | recoverySteps: ["Reinitialize ToolsRegistry with proper dependencies"]
89 | });
90 | }
91 |
92 | try {
93 | if (!this.tools.has(toolName)) {
94 | return formatToolError({
95 | operation: toolName,
96 | error: `Tool not found: ${toolName}`,
97 | context: {availableTools: Array.from(this.tools.keys())},
98 | suggestions: ["Verify tool name exists"]
99 | });
100 | }
101 |
102 | if (dynamicToolManager.isDynamicTool(toolName)) {
103 | return await dynamicToolManager.handleToolCall(
104 | toolName,
105 | args,
106 | this.knowledgeGraphManager
107 | );
108 | }
109 |
110 | // For static tools, return success response
111 | return {
112 | toolResult: {
113 | isError: false,
114 | data: args,
115 | actionTaken: `Executed tool: ${toolName}`,
116 | timestamp: new Date().toISOString(),
117 | content: []
118 | }
119 | };
120 | } catch (error) {
121 | return formatToolError({
122 | operation: toolName,
123 | error: error instanceof Error ? error.message : 'Unknown error occurred',
124 | context: {toolName, args},
125 | suggestions: [
126 | "Check tool name and arguments",
127 | "Verify tool registration"
128 | ],
129 | recoverySteps: [
130 | "Review tool documentation",
131 | "Ensure tool is properly registered"
132 | ]
133 | });
134 | }
135 | }
136 |
137 | /**
138 | * Checks if a specific tool exists
139 | */
140 | hasTool(name: string): boolean {
141 | this.ensureInitialized();
142 | return this.tools.has(name);
143 | }
144 |
145 | /**
146 | * Ensures the registry is initialized before use
147 | */
148 | private ensureInitialized(): void {
149 | if (!this.initialized) {
150 | throw new Error('Tools registry not initialized');
151 | }
152 | }
153 | }
154 |
155 | /**
156 | * Singleton instance of the ToolsRegistry
157 | */
158 | export const toolsRegistry = ToolsRegistry.getInstance();
159 |
160 | /**
161 | * Re-export types that might be needed by consumers
162 | */
163 | export type {
164 | Tool,
165 | ToolResponse,
166 | ApplicationManager
167 | };
--------------------------------------------------------------------------------
/src/shared/index.ts:
--------------------------------------------------------------------------------
1 | // src/shared/index.ts
2 |
3 | export * from './types/index.js';
4 | export * from './utils/index.js';
--------------------------------------------------------------------------------
/src/shared/types/index.ts:
--------------------------------------------------------------------------------
1 | // src/shared/types/index.ts
2 |
3 | export * from './operations.js';
4 | export * from './tools.js';
--------------------------------------------------------------------------------
/src/shared/types/operations.ts:
--------------------------------------------------------------------------------
1 | // src/types/operations.ts
2 |
3 | import type {Node, Edge, Graph} from '@core/index.js';
4 |
5 | /**
6 | * Edge update operation parameters
7 | */
8 | export interface EdgeUpdate {
9 | from: string;
10 | to: string;
11 | edgeType: string;
12 | newFrom?: string;
13 | newTo?: string;
14 | newEdgeType?: string;
15 | newWeight?: number;
16 | }
17 |
18 | /**
19 | * Edge filter parameters
20 | */
21 | export interface EdgeFilter {
22 | from?: string;
23 | to?: string;
24 | edgeType?: string;
25 | }
26 |
27 | /**
28 | * Metadata addition parameters
29 | */
30 | export interface MetadataAddition {
31 | nodeName: string;
32 | contents: string[];
33 | }
34 |
35 | /**
36 | * Metadata deletion parameters
37 | */
38 | export interface MetadataDeletion {
39 | nodeName: string;
40 | metadata: string[];
41 | }
42 |
43 | /**
44 | * Result of metadata operation
45 | */
46 | export interface MetadataResult {
47 | nodeName: string;
48 | addedMetadata: string[];
49 | }
50 |
51 | /**
52 | * Result of getting edges
53 | */
54 | export interface GetEdgesResult {
55 | edges: Edge[];
56 | }
57 |
58 | /**
59 | * Result of opening or searching nodes
60 | */
61 | export interface OpenNodesResult {
62 | nodes: Node[];
63 | edges: Edge[];
64 | }
65 |
66 | // Operation Results
67 | export interface GraphOperationResult {
68 | success: boolean;
69 | nodes?: Node[];
70 | edges?: Edge[];
71 | error?: string;
72 | }
73 |
74 | export interface SearchOperationResult {
75 | success: boolean;
76 | result?: Graph;
77 | error?: string;
78 | }
79 |
80 | export interface MetadataOperationResult {
81 | success: boolean;
82 | results?: MetadataResult[];
83 | error?: string;
84 | }
--------------------------------------------------------------------------------
/src/shared/types/tools.ts:
--------------------------------------------------------------------------------
1 | // src/types/tools.ts
2 |
3 | /**
4 | * Schema definition for tool parameters
5 | */
6 | export interface ToolSchema {
7 | type: string;
8 | properties: Record;
9 | required?: string[];
10 | }
11 |
12 | /**
13 | * Property definition in a tool schema
14 | */
15 | export interface SchemaProperty {
16 | type: string;
17 | description: string;
18 | items?: SchemaProperty;
19 | properties?: Record;
20 | required?: string[];
21 | enum?: string[];
22 | minimum?: number;
23 | maximum?: number;
24 | }
25 |
26 | /**
27 | * Tool definition
28 | */
29 | export interface Tool {
30 | name: string;
31 | description: string;
32 | inputSchema: ToolSchema;
33 | }
34 |
35 | /**
36 | * Content item in a tool result
37 | */
38 | export interface ToolResultContent {
39 | type: string;
40 | text: string;
41 | }
42 |
43 | /**
44 | * Core result template that both success and error responses use
45 | */
46 | export interface ToolResult {
47 | isError: boolean;
48 | content: ToolResultContent[]; // For message display
49 | data?: T;
50 | actionTaken?: string;
51 | timestamp: string;
52 | suggestions?: string[];
53 | recoverySteps?: string[];
54 | }
55 |
56 | /**
57 | * Wrapper interface that matches the MCP server response format
58 | */
59 | export interface ToolResponse {
60 | toolResult: ToolResult;
61 | }
62 |
63 | /**
64 | * Options for formatting a tool response
65 | */
66 | export interface ToolResponseOptions {
67 | data?: T;
68 | message?: string;
69 | actionTaken?: string;
70 | suggestions?: string[];
71 | }
72 |
73 | /**
74 | * Options for formatting a tool error
75 | */
76 | export interface ToolErrorOptions {
77 | operation: string;
78 | error: string;
79 | context?: Record;
80 | suggestions?: string[];
81 | recoverySteps?: string[];
82 | }
83 |
84 | /**
85 | * Options for formatting a partial success response
86 | */
87 | export interface PartialSuccessOptions {
88 | operation: string;
89 | attempted: T[];
90 | succeeded: T[];
91 | failed: T[];
92 | details: Record;
93 | }
94 |
95 | /**
96 | * Request format for tool calls
97 | */
98 | export interface ToolCallRequest {
99 | params: {
100 | name: string;
101 | arguments?: Record;
102 | };
103 | }
--------------------------------------------------------------------------------
/src/shared/utils/index.ts:
--------------------------------------------------------------------------------
1 | // src/shared/utils/index.ts
2 |
3 | export {
4 | formatToolResponse,
5 | formatToolError,
6 | formatPartialSuccess
7 | } from './responseFormatter.js';
--------------------------------------------------------------------------------
/src/shared/utils/responseFormatter.ts:
--------------------------------------------------------------------------------
1 | // src/utils/responseFormatter.ts
2 |
3 | import type {
4 | ToolResponse,
5 | ToolResult,
6 | ToolResponseOptions,
7 | ToolErrorOptions,
8 | PartialSuccessOptions
9 | } from '@shared/index.js';
10 |
11 | /**
12 | * Formats successful tool responses in a consistent, AI-friendly way.
13 | */
14 | export function formatToolResponse({
15 | data,
16 | message,
17 | actionTaken,
18 | suggestions = []
19 | }: ToolResponseOptions): ToolResponse {
20 | const toolResult: ToolResult = {
21 | isError: false,
22 | content: message ? [{type: "text", text: message}] : [],
23 | timestamp: new Date().toISOString()
24 | };
25 |
26 | if (data !== undefined) {
27 | toolResult.data = data;
28 | }
29 |
30 | if (actionTaken) {
31 | toolResult.actionTaken = actionTaken;
32 | }
33 |
34 | if (suggestions.length > 0) {
35 | toolResult.suggestions = suggestions;
36 | }
37 |
38 | return {toolResult};
39 | }
40 |
41 | /**
42 | * Formats error responses in a consistent, AI-friendly way.
43 | */
44 | export function formatToolError({
45 | operation,
46 | error,
47 | context,
48 | suggestions = [],
49 | recoverySteps = []
50 | }: ToolErrorOptions): ToolResponse {
51 | const toolResult: ToolResult = {
52 | isError: true,
53 | content: [
54 | {type: "text", text: `Error during ${operation}: ${error}`},
55 | ...(context ? [{type: "text", text: `Context: ${JSON.stringify(context)}`}] : [])
56 | ],
57 | timestamp: new Date().toISOString()
58 | };
59 |
60 | if (suggestions.length > 0) {
61 | toolResult.suggestions = suggestions;
62 | }
63 |
64 | if (recoverySteps.length > 0) {
65 | toolResult.recoverySteps = recoverySteps;
66 | }
67 |
68 | return {toolResult};
69 | }
70 |
71 | /**
72 | * Creates an informative message for partial success scenarios.
73 | */
74 | export function formatPartialSuccess({
75 | operation,
76 | attempted,
77 | succeeded,
78 | failed,
79 | details
80 | }: PartialSuccessOptions): ToolResponse {
81 | const toolResult: ToolResult = {
82 | isError: true,
83 | content: [
84 | {
85 | type: "text",
86 | text: `Partial success for ${operation}: ${succeeded.length} succeeded, ${failed.length} failed`
87 | },
88 | {
89 | type: "text",
90 | text: `Details: ${JSON.stringify(details)}`
91 | }
92 | ],
93 | data: {
94 | succeededItems: succeeded,
95 | failedItems: failed.map(item => ({
96 | item,
97 | reason: details[String(item)] || 'Unknown error'
98 | }))
99 | },
100 | suggestions: [
101 | "Review failed items and their reasons",
102 | "Consider retrying failed operations individually",
103 | "Verify requirements for failed items"
104 | ],
105 | timestamp: new Date().toISOString()
106 | };
107 |
108 | return {toolResult};
109 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "Node16",
5 | "moduleResolution": "Node16",
6 | "outDir": "dist",
7 | "rootDir": "src",
8 | "esModuleInterop": true,
9 | "allowSyntheticDefaultImports": true,
10 | "resolveJsonModule": true,
11 | "strict": true,
12 | "sourceMap": true,
13 | "forceConsistentCasingInFileNames": true,
14 | "allowJs": true,
15 | "checkJs": false,
16 | "declaration": true,
17 | "skipLibCheck": true,
18 | "baseUrl": "src",
19 | "paths": {
20 | "@core/*": [
21 | "core/*"
22 | ],
23 | "@infrastructure/*": [
24 | "infrastructure/*"
25 | ],
26 | "@application/*": [
27 | "application/*"
28 | ],
29 | "@integration/*": [
30 | "integration/*"
31 | ],
32 | "@shared/*": [
33 | "shared/*"
34 | ],
35 | "@data/*": [
36 | "data/*"
37 | ],
38 | "@config/*": [
39 | "config/*"
40 | ]
41 | }
42 | },
43 | "include": [
44 | "src/**/*"
45 | ],
46 | "exclude": [
47 | "node_modules",
48 | "dist"
49 | ]
50 | }
--------------------------------------------------------------------------------