@datafrog-io/n2n-nexus 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/build/index.js +49 -3
- package/build/resources/index.js +14 -5
- package/build/storage/index.js +25 -2
- package/build/tools/definitions.js +59 -13
- package/build/tools/handlers.js +72 -16
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -45,6 +45,28 @@ Nexus_Storage/
|
|
|
45
45
|
|
|
46
46
|
**Self-healing**: Core data files (e.g., `registry.json`, `discussion.json`) include automatic detection and repair mechanisms. If files are corrupted or missing, the system automatically rebuilds the initial state to ensure uninterrupted service.
|
|
47
47
|
|
|
48
|
+
## 🏷️ Project ID Conventions (Naming Standard)
|
|
49
|
+
|
|
50
|
+
To ensure clarity and prevent collisions in the flat local namespace, all Project IDs MUST follow the **Prefix Dictionary** format: `[prefix]_[project-name]`.
|
|
51
|
+
|
|
52
|
+
| Prefix | Category | Example |
|
|
53
|
+
| :--- | :--- | :--- |
|
|
54
|
+
| `web_` | Websites, landing pages, domain-based projects | `web_datafrog.io` |
|
|
55
|
+
| `api_` | Backend services, REST/gRPC APIs | `api_user-auth` |
|
|
56
|
+
| `chrome_` | Chrome extensions | `chrome_evisa-helper` |
|
|
57
|
+
| `vscode_` | VSCode extensions | `vscode_super-theme` |
|
|
58
|
+
| `mcp_` | MCP Servers and MCP-related tools | `mcp_github-repo` |
|
|
59
|
+
| `android_` | Native Android projects (Kotlin/Java) | `android_client-app` |
|
|
60
|
+
| `ios_` | Native iOS projects (Swift/ObjC) | `ios_client-app` |
|
|
61
|
+
| `flutter_` | **Mobile Cross-platform Special Case** | `flutter_unified-app` |
|
|
62
|
+
| `desktop_` | General desktop apps (Tauri, Electron, etc.) | `desktop_main-hub` |
|
|
63
|
+
| `lib_` | Shared libraries, SDKs, NPM/Python packages | `lib_crypto-core` |
|
|
64
|
+
| `bot_` | Bots (Discord, Slack, DingTalk, etc.) | `bot_auto-moderator` |
|
|
65
|
+
| `infra_` | Infrastructure as Code, CI/CD, DevOps scripts | `infra_k8s-config` |
|
|
66
|
+
| `doc_` | Pure technical handbooks, strategies, roadmaps | `doc_coding-guide` |
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
48
70
|
## 🛠️ Toolset
|
|
49
71
|
|
|
50
72
|
### A. Session & Context
|
package/build/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
-
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
5
|
import { CONFIG } from "./config.js";
|
|
5
6
|
import { StorageManager } from "./storage/index.js";
|
|
6
7
|
import { TOOL_DEFINITIONS, handleToolCall } from "./tools/index.js";
|
|
@@ -14,7 +15,7 @@ class NexusServer {
|
|
|
14
15
|
server;
|
|
15
16
|
currentProject = null;
|
|
16
17
|
constructor() {
|
|
17
|
-
this.server = new Server({ name: "n2n-nexus", version: "0.1.
|
|
18
|
+
this.server = new Server({ name: "n2n-nexus", version: "0.1.5" }, { capabilities: { resources: {}, tools: {}, prompts: {} } });
|
|
18
19
|
this.setupHandlers();
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
@@ -75,7 +76,10 @@ class NexusServer {
|
|
|
75
76
|
this.checkModerator(name);
|
|
76
77
|
const result = await handleToolCall(name, toolArgs, {
|
|
77
78
|
currentProject: this.currentProject,
|
|
78
|
-
setCurrentProject: (id) => { this.currentProject = id; }
|
|
79
|
+
setCurrentProject: (id) => { this.currentProject = id; },
|
|
80
|
+
notifyResourceUpdate: (uri) => {
|
|
81
|
+
this.server.sendResourceUpdated({ uri });
|
|
82
|
+
}
|
|
79
83
|
});
|
|
80
84
|
return result;
|
|
81
85
|
}
|
|
@@ -89,6 +93,48 @@ class NexusServer {
|
|
|
89
93
|
};
|
|
90
94
|
}
|
|
91
95
|
});
|
|
96
|
+
// --- Prompt Listing ---
|
|
97
|
+
this.server.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
|
98
|
+
prompts: [
|
|
99
|
+
{
|
|
100
|
+
name: "init_project_nexus",
|
|
101
|
+
description: "Step-by-step guide for registering a new project with proper ID naming conventions.",
|
|
102
|
+
arguments: [
|
|
103
|
+
{ name: "projectType", description: "Type: web, api, chrome, vscode, mcp, android, ios, flutter, desktop, lib, bot, infra, doc", required: true },
|
|
104
|
+
{ name: "technicalName", description: "Domain (e.g., example.com) or repo slug (e.g., my-library)", required: true }
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
}));
|
|
109
|
+
// --- Prompt Retrieval ---
|
|
110
|
+
this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
111
|
+
const { name, arguments: args } = request.params;
|
|
112
|
+
if (name === "init_project_nexus") {
|
|
113
|
+
const projectType = args?.projectType || "[TYPE]";
|
|
114
|
+
const technicalName = args?.technicalName || "[NAME]";
|
|
115
|
+
const projectId = `${projectType}_${technicalName}`;
|
|
116
|
+
return {
|
|
117
|
+
description: "Initialize a new Nexus project",
|
|
118
|
+
messages: [
|
|
119
|
+
{
|
|
120
|
+
role: "user",
|
|
121
|
+
content: {
|
|
122
|
+
type: "text",
|
|
123
|
+
text: `I want to register a new project in Nexus.\n\n**Project Type:** ${projectType}\n**Technical Name:** ${technicalName}`
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
role: "assistant",
|
|
128
|
+
content: {
|
|
129
|
+
type: "text",
|
|
130
|
+
text: `## Project ID Convention\n\nBased on your input, the correct Project ID is:\n\n\`\`\`\n${projectId}\n\`\`\`\n\n### Prefix Dictionary\n| Prefix | Use Case |\n|--------|----------|\n| web_ | Websites/Domains |\n| api_ | Backend Services |\n| chrome_ | Chrome Extensions |\n| vscode_ | VSCode Extensions |\n| mcp_ | MCP Servers |\n| android_ | Native Android |\n| ios_ | Native iOS |\n| flutter_ | Cross-platform Mobile |\n| desktop_ | Desktop Apps |\n| lib_ | Libraries/SDKs |\n| bot_ | Bots |\n| infra_ | Infrastructure as Code |\n| doc_ | Technical Docs |\n\n### Next Steps\n1. Call \`register_session_context\` with projectId: \`${projectId}\`\n2. Call \`sync_project_assets\` with your manifest and internal docs.`
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
throw new McpError(ErrorCode.InvalidRequest, `Unknown prompt: ${name}`);
|
|
137
|
+
});
|
|
92
138
|
}
|
|
93
139
|
async run() {
|
|
94
140
|
const transport = new StdioServerTransport();
|
package/build/resources/index.js
CHANGED
|
@@ -57,11 +57,20 @@ export async function listResources() {
|
|
|
57
57
|
{ uri: "mcp://hub/registry", name: "Global Project Registry", description: "Consolidated index of all local projects." },
|
|
58
58
|
{ uri: "mcp://docs/global-strategy", name: "Master Strategy Blueprint", description: "Top-level cross-project coordination." },
|
|
59
59
|
{ uri: "mcp://nexus/session", name: "Current Session Info", description: "Your identity and role in this Nexus instance." },
|
|
60
|
-
...projectIds.map(id =>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
...projectIds.map(id => {
|
|
61
|
+
const prefix = id.split("_")[0];
|
|
62
|
+
const typeLabel = {
|
|
63
|
+
web: "🌐 Website", api: "⚙️ API", chrome: "🧩 Chrome Ext",
|
|
64
|
+
vscode: "💻 VSCode Ext", mcp: "🔌 MCP Server", android: "📱 Android",
|
|
65
|
+
ios: "🍎 iOS", flutter: "📲 Flutter", desktop: "🖥️ Desktop",
|
|
66
|
+
lib: "📦 Library", bot: "🤖 Bot", infra: "☁️ Infra", doc: "📄 Docs"
|
|
67
|
+
}[prefix] || "📁 Project";
|
|
68
|
+
return {
|
|
69
|
+
uri: `mcp://hub/projects/${id}/manifest`,
|
|
70
|
+
name: `${typeLabel}: ${id}`,
|
|
71
|
+
description: `Structured metadata (Tech stack, relations) for ${id}`
|
|
72
|
+
};
|
|
73
|
+
})
|
|
65
74
|
],
|
|
66
75
|
resourceTemplates: [
|
|
67
76
|
{ uriTemplate: "mcp://hub/projects/{projectId}/internal-docs", name: "Internal Project Docs", description: "Markdown-based detailed implementation plans." }
|
package/build/storage/index.js
CHANGED
|
@@ -103,11 +103,20 @@ export class StorageManager {
|
|
|
103
103
|
return { nodes, edges };
|
|
104
104
|
}
|
|
105
105
|
// --- Discussion & Log Management ---
|
|
106
|
-
static async addGlobalLog(from, text) {
|
|
106
|
+
static async addGlobalLog(from, text, category) {
|
|
107
107
|
const logs = await this.loadJsonSafe(this.globalDiscussion, []);
|
|
108
|
-
logs.push({
|
|
108
|
+
logs.push({
|
|
109
|
+
timestamp: new Date().toISOString(),
|
|
110
|
+
from,
|
|
111
|
+
text,
|
|
112
|
+
category
|
|
113
|
+
});
|
|
109
114
|
await fs.writeFile(this.globalDiscussion, JSON.stringify(logs, null, 2));
|
|
110
115
|
}
|
|
116
|
+
static async getRecentLogs(count = 10) {
|
|
117
|
+
const logs = await this.loadJsonSafe(this.globalDiscussion, []);
|
|
118
|
+
return logs.slice(-count);
|
|
119
|
+
}
|
|
111
120
|
static async getProjectDocs(id) {
|
|
112
121
|
if (!id)
|
|
113
122
|
return null;
|
|
@@ -249,4 +258,18 @@ export class StorageManager {
|
|
|
249
258
|
}
|
|
250
259
|
return updatedCount;
|
|
251
260
|
}
|
|
261
|
+
/**
|
|
262
|
+
* Delete a project from the registry and disk.
|
|
263
|
+
*/
|
|
264
|
+
static async deleteProject(id) {
|
|
265
|
+
const registry = await this.listRegistry();
|
|
266
|
+
if (registry.projects[id]) {
|
|
267
|
+
delete registry.projects[id];
|
|
268
|
+
await fs.writeFile(this.registryFile, JSON.stringify(registry, null, 2), "utf-8");
|
|
269
|
+
}
|
|
270
|
+
const projectDir = path.join(this.projectsRoot, id);
|
|
271
|
+
if (await this.exists(projectDir)) {
|
|
272
|
+
await fs.rm(projectDir, { recursive: true, force: true });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
252
275
|
}
|
|
@@ -4,12 +4,21 @@
|
|
|
4
4
|
export const TOOL_DEFINITIONS = [
|
|
5
5
|
{
|
|
6
6
|
name: "register_session_context",
|
|
7
|
-
description: "Declare the
|
|
8
|
-
inputSchema: {
|
|
7
|
+
description: "[IDENTITY] Declare the PROJECT identity. Format: [prefix]_[technical-identifier]. (e.g., 'web_datafrog.io', 'mcp_nexus-core').",
|
|
8
|
+
inputSchema: {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
projectId: {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "Strict flat identifier. MUST start with a type-prefix (web_, api_, chrome_, vscode_, mcp_, android_, ios_, flutter_, desktop_, lib_, bot_, infra_, doc_) followed by an underscore and a technical name (Domain for websites, Repo name/Slug for code). Use kebab-case. No hierarchy dots except in domains."
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
required: ["projectId"]
|
|
17
|
+
}
|
|
9
18
|
},
|
|
10
19
|
{
|
|
11
20
|
name: "sync_project_assets",
|
|
12
|
-
description: "CRITICAL: Sync full project state. Both manifest and documentation are MANDATORY.",
|
|
21
|
+
description: "CRITICAL: [PREREQUISITE: register_session_context] Sync full project state. Both manifest and documentation are MANDATORY.",
|
|
13
22
|
inputSchema: {
|
|
14
23
|
type: "object",
|
|
15
24
|
properties: {
|
|
@@ -17,7 +26,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
17
26
|
type: "object",
|
|
18
27
|
description: "Full ProjectManifest metadata.",
|
|
19
28
|
properties: {
|
|
20
|
-
id: { type: "string" },
|
|
29
|
+
id: { type: "string", description: "Project ID. MUST follow '[prefix]_[technical-name]' format and match active session." },
|
|
21
30
|
name: { type: "string" },
|
|
22
31
|
description: { type: "string" },
|
|
23
32
|
techStack: { type: "array", items: { type: "string" } },
|
|
@@ -26,13 +35,13 @@ export const TOOL_DEFINITIONS = [
|
|
|
26
35
|
items: {
|
|
27
36
|
type: "object",
|
|
28
37
|
properties: {
|
|
29
|
-
targetId: { type: "string" },
|
|
38
|
+
targetId: { type: "string", description: "ID of the target project (e.g., 'acme.auth-service')." },
|
|
30
39
|
type: { type: "string", enum: ["dependency", "parent", "child", "related"] }
|
|
31
40
|
},
|
|
32
41
|
required: ["targetId", "type"]
|
|
33
42
|
}
|
|
34
43
|
},
|
|
35
|
-
lastUpdated: { type: "string", description: "ISO timestamp." },
|
|
44
|
+
lastUpdated: { type: "string", description: "ISO timestamp (e.g., 2025-12-29T...)." },
|
|
36
45
|
repositoryUrl: { type: "string", description: "GitHub repository URL." },
|
|
37
46
|
endpoints: {
|
|
38
47
|
type: "array",
|
|
@@ -81,7 +90,12 @@ export const TOOL_DEFINITIONS = [
|
|
|
81
90
|
},
|
|
82
91
|
{
|
|
83
92
|
name: "get_global_topology",
|
|
84
|
-
description: "Retrieve complete project relationship graph.",
|
|
93
|
+
description: "Retrieve complete project relationship graph. Use this to understand current IDs and their connections.",
|
|
94
|
+
inputSchema: { type: "object", properties: {} }
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "list_projects",
|
|
98
|
+
description: "List all existing projects registered in the Nexus Hub. Use this to find correct IDs before performing project-specific operations.",
|
|
85
99
|
inputSchema: { type: "object", properties: {} }
|
|
86
100
|
},
|
|
87
101
|
{
|
|
@@ -90,7 +104,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
90
104
|
inputSchema: {
|
|
91
105
|
type: "object",
|
|
92
106
|
properties: {
|
|
93
|
-
projectId: { type: "string", description: "Project
|
|
107
|
+
projectId: { type: "string", description: "Project ID (e.g., 'web_datafrog.io', 'mcp_nexus-hub')." },
|
|
94
108
|
include: {
|
|
95
109
|
type: "string",
|
|
96
110
|
enum: ["manifest", "docs", "repo", "endpoints", "api", "relations", "summary", "all"],
|
|
@@ -102,8 +116,29 @@ export const TOOL_DEFINITIONS = [
|
|
|
102
116
|
},
|
|
103
117
|
{
|
|
104
118
|
name: "post_global_discussion",
|
|
105
|
-
description: "
|
|
106
|
-
inputSchema: {
|
|
119
|
+
description: "Join the 'Nexus Meeting Room' to collaborate with other AI agents. Use this for initiating meetings, making cross-project proposals, or announcing key decisions. Every message is shared across all assistants in real-time.",
|
|
120
|
+
inputSchema: {
|
|
121
|
+
type: "object",
|
|
122
|
+
properties: {
|
|
123
|
+
message: { type: "string", description: "The core content of your speech, proposal, or announcement." },
|
|
124
|
+
category: {
|
|
125
|
+
type: "string",
|
|
126
|
+
enum: ["MEETING_START", "PROPOSAL", "DECISION", "UPDATE", "CHAT"],
|
|
127
|
+
description: "The nature of this message. Use MEETING_START to call for a synchronous discussion."
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
required: ["message"]
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: "read_recent_discussion",
|
|
135
|
+
description: "Quickly 'listen' to the last few messages in the Nexus Room to catch up on the context of the current meeting or collaboration.",
|
|
136
|
+
inputSchema: {
|
|
137
|
+
type: "object",
|
|
138
|
+
properties: {
|
|
139
|
+
count: { type: "number", description: "Number of recent messages to retrieve (defaults to 10).", default: 10 }
|
|
140
|
+
}
|
|
141
|
+
}
|
|
107
142
|
},
|
|
108
143
|
{
|
|
109
144
|
name: "update_global_strategy",
|
|
@@ -145,7 +180,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
145
180
|
inputSchema: {
|
|
146
181
|
type: "object",
|
|
147
182
|
properties: {
|
|
148
|
-
projectId: { type: "string", description: "Project ID to update" },
|
|
183
|
+
projectId: { type: "string", description: "Project ID to update (e.g., 'web_datafrog.io')." },
|
|
149
184
|
patch: {
|
|
150
185
|
type: "object",
|
|
151
186
|
description: "Fields to update (e.g., description, techStack, endpoints, apiSpec, relations)",
|
|
@@ -161,8 +196,8 @@ export const TOOL_DEFINITIONS = [
|
|
|
161
196
|
inputSchema: {
|
|
162
197
|
type: "object",
|
|
163
198
|
properties: {
|
|
164
|
-
oldId: { type: "string", description: "Current project ID" },
|
|
165
|
-
newId: { type: "string", description: "New project ID" }
|
|
199
|
+
oldId: { type: "string", description: "Current project ID (e.g., 'web_oldname.com')." },
|
|
200
|
+
newId: { type: "string", description: "New project ID following the '[prefix]_[name]' standard." }
|
|
166
201
|
},
|
|
167
202
|
required: ["oldId", "newId"]
|
|
168
203
|
}
|
|
@@ -178,5 +213,16 @@ export const TOOL_DEFINITIONS = [
|
|
|
178
213
|
},
|
|
179
214
|
required: ["action", "count"]
|
|
180
215
|
}
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: "delete_project",
|
|
219
|
+
description: "[ADMIN ONLY] Completely remove a project, its manifest, and all its assets from Nexus.",
|
|
220
|
+
inputSchema: {
|
|
221
|
+
type: "object",
|
|
222
|
+
properties: {
|
|
223
|
+
projectId: { type: "string", description: "The ID of the project to destroy." }
|
|
224
|
+
},
|
|
225
|
+
required: ["projectId"]
|
|
226
|
+
}
|
|
181
227
|
}
|
|
182
228
|
];
|
package/build/tools/handlers.js
CHANGED
|
@@ -2,6 +2,18 @@ import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
|
2
2
|
import { promises as fs } from "fs";
|
|
3
3
|
import { CONFIG } from "../config.js";
|
|
4
4
|
import { StorageManager } from "../storage/index.js";
|
|
5
|
+
/**
|
|
6
|
+
* Validation helper for Project IDs.
|
|
7
|
+
*/
|
|
8
|
+
function validateProjectId(id) {
|
|
9
|
+
if (!id)
|
|
10
|
+
throw new McpError(ErrorCode.InvalidParams, "Project ID cannot be empty.");
|
|
11
|
+
const validPrefixes = ["web_", "api_", "chrome_", "vscode_", "mcp_", "android_", "ios_", "flutter_", "desktop_", "lib_", "bot_", "infra_", "doc_"];
|
|
12
|
+
const hasPrefix = validPrefixes.some(p => id.startsWith(p));
|
|
13
|
+
if (!hasPrefix || id.includes("..") || id.startsWith("/") || id.endsWith("/")) {
|
|
14
|
+
throw new McpError(ErrorCode.InvalidParams, "Project ID must follow the standard '[prefix]_[technical-name]' format and cannot contain '..' or slashes.");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
5
17
|
/**
|
|
6
18
|
* Handles all tool executions
|
|
7
19
|
*/
|
|
@@ -20,8 +32,10 @@ export async function handleToolCall(name, toolArgs, ctx) {
|
|
|
20
32
|
return handleReadProject(toolArgs);
|
|
21
33
|
case "post_global_discussion":
|
|
22
34
|
return handlePostDiscussion(toolArgs, ctx);
|
|
35
|
+
case "read_recent_discussion":
|
|
36
|
+
return handleReadRecentDiscussion(toolArgs);
|
|
23
37
|
case "update_global_strategy":
|
|
24
|
-
return handleUpdateStrategy(toolArgs);
|
|
38
|
+
return handleUpdateStrategy(toolArgs, ctx);
|
|
25
39
|
case "sync_global_doc":
|
|
26
40
|
return handleSyncGlobalDoc(toolArgs);
|
|
27
41
|
case "list_global_docs":
|
|
@@ -31,9 +45,13 @@ export async function handleToolCall(name, toolArgs, ctx) {
|
|
|
31
45
|
case "update_project":
|
|
32
46
|
return handleUpdateProject(toolArgs);
|
|
33
47
|
case "rename_project":
|
|
34
|
-
return handleRenameProject(toolArgs);
|
|
48
|
+
return handleRenameProject(toolArgs, ctx);
|
|
49
|
+
case "list_projects":
|
|
50
|
+
return handleListProjects();
|
|
51
|
+
case "delete_project":
|
|
52
|
+
return handleRemoveProject(toolArgs, ctx);
|
|
35
53
|
case "moderator_maintenance":
|
|
36
|
-
return handleModeratorMaintenance(toolArgs);
|
|
54
|
+
return handleModeratorMaintenance(toolArgs, ctx);
|
|
37
55
|
default:
|
|
38
56
|
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
39
57
|
}
|
|
@@ -42,6 +60,7 @@ export async function handleToolCall(name, toolArgs, ctx) {
|
|
|
42
60
|
function handleRegisterSession(args, ctx) {
|
|
43
61
|
if (!args?.projectId)
|
|
44
62
|
throw new McpError(ErrorCode.InvalidParams, "Missing required parameter: projectId");
|
|
63
|
+
validateProjectId(args.projectId);
|
|
45
64
|
ctx.setCurrentProject(args.projectId);
|
|
46
65
|
return { content: [{ type: "text", text: `Active Nexus Context: ${args.projectId}` }] };
|
|
47
66
|
}
|
|
@@ -53,18 +72,24 @@ async function handleSyncProjectAssets(args, ctx) {
|
|
|
53
72
|
throw new McpError(ErrorCode.InvalidParams, "Both 'manifest' and 'internalDocs' are mandatory.");
|
|
54
73
|
}
|
|
55
74
|
const m = args.manifest;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
75
|
+
const requiredFields = ["id", "name", "description", "techStack", "relations", "lastUpdated", "repositoryUrl", "localPath", "endpoints", "apiSpec"];
|
|
76
|
+
for (const field of requiredFields) {
|
|
77
|
+
if (m[field] === undefined || m[field] === null) {
|
|
78
|
+
throw new McpError(ErrorCode.InvalidParams, `Project manifest incomplete. Missing field: ${field}`);
|
|
79
|
+
}
|
|
61
80
|
}
|
|
81
|
+
validateProjectId(m.id);
|
|
62
82
|
if (!await StorageManager.exists(m.localPath)) {
|
|
63
83
|
throw new McpError(ErrorCode.InvalidParams, `localPath does not exist: '${m.localPath}'. Please provide a valid directory path.`);
|
|
64
84
|
}
|
|
65
85
|
await StorageManager.saveProjectManifest(m);
|
|
66
86
|
await StorageManager.saveProjectDocs(ctx.currentProject, args.internalDocs);
|
|
67
87
|
await StorageManager.addGlobalLog("SYSTEM", `[${CONFIG.instanceId}@${ctx.currentProject}] Asset Sync: Full sync of manifest and docs.`);
|
|
88
|
+
// Notify updates
|
|
89
|
+
ctx.notifyResourceUpdate(`mcp://hub/projects/${m.id}/manifest`);
|
|
90
|
+
ctx.notifyResourceUpdate(`mcp://hub/projects/${m.id}/internal-docs`);
|
|
91
|
+
ctx.notifyResourceUpdate("mcp://hub/registry");
|
|
92
|
+
ctx.notifyResourceUpdate("mcp://chat/global");
|
|
68
93
|
return { content: [{ type: "text", text: "Project assets synchronized (Manifest + Docs)." }] };
|
|
69
94
|
}
|
|
70
95
|
async function handleUploadAsset(args, ctx) {
|
|
@@ -140,14 +165,16 @@ async function handleUpdateProject(args) {
|
|
|
140
165
|
const changedFields = Object.keys(args.patch).join(", ");
|
|
141
166
|
return { content: [{ type: "text", text: `Project '${args.projectId}' updated. Changed fields: ${changedFields}.` }] };
|
|
142
167
|
}
|
|
143
|
-
async function handleRenameProject(args) {
|
|
168
|
+
async function handleRenameProject(args, ctx) {
|
|
144
169
|
if (!args?.oldId || !args?.newId) {
|
|
145
170
|
throw new McpError(ErrorCode.InvalidParams, "Both 'oldId' and 'newId' are required.");
|
|
146
171
|
}
|
|
147
|
-
|
|
148
|
-
throw new McpError(ErrorCode.InvalidParams, "New ID cannot contain '..' or start/end with '/'.");
|
|
149
|
-
}
|
|
172
|
+
validateProjectId(args.newId);
|
|
150
173
|
const updatedCount = await StorageManager.renameProject(args.oldId, args.newId);
|
|
174
|
+
// Notify all affected project resources and registry
|
|
175
|
+
ctx.notifyResourceUpdate("mcp://hub/registry");
|
|
176
|
+
ctx.notifyResourceUpdate(`mcp://hub/projects/${args.newId}/manifest`);
|
|
177
|
+
ctx.notifyResourceUpdate("mcp://get_global_topology"); // Topology changed
|
|
151
178
|
return { content: [{ type: "text", text: `Project renamed: '${args.oldId}' → '${args.newId}'. Cascading updates: ${updatedCount} project(s).` }] };
|
|
152
179
|
}
|
|
153
180
|
// --- Global Handlers ---
|
|
@@ -158,16 +185,32 @@ async function handleGetTopology() {
|
|
|
158
185
|
async function handlePostDiscussion(args, ctx) {
|
|
159
186
|
if (!args?.message)
|
|
160
187
|
throw new McpError(ErrorCode.InvalidParams, "Message content cannot be empty.");
|
|
161
|
-
await StorageManager.addGlobalLog(`${CONFIG.instanceId}@${ctx.currentProject || "Global"}`, args.message);
|
|
162
|
-
|
|
188
|
+
await StorageManager.addGlobalLog(`${CONFIG.instanceId}@${ctx.currentProject || "Global"}`, args.message, args.category);
|
|
189
|
+
// Notify chat resource update
|
|
190
|
+
ctx.notifyResourceUpdate("mcp://chat/global");
|
|
191
|
+
return { content: [{ type: "text", text: `Message broadcasted to Nexus Room${args.category ? ` [${args.category}]` : ""}.` }] };
|
|
192
|
+
}
|
|
193
|
+
async function handleReadRecentDiscussion(args) {
|
|
194
|
+
const count = args?.count || 10;
|
|
195
|
+
const logs = await StorageManager.getRecentLogs(count);
|
|
196
|
+
return { content: [{ type: "text", text: JSON.stringify(logs, null, 2) }] };
|
|
163
197
|
}
|
|
164
|
-
async function handleUpdateStrategy(args) {
|
|
198
|
+
async function handleUpdateStrategy(args, ctx) {
|
|
165
199
|
if (!args?.content)
|
|
166
200
|
throw new McpError(ErrorCode.InvalidParams, "Strategy content cannot be empty.");
|
|
167
201
|
await fs.writeFile(StorageManager.globalBlueprint, args.content);
|
|
168
202
|
await StorageManager.addGlobalLog("SYSTEM", `[${CONFIG.instanceId}] Updated Coordination Strategy.`);
|
|
203
|
+
// Notify strategy update
|
|
169
204
|
return { content: [{ type: "text", text: "Strategy updated." }] };
|
|
170
205
|
}
|
|
206
|
+
async function handleRemoveProject(args, ctx) {
|
|
207
|
+
if (!args?.projectId)
|
|
208
|
+
throw new McpError(ErrorCode.InvalidParams, "projectId is required.");
|
|
209
|
+
await StorageManager.deleteProject(args.projectId);
|
|
210
|
+
ctx.notifyResourceUpdate("mcp://hub/registry");
|
|
211
|
+
ctx.notifyResourceUpdate("mcp://get_global_topology");
|
|
212
|
+
return { content: [{ type: "text", text: `Project '${args.projectId}' removed from Nexus.` }] };
|
|
213
|
+
}
|
|
171
214
|
async function handleSyncGlobalDoc(args) {
|
|
172
215
|
if (!args?.docId || !args?.title || !args?.content) {
|
|
173
216
|
throw new McpError(ErrorCode.InvalidParams, "All fields required: docId, title, content.");
|
|
@@ -189,17 +232,30 @@ async function handleReadGlobalDoc(args) {
|
|
|
189
232
|
return { content: [{ type: "text", text: content }] };
|
|
190
233
|
}
|
|
191
234
|
// --- Admin Handlers ---
|
|
192
|
-
async function
|
|
235
|
+
async function handleListProjects() {
|
|
236
|
+
const registry = await StorageManager.listRegistry();
|
|
237
|
+
const projects = Object.entries(registry.projects).map(([id, p]) => ({
|
|
238
|
+
id,
|
|
239
|
+
name: p.name,
|
|
240
|
+
summary: p.summary,
|
|
241
|
+
lastActive: p.lastActive
|
|
242
|
+
}));
|
|
243
|
+
return { content: [{ type: "text", text: JSON.stringify(projects, null, 2) }] };
|
|
244
|
+
}
|
|
245
|
+
// --- Admin Handlers ---
|
|
246
|
+
async function handleModeratorMaintenance(args, ctx) {
|
|
193
247
|
if (!args.action || args.count === undefined) {
|
|
194
248
|
throw new McpError(ErrorCode.InvalidParams, "Both 'action' and 'count' are mandatory for maintenance.");
|
|
195
249
|
}
|
|
196
250
|
if (args.action === "clear") {
|
|
197
251
|
await StorageManager.clearGlobalLogs();
|
|
252
|
+
ctx.notifyResourceUpdate("mcp://chat/global");
|
|
198
253
|
return { content: [{ type: "text", text: "History wiped." }] };
|
|
199
254
|
}
|
|
200
255
|
else {
|
|
201
256
|
try {
|
|
202
257
|
await StorageManager.pruneGlobalLogs(args.count);
|
|
258
|
+
ctx.notifyResourceUpdate("mcp://chat/global");
|
|
203
259
|
return { content: [{ type: "text", text: `Pruned ${args.count} logs.` }] };
|
|
204
260
|
}
|
|
205
261
|
catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datafrog-io/n2n-nexus",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Unified Project Asset & Collaboration Hub (MCP Server) designed for AI agent coordination, featuring structured metadata, real-time messaging, and dependency topology.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -53,4 +53,4 @@
|
|
|
53
53
|
"typescript-eslint": "^8.18.0",
|
|
54
54
|
"vitest": "^2.1.8"
|
|
55
55
|
}
|
|
56
|
-
}
|
|
56
|
+
}
|